blob: 68c5715ea3700efc43debeddc1bcd4e29541d511 [file] [log] [blame]
# Copyright (C) 2021 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.
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@kernel_toolchain_info//:dict.bzl", "CLANG_VERSION")
# Outputs of a kernel_build rule needed to build kernel_module's
_kernel_build_internal_outs = [
"Module.symvers",
"include/config/kernel.release",
]
def _debug_trap():
return """set -x
trap '>&2 /bin/date' DEBUG"""
def _debug_print_scripts(ctx, command):
if ctx.attr._debug_print_scripts[BuildSettingInfo].value:
print("""
# Script that runs %s:%s""" % (ctx.label, command))
def _reverse_dict(d):
"""Reverse a dictionary of {key: [value, ...]}
Return {value: [key, ...]}.
"""
ret = {}
for k, values in d.items():
for v in values:
if v not in ret:
ret[v] = []
ret[v].append(k)
return ret
def _getoptattr(thing, attr, default_value = None):
if hasattr(thing, attr):
return getattr(thing, attr)
return default_value
def _kernel_build_config_impl(ctx):
out_file = ctx.actions.declare_file(ctx.attr.name + ".generated")
command = "cat {srcs} > {out_file}".format(
srcs = " ".join([src.path for src in ctx.files.srcs]),
out_file = out_file.path,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = ctx.files.srcs,
outputs = [out_file],
command = command,
progress_message = "Generating build config {}".format(ctx.label),
)
return DefaultInfo(files = depset([out_file]))
kernel_build_config = rule(
implementation = _kernel_build_config_impl,
doc = "Create a build.config file by concatenating build config fragments.",
attrs = {
"srcs": attr.label_list(
allow_files = True,
doc = """List of build config fragments.
Order matters. To prevent buildifier from sorting the list, use the
`# do not sort` magic line. For example:
```
kernel_build_config(
name = "build.config.foo.mixed",
srcs = [
# do not sort
"build.config.mixed",
"build.config.foo",
],
)
```
""",
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
def _transform_kernel_build_outs(name, what, outs):
"""Transform `*outs` attributes for `kernel_build`.
- If `outs` is a list, return it directly.
- If `outs` is a dict, return `select(outs)`.
- Otherwise fail
"""
if outs == None:
return None
if type(outs) == type([]):
return outs
elif type(outs) == type({}):
return select(outs)
else:
fail("{}: Invalid type for {}: {}".format(name, what, type(outs)))
KernelFilesInfo = provider(doc = """Contains information of files that a kernel build produces.
In particular, this is required by the `base_kernel` attribute of a `kernel_build` rule.
""", fields = {
"files": "A list of files that this kernel build provides.",
})
def kernel_build(
name,
build_config,
outs,
srcs = None,
module_outs = None,
implicit_outs = None,
generate_vmlinux_btf = None,
deps = None,
base_kernel = None,
kconfig_ext = None,
dtstree = None,
toolchain_version = None,
**kwargs):
"""Defines a kernel build target with all dependent targets.
It uses a `build_config` to construct a deterministic build environment (e.g.
`common/build.config.gki.aarch64`). The kernel sources need to be declared
via srcs (using a `glob()`). outs declares the output files that are surviving
the build. The effective output file names will be
`$(name)/$(output_file)`. Any other artifact is not guaranteed to be
accessible after the rule has run. The default `toolchain_version` is defined
with the value in `common/build.config.constants`, but can be overriden.
A few additional labels are generated.
For example, if name is `"kernel_aarch64"`:
- `kernel_aarch64_uapi_headers` provides the UAPI kernel headers.
- `kernel_aarch64_headers` provides the kernel headers.
Args:
name: The final kernel target name, e.g. `"kernel_aarch64"`.
build_config: Label of the build.config file, e.g. `"build.config.gki.aarch64"`.
kconfig_ext: Label of an external Kconfig.ext file sourced by the GKI kernel.
srcs: The kernel sources (a `glob()`). If unspecified or `None`, it is the following:
```
glob(
["**"],
exclude = [
"**/.*", # Hidden files
"**/.*/**", # Files in hidden directories
"**/BUILD.bazel", # build files
"**/*.bzl", # build files
],
)
```
base_kernel: A label referring the base kernel build.
If set, the list of files specified in the `KernelFilesInfo` of the rule specified in
`base_kernel` is copied to a directory, and `KBUILD_MIXED_TREE` is set to the directory.
Setting `KBUILD_MIXED_TREE` effectively enables mixed build.
To set additional flags for mixed build, change `build_config` to a `kernel_build_config`
rule, with a build config fragment that contains the additional flags.
The label specified by `base_kernel` must conform to
[`KernelFilesInfo`](#kernelfilesinfo). Usually, this points to one of the following:
- `//common:kernel_{arch}`
- A `kernel_filegroup` rule, e.g.
```
load("//build/kleaf:common_kernels.bzl, "aarch64_outs")
kernel_filegroup(
name = "my_kernel_filegroup",
srcs = aarch64_outs,
)
```
generate_vmlinux_btf: If `True`, generates `vmlinux.btf` that is stripped off any debug
symbols, but contains type and symbol information within a .BTF section.
This is suitable for ABI analysis through BTF.
Requires that `"vmlinux"` is in `outs`.
deps: Additional dependencies to build this kernel.
module_outs: A list of in-tree drivers. Similar to `outs`, but for `*.ko` files.
If a `*.ko` kernel module should not be copied to `${DIST_DIR}`, it must be
included `implicit_outs` instead of `module_outs`. The list `implicit_outs + module_outs`
must include **all** `*.ko` files in `${OUT_DIR}`. If not, a build error is raised.
Like `outs`, `module_outs` are part of the
[`DefaultInfo`](https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html)
that this `kernel_build` returns. For example:
```
kernel_build(name = "kernel", module_outs = ["foo.ko"], ...)
copy_to_dist_dir(name = "kernel_dist", data = [":kernel"])
```
`foo.ko` will be included in the distribution.
Like `outs`, this may be a `dict`. If so, it is wrapped in
[`select()`](https://docs.bazel.build/versions/main/configurable-attributes.html). See
documentation for `outs` for more details.
outs: The expected output files.
Note: in-tree modules should be specified in `module_outs` instead.
This attribute must be either a `dict` or a `list`. If it is a `list`, for each item
in `out`:
- If `out` does not contain a slash, the build rule
automatically finds a file with name `out` in the kernel
build output directory `${OUT_DIR}`.
```
find ${OUT_DIR} -name {out}
```
There must be exactly one match.
The file is copied to the following in the output directory
`{name}/{out}`
Example:
```
kernel_build(name = "kernel_aarch64", outs = ["vmlinux"])
```
The bulid system copies `${OUT_DIR}/[<optional subdirectory>/]vmlinux`
to `kernel_aarch64/vmlinux`.
`kernel_aarch64/vmlinux` is the label to the file.
- If `out` contains a slash, the build rule locates the file in the
kernel build output directory `${OUT_DIR}` with path `out`
The file is copied to the following in the output directory
1. `{name}/{out}`
2. `{name}/$(basename {out})`
Example:
```
kernel_build(
name = "kernel_aarch64",
outs = ["arch/arm64/boot/vmlinux"])
```
The bulid system copies
`${OUT_DIR}/arch/arm64/boot/vmlinux`
to:
- `kernel_aarch64/arch/arm64/boot/vmlinux`
- `kernel_aarch64/vmlinux`
They are also the labels to the output files, respectively.
See `search_and_mv_output.py` for details.
Files in `outs` are part of the
[`DefaultInfo`](https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html)
that this `kernel_build` returns. For example:
```
kernel_build(name = "kernel", outs = ["vmlinux"], ...)
copy_to_dist_dir(name = "kernel_dist", data = [":kernel"])
```
`vmlinux` will be included in the distribution.
If it is a `dict`, it is wrapped in
[`select()`](https://docs.bazel.build/versions/main/configurable-attributes.html).
Example:
```
kernel_build(
name = "kernel_aarch64",
outs = {"config_foo": ["vmlinux"]})
```
If conditions in `config_foo` is met, the rule is equivalent to
```
kernel_build(
name = "kernel_aarch64",
outs = ["vmlinux"])
```
As explained above, the bulid system copies `${OUT_DIR}/[<optional subdirectory>/]vmlinux`
to `kernel_aarch64/vmlinux`.
`kernel_aarch64/vmlinux` is the label to the file.
Note that a `select()` may not be passed into `kernel_build()` because
[`select()` cannot be evaluated in macros](https://docs.bazel.build/versions/main/configurable-attributes.html#why-doesnt-select-work-in-macros).
Hence:
- [combining `select()`s](https://docs.bazel.build/versions/main/configurable-attributes.html#combining-selects)
is not allowed. Instead, expand the cartesian product.
- To use
[`AND` chaining](https://docs.bazel.build/versions/main/configurable-attributes.html#or-chaining)
or
[`OR` chaining](https://docs.bazel.build/versions/main/configurable-attributes.html#selectsconfig_setting_group),
use `selects.config_setting_group()`.
implicit_outs: Like `outs`, but not copied to the distribution directory.
Labels are created for each item in `implicit_outs` as in `outs`.
toolchain_version: The toolchain version to depend on.
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-attributes).
These arguments applies on the target with `{name}`, `{name}_headers`, `{name}_uapi_headers`, and `{name}_vmlinux_btf`.
"""
env_target_name = name + "_env"
config_target_name = name + "_config"
modules_prepare_target_name = name + "_modules_prepare"
uapi_headers_target_name = name + "_uapi_headers"
headers_target_name = name + "_headers"
if srcs == None:
srcs = native.glob(
["**"],
exclude = [
"**/.*",
"**/.*/**",
"**/BUILD.bazel",
"**/*.bzl",
],
)
_kernel_env(
name = env_target_name,
build_config = build_config,
kconfig_ext = kconfig_ext,
dtstree = dtstree,
srcs = srcs,
toolchain_version = toolchain_version,
)
_kernel_config(
name = config_target_name,
env = env_target_name,
srcs = srcs,
config = config_target_name + "/.config",
include_tar_gz = config_target_name + "/include.tar.gz",
)
_modules_prepare(
name = modules_prepare_target_name,
config = config_target_name,
srcs = srcs,
outdir_tar_gz = modules_prepare_target_name + "/outdir.tar.gz",
)
_kernel_build(
name = name,
config = config_target_name,
srcs = srcs,
outs = _transform_kernel_build_outs(name, "outs", outs),
module_outs = _transform_kernel_build_outs(name, "module_outs", module_outs),
implicit_outs = _transform_kernel_build_outs(name, "implicit_outs", implicit_outs),
internal_outs = _transform_kernel_build_outs(name, "internal_outs", _kernel_build_internal_outs),
deps = deps,
base_kernel = base_kernel,
modules_prepare = modules_prepare_target_name,
**kwargs
)
for out_name, out_attr_val in (
("outs", outs),
("module_outs", module_outs),
("implicit_outs", implicit_outs),
# internal_outs are opaque to the user, hence we don't create a alias (filegroup) for them.
):
if out_attr_val == None:
continue
if type(out_attr_val) == type([]):
for out in out_attr_val:
native.filegroup(name = name + "/" + out, srcs = [":" + name], output_group = out)
elif type(out_attr_val) == type({}):
# out_attr_val = {config_setting: [out, ...], ...}
# => reverse_dict = {out: [config_setting, ...], ...}
for out, config_settings in _reverse_dict(out_attr_val).items():
native.filegroup(
name = name + "/" + out,
# Use a select() to prevent this rule to build when config_setting is not fulfilled.
srcs = select({
config_setting: [":" + name]
for config_setting in config_settings
}),
output_group = out,
# Use "manual" tags to prevent it to be built with ...
tags = ["manual"],
)
else:
fail("Unexpected type {} for {}: {}".format(type(out_attr_val), out_name, out_attr_val))
_kernel_uapi_headers(
name = uapi_headers_target_name,
config = config_target_name,
srcs = srcs,
**kwargs
)
_kernel_headers(
name = headers_target_name,
kernel_build = name,
env = env_target_name,
# TODO: We need arch/ and include/ only.
srcs = srcs,
**kwargs
)
if generate_vmlinux_btf:
vmlinux_btf_name = name + "_vmlinux_btf"
_vmlinux_btf(
name = vmlinux_btf_name,
vmlinux = name + "/vmlinux",
env = env_target_name,
**kwargs
)
_DtsTreeInfo = provider(fields = {
"srcs": "DTS tree sources",
"makefile": "DTS tree makefile",
})
def _kernel_dtstree_impl(ctx):
return _DtsTreeInfo(
srcs = ctx.files.srcs,
makefile = ctx.file.makefile,
)
_kernel_dtstree = rule(
implementation = _kernel_dtstree_impl,
attrs = {
"srcs": attr.label_list(doc = "kernel device tree sources", allow_files = True),
"makefile": attr.label(mandatory = True, allow_single_file = True),
},
)
def kernel_dtstree(
name,
srcs = None,
makefile = None,
**kwargs):
"""Specify a kernel DTS tree.
Args:
srcs: sources of the DTS tree. Default is
```
glob(["**"], exclude = [
"**/.*",
"**/.*/**",
"**/BUILD.bazel",
"**/*.bzl",
])
```
makefile: Makefile of the DTS tree. Default is `:Makefile`, i.e. the `Makefile`
at the root of the package.
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-attributes).
"""
if srcs == None:
srcs = native.glob(
["**"],
exclude = [
"**/.*",
"**/.*/**",
"**/BUILD.bazel",
"**/*.bzl",
],
)
if makefile == None:
makefile = ":Makefile"
kwargs.update(
# This should be the exact list of arguments of kernel_dtstree.
name = name,
srcs = srcs,
makefile = makefile,
)
_kernel_dtstree(**kwargs)
_KernelEnvInfo = provider(fields = {
"dependencies": "dependencies required to use this environment setup",
"setup": "setup script to initialize the environment",
})
def _kernel_env_impl(ctx):
srcs = [
s
for s in ctx.files.srcs
if "/build.config" in s.path or s.path.startswith("build.config")
]
build_config = ctx.file.build_config
kconfig_ext = ctx.file.kconfig_ext
dtstree_makefile = None
dtstree_srcs = []
if ctx.attr.dtstree != None:
dtstree_makefile = ctx.attr.dtstree[_DtsTreeInfo].makefile
dtstree_srcs = ctx.attr.dtstree[_DtsTreeInfo].srcs
setup_env = ctx.file.setup_env
preserve_env = ctx.file.preserve_env
out_file = ctx.actions.declare_file("%s.sh" % ctx.attr.name)
dependencies = ctx.files._tools + ctx.files._host_tools
command = ""
if ctx.attr._debug_annotate_scripts[BuildSettingInfo].value:
command += _debug_trap()
if kconfig_ext:
command += """
export KCONFIG_EXT={kconfig_ext}
""".format(
kconfig_ext = kconfig_ext.short_path,
)
if dtstree_makefile:
command += """
export DTSTREE_MAKEFILE={dtstree}
""".format(
dtstree = dtstree_makefile.short_path,
)
command += """
# error on failures
set -e
set -o pipefail
"""
if ctx.attr._debug_annotate_scripts[BuildSettingInfo].value:
command += """
export MAKEFLAGS="${MAKEFLAGS} V=1"
"""
else:
command += """
# Run Make in silence mode to suppress most of the info output
export MAKEFLAGS="${MAKEFLAGS} -s"
"""
command += """
# Increase parallelism # TODO(b/192655643): do not use -j anymore
export MAKEFLAGS="${{MAKEFLAGS}} -j$(nproc)"
# create a build environment
source {build_utils_sh}
export BUILD_CONFIG={build_config}
source {setup_env}
# capture it as a file to be sourced in downstream rules
{preserve_env} > {out}
""".format(
build_utils_sh = ctx.file._build_utils_sh.path,
build_config = build_config.path,
setup_env = setup_env.path,
preserve_env = preserve_env.path,
out = out_file.path,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = srcs + [
ctx.file._build_utils_sh,
build_config,
setup_env,
preserve_env,
],
outputs = [out_file],
progress_message = "Creating build environment for %s" % ctx.attr.name,
command = command,
use_default_shell_env = True,
)
host_tool_path = ctx.files._host_tools[0].dirname
setup = ""
if ctx.attr._debug_annotate_scripts[BuildSettingInfo].value:
setup += _debug_trap()
setup += """
# error on failures
set -e
set -o pipefail
# utility functions
source {build_utils_sh}
# source the build environment
source {env}
# setup the PATH to also include the host tools
export PATH=$PATH:$PWD/{host_tool_path}
# setup LD_LIBRARY_PATH for prebuilts
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/{linux_x86_libs_path}
if [ -n "${{KCONFIG_EXT}}" ]; then
export KCONFIG_EXT_PREFIX=$(rel_path $(realpath $(dirname ${{KCONFIG_EXT}})) ${{ROOT_DIR}}/${{KERNEL_DIR}})/
fi
if [ -n "${{DTSTREE_MAKEFILE}}" ]; then
export dtstree=$(rel_path $(realpath $(dirname ${{DTSTREE_MAKEFILE}})) ${{ROOT_DIR}}/${{KERNEL_DIR}})
fi
""".format(
env = out_file.path,
host_tool_path = host_tool_path,
build_utils_sh = ctx.file._build_utils_sh.path,
linux_x86_libs_path = ctx.files._linux_x86_libs[0].dirname,
)
dependencies += [
out_file,
ctx.file._build_utils_sh,
]
if kconfig_ext:
dependencies.append(kconfig_ext)
dependencies += dtstree_srcs
return [
_KernelEnvInfo(
dependencies = dependencies,
setup = setup,
),
DefaultInfo(files = depset([out_file])),
]
def _get_tools(toolchain_version):
return [
Label(e)
for e in (
"//build:kernel-build-scripts",
"//prebuilts/build-tools:linux-x86",
"//prebuilts/kernel-build-tools:linux-x86",
"//prebuilts/clang/host/linux-x86/clang-%s:binaries" % toolchain_version,
)
]
_KernelToolchainInfo = provider(fields = {
"toolchain_version": "The toolchain version",
})
def _kernel_toolchain_aspect_impl(target, ctx):
if ctx.rule.kind == "_kernel_build":
return ctx.rule.attr.config[_KernelToolchainInfo]
if ctx.rule.kind == "_kernel_config":
return ctx.rule.attr.env[_KernelToolchainInfo]
if ctx.rule.kind == "_kernel_env":
return _KernelToolchainInfo(toolchain_version = ctx.rule.attr.toolchain_version)
if ctx.rule.kind == "kernel_filegroup":
# TODO(b/213939521): Support _KernelToolchainInfo on prebuilts
return _KernelToolchainInfo()
fail("{label}: Unable to get toolchain info because {kind} is not supported.".format(
kind = ctx.rule.kind,
label = ctx.label,
))
_kernel_toolchain_aspect = aspect(
implementation = _kernel_toolchain_aspect_impl,
doc = "An aspect describing the toolchain of a `_kernel_build`, `_kernel_config`, or `_kernel_env` rule.",
attr_aspects = [
"config",
"env",
],
)
_kernel_env = rule(
implementation = _kernel_env_impl,
doc = """Generates a rule that generates a source-able build environment.
A build environment is defined by a single entry build config file
that can refer to further build config files.
Example:
```
kernel_env(
name = "kernel_aarch64_env,
build_config = "build.config.gki.aarch64",
srcs = glob(["build.config.*"]),
)
```
""",
attrs = {
"build_config": attr.label(
mandatory = True,
allow_single_file = True,
doc = "label referring to the main build config",
),
"srcs": attr.label_list(
mandatory = True,
allow_files = True,
doc = """labels that this build config refers to, including itself.
E.g. ["build.config.gki.aarch64", "build.config.gki"]""",
),
"setup_env": attr.label(
allow_single_file = True,
default = Label("//build:_setup_env.sh"),
doc = "label referring to _setup_env.sh",
),
"preserve_env": attr.label(
allow_single_file = True,
default = Label("//build/kleaf:preserve_env.sh"),
doc = "label referring to the script capturing the environment",
),
"toolchain_version": attr.string(
doc = "the toolchain to use for this environment",
default = CLANG_VERSION,
),
"kconfig_ext": attr.label(
allow_single_file = True,
doc = "an external Kconfig.ext file sourced by the base kernel",
),
"dtstree": attr.label(
providers = [_DtsTreeInfo],
doc = "Device tree",
),
"_tools": attr.label_list(default = _get_tools),
"_host_tools": attr.label(default = "//build:host-tools"),
"_build_utils_sh": attr.label(
allow_single_file = True,
default = Label("//build:build_utils.sh"),
),
"_debug_annotate_scripts": attr.label(
default = "//build/kleaf:debug_annotate_scripts",
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
"_linux_x86_libs": attr.label(default = "//prebuilts/kernel-build-tools:linux-x86-libs"),
},
)
def _kernel_config_impl(ctx):
srcs = [
s
for s in ctx.files.srcs
if any([token in s.path for token in [
"Kbuild",
"Kconfig",
"Makefile",
"configs/",
"scripts/",
".fragment",
]])
]
config = ctx.outputs.config
include_tar_gz = ctx.outputs.include_tar_gz
lto_config_flag = ctx.attr.lto[BuildSettingInfo].value
lto_command = ""
if lto_config_flag != "default":
# none config
lto_config = {
"LTO_CLANG": "d",
"LTO_NONE": "e",
"LTO_CLANG_THIN": "d",
"LTO_CLANG_FULL": "d",
"THINLTO": "d",
}
if lto_config_flag == "thin":
lto_config.update(
LTO_CLANG = "e",
LTO_NONE = "d",
LTO_CLANG_THIN = "e",
THINLTO = "e",
)
elif lto_config_flag == "full":
lto_config.update(
LTO_CLANG = "e",
LTO_NONE = "d",
LTO_CLANG_FULL = "e",
)
lto_command = """
${{KERNEL_DIR}}/scripts/config --file ${{OUT_DIR}}/.config {configs}
make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} olddefconfig
""".format(configs = " ".join([
"-%s %s" % (value, key)
for key, value in lto_config.items()
]))
command = ctx.attr.env[_KernelEnvInfo].setup + """
# Pre-defconfig commands
eval ${{PRE_DEFCONFIG_CMDS}}
# Actual defconfig
make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} ${{DEFCONFIG}}
# Post-defconfig commands
eval ${{POST_DEFCONFIG_CMDS}}
# LTO configuration
{lto_command}
# Grab outputs
cp -p ${{OUT_DIR}}/.config {config}
tar czf {include_tar_gz} -C ${{OUT_DIR}} include/
""".format(
config = config.path,
include_tar_gz = include_tar_gz.path,
lto_command = lto_command,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = srcs,
outputs = [config, include_tar_gz],
tools = ctx.attr.env[_KernelEnvInfo].dependencies,
progress_message = "Creating kernel config %s" % ctx.attr.name,
command = command,
)
setup = ctx.attr.env[_KernelEnvInfo].setup + """
# Restore kernel config inputs
mkdir -p ${{OUT_DIR}}/include/
rsync -p -L {config} ${{OUT_DIR}}/.config
tar xf {include_tar_gz} -C ${{OUT_DIR}}
""".format(config = config.path, include_tar_gz = include_tar_gz.path)
return [
_KernelEnvInfo(
dependencies = ctx.attr.env[_KernelEnvInfo].dependencies +
[config, include_tar_gz],
setup = setup,
),
DefaultInfo(files = depset([config, include_tar_gz])),
]
_kernel_config = rule(
implementation = _kernel_config_impl,
doc = "Defines a kernel config target.",
attrs = {
"env": attr.label(
mandatory = True,
providers = [_KernelEnvInfo],
doc = "environment target that defines the kernel build environment",
),
"srcs": attr.label_list(mandatory = True, doc = "kernel sources", allow_files = True),
"config": attr.output(mandatory = True, doc = "the .config file"),
"include_tar_gz": attr.output(
mandatory = True,
doc = "the packaged include/ files",
),
"lto": attr.label(default = "//build/kleaf:lto"),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
_KernelBuildInfo = provider(fields = {
"modules_staging_archive": "Archive containing staging kernel modules. " +
"Does not contain the lib/modules/* suffix.",
"module_srcs": "sources for this kernel_build for building external modules",
"out_dir_kernel_headers_tar": "Archive containing headers in `OUT_DIR`",
"outs": "A list of File object corresponding to the `outs` attribute (excluding `module_outs`, `implicit_outs` and `internal_outs`)",
"base_kernel_files": "[Default outputs](https://docs.bazel.build/versions/main/skylark/rules.html#default-outputs) of the rule specified by `base_kernel`",
"interceptor_output": "`interceptor` log. See [`interceptor`](https://android.googlesource.com/kernel/tools/interceptor/) project.",
})
_SrcsInfo = provider(fields = {
"srcs": "The srcs attribute of a rule.",
})
def _srcs_aspect_impl(target, ctx):
return [_SrcsInfo(srcs = _getoptattr(ctx.rule.attr, "srcs"))]
_srcs_aspect = aspect(
implementation = _srcs_aspect_impl,
doc = "An aspect that retrieves srcs attribute from a rule.",
attr_aspects = ["srcs"],
)
_KernelBuildAspectInfo = provider(fields = {
"modules_prepare": "The *_modules_prepare target",
})
def _kernel_build_aspect_impl(target, ctx):
return [_KernelBuildAspectInfo(
modules_prepare = _getoptattr(ctx.rule.attr, "modules_prepare"),
)]
_kernel_build_aspect = aspect(
implementation = _kernel_build_aspect_impl,
doc = "An aspect describing attributes of a _kernel_build rule.",
attr_aspects = [
"modules_prepare",
],
)
def _kernel_build_check_toolchain(ctx):
"""
Check toolchain_version is the same as base_kernel.
"""
this_toolchain = ctx.attr.config[_KernelToolchainInfo].toolchain_version
base_toolchain = _getoptattr(ctx.attr.base_kernel[_KernelToolchainInfo], "toolchain_version")
# TODO(b/213939521): Support _KernelToolchainInfo on kernel_filegroup and drop the None check
if base_toolchain == None:
return
if this_toolchain != base_toolchain:
fail("""{this_label}:
ERROR: `toolchain_version` is "{this_toolchain}" for "{this_label}", but
`toolchain_version` is "{base_toolchain}" for "{base_kernel}" (`base_kernel`).
They must use the same `toolchain_version`.
Fix by setting `toolchain_version` of "{this_label}"
to be the one used by "{base_kernel}".
If "{base_kernel}" does not set `toolchain_version` explicitly, do not set
`toolchain_version` for "{this_label}" either.
""".format(
this_label = ctx.label,
this_toolchain = this_toolchain,
base_kernel = ctx.attr.base_kernel.label,
base_toolchain = base_toolchain,
))
def _kernel_build_impl(ctx):
kbuild_mixed_tree = None
base_kernel_files = []
if ctx.attr.base_kernel:
_kernel_build_check_toolchain(ctx)
# Create a directory for KBUILD_MIXED_TREE. Flatten the directory structure of the files
# that ctx.attr.base_kernel provides. declare_directory is sufficient because the directory should
# only change when the dependent ctx.attr.base_kernel changes.
kbuild_mixed_tree = ctx.actions.declare_directory("{}_kbuild_mixed_tree".format(ctx.label.name))
base_kernel_files = ctx.attr.base_kernel[KernelFilesInfo].files
kbuild_mixed_tree_command = """
# Restore GKI artifacts for mixed build
export KBUILD_MIXED_TREE=$(realpath {kbuild_mixed_tree})
rm -rf ${{KBUILD_MIXED_TREE}}
mkdir -p ${{KBUILD_MIXED_TREE}}
for base_kernel_file in {base_kernel_files}; do
ln -s $(readlink -m ${{base_kernel_file}}) ${{KBUILD_MIXED_TREE}}
done
""".format(
base_kernel_files = " ".join([file.path for file in base_kernel_files]),
kbuild_mixed_tree = kbuild_mixed_tree.path,
)
ctx.actions.run_shell(
inputs = base_kernel_files,
outputs = [kbuild_mixed_tree],
progress_message = "Creating KBUILD_MIXED_TREE",
command = kbuild_mixed_tree_command,
)
ruledir = ctx.actions.declare_directory(ctx.label.name)
inputs = [
ctx.file._search_and_mv_output,
]
inputs += ctx.files.srcs
inputs += ctx.files.deps
if kbuild_mixed_tree:
inputs.append(kbuild_mixed_tree)
# kernel_build(name="kenrel", outs=["out"])
# => _kernel_build(name="kernel", outs=["kernel/out"], internal_outs=["kernel/Module.symvers", ...])
# => all_output_names = ["foo", "Module.symvers", ...]
# all_output_files = {"out": {"foo": File(...)}, "internal_outs": {"Module.symvers": File(...)}, ...}
all_output_files = {}
for attr in ("outs", "module_outs", "implicit_outs", "internal_outs"):
all_output_files[attr] = {name: ctx.actions.declare_file("{}/{}".format(ctx.label.name, name)) for name in getattr(ctx.attr, attr)}
all_output_names = []
for d in all_output_files.values():
all_output_names += d.keys()
modules_staging_archive = ctx.actions.declare_file(
"{name}/modules_staging_dir.tar.gz".format(name = ctx.label.name),
)
out_dir_kernel_headers_tar = ctx.actions.declare_file(
"{name}/out-dir-kernel-headers.tar.gz".format(name = ctx.label.name),
)
interceptor_output = ctx.actions.declare_file("{name}/interceptor_output.bin".format(name = ctx.label.name))
modules_staging_dir = modules_staging_archive.dirname + "/staging"
# all outputs that |command| generates
command_outputs = [
ruledir,
modules_staging_archive,
out_dir_kernel_headers_tar,
interceptor_output,
]
for d in all_output_files.values():
command_outputs += d.values()
command = ""
command += ctx.attr.config[_KernelEnvInfo].setup
if kbuild_mixed_tree:
command += """
export KBUILD_MIXED_TREE=$(realpath {kbuild_mixed_tree})
""".format(
kbuild_mixed_tree = kbuild_mixed_tree.path,
)
command += """
# Actual kernel build
interceptor -r -l {interceptor_output} -- make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} ${{MAKE_GOALS}}
# Set variables and create dirs for modules
if [ "${{DO_NOT_STRIP_MODULES}}" != "1" ]; then
module_strip_flag="INSTALL_MOD_STRIP=1"
fi
mkdir -p {modules_staging_dir}
# Install modules
make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} DEPMOD=true O=${{OUT_DIR}} ${{module_strip_flag}} INSTALL_MOD_PATH=$(realpath {modules_staging_dir}) modules_install
# Archive headers in OUT_DIR
find ${{OUT_DIR}} -name *.h -print0 \
| tar czf {out_dir_kernel_headers_tar} \
--absolute-names \
--dereference \
--transform "s,.*$OUT_DIR,," \
--transform "s,^/,," \
--null -T -
# Grab outputs. If unable to find from OUT_DIR, look at KBUILD_MIXED_TREE as well.
{search_and_mv_output} --srcdir ${{OUT_DIR}} {kbuild_mixed_tree_arg} {dtstree_arg} --dstdir {ruledir} {all_output_names}
# Check if there are remaining *.ko files
remaining_ko_files=$(find ${{OUT_DIR}} -type f -name '*.ko')
if [[ ${{remaining_ko_files}} ]]; then
echo "ERROR: The following kernel modules are built but not copied. Add these lines to the module_outs attribute of {label}:"
for ko in ${{remaining_ko_files}}; do
echo ' "'"$(basename ${{ko}})"'",'
done
exit 1
fi
# Archive modules_staging_dir
tar czf {modules_staging_archive} -C {modules_staging_dir} .
# Clean up staging directories
rm -rf {modules_staging_dir}
""".format(
search_and_mv_output = ctx.file._search_and_mv_output.path,
kbuild_mixed_tree_arg = "--srcdir ${KBUILD_MIXED_TREE}" if kbuild_mixed_tree else "",
dtstree_arg = "--srcdir ${OUT_DIR}/${dtstree}",
ruledir = ruledir.path,
all_output_names = " ".join(all_output_names),
modules_staging_dir = modules_staging_dir,
modules_staging_archive = modules_staging_archive.path,
out_dir_kernel_headers_tar = out_dir_kernel_headers_tar.path,
interceptor_output = interceptor_output.path,
label = ctx.label,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = command_outputs,
tools = ctx.attr.config[_KernelEnvInfo].dependencies,
progress_message = "Building kernel %s" % ctx.attr.name,
command = command,
)
# Only outs and internal_outs are needed. But for simplicity, copy the full {ruledir}
# which includes module_outs and implicit_outs too.
env_info_dependencies = []
env_info_dependencies += ctx.attr.config[_KernelEnvInfo].dependencies
for d in all_output_files.values():
env_info_dependencies += d.values()
env_info_setup = ctx.attr.config[_KernelEnvInfo].setup + """
# Restore kernel build outputs
cp -R {ruledir}/* ${{OUT_DIR}}
""".format(ruledir = ruledir.path)
if kbuild_mixed_tree:
env_info_dependencies.append(kbuild_mixed_tree)
env_info_setup += """
export KBUILD_MIXED_TREE=$(realpath {kbuild_mixed_tree})
""".format(kbuild_mixed_tree = kbuild_mixed_tree.path)
env_info = _KernelEnvInfo(
dependencies = env_info_dependencies,
setup = env_info_setup,
)
module_srcs = [
s
for s in ctx.files.srcs
if s.path.endswith(".h") or any([token in s.path for token in [
"Makefile",
"scripts/",
]])
]
kernel_build_info = _KernelBuildInfo(
modules_staging_archive = modules_staging_archive,
module_srcs = module_srcs,
out_dir_kernel_headers_tar = out_dir_kernel_headers_tar,
outs = all_output_files["outs"].values(),
base_kernel_files = base_kernel_files,
interceptor_output = interceptor_output,
)
output_group_kwargs = {}
for d in all_output_files.values():
output_group_kwargs.update({name: depset([file]) for name, file in d.items()})
output_group_info = OutputGroupInfo(**output_group_kwargs)
default_info_files = all_output_files["outs"].values() + all_output_files["module_outs"].values()
default_info = DefaultInfo(files = depset(default_info_files))
kernel_files_info = KernelFilesInfo(files = default_info_files)
return [
env_info,
kernel_build_info,
output_group_info,
default_info,
kernel_files_info,
]
_kernel_build = rule(
implementation = _kernel_build_impl,
doc = "Defines a kernel build target.",
attrs = {
"config": attr.label(
mandatory = True,
providers = [_KernelEnvInfo],
aspects = [_kernel_toolchain_aspect],
doc = "the kernel_config target",
),
"srcs": attr.label_list(mandatory = True, doc = "kernel sources", allow_files = True),
"outs": attr.string_list(),
"module_outs": attr.string_list(doc = "output *.ko files"),
"internal_outs": attr.string_list(doc = "Like `outs`, but not in dist"),
"implicit_outs": attr.string_list(doc = "Like `outs`, but not in dist"),
"_search_and_mv_output": attr.label(
allow_single_file = True,
default = Label("//build/kleaf:search_and_mv_output.py"),
doc = "label referring to the script to process outputs",
),
"deps": attr.label_list(
allow_files = True,
),
"base_kernel": attr.label(
providers = [KernelFilesInfo],
aspects = [_kernel_toolchain_aspect],
),
"modules_prepare": attr.label(),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
def _modules_prepare_impl(ctx):
command = ctx.attr.config[_KernelEnvInfo].setup + """
# Prepare for the module build
make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}} modules_prepare
# Package files
tar czf {outdir_tar_gz} -C ${{OUT_DIR}} .
""".format(outdir_tar_gz = ctx.outputs.outdir_tar_gz.path)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = ctx.files.srcs,
outputs = [ctx.outputs.outdir_tar_gz],
tools = ctx.attr.config[_KernelEnvInfo].dependencies,
progress_message = "Preparing for module build %s" % ctx.label,
command = command,
)
setup = """
# Restore modules_prepare outputs. Assumes env setup.
[ -z ${{OUT_DIR}} ] && echo "modules_prepare setup run without OUT_DIR set!" && exit 1
tar xf {outdir_tar_gz} -C ${{OUT_DIR}}
""".format(outdir_tar_gz = ctx.outputs.outdir_tar_gz.path)
return [_KernelEnvInfo(
dependencies = [ctx.outputs.outdir_tar_gz],
setup = setup,
)]
_modules_prepare = rule(
implementation = _modules_prepare_impl,
attrs = {
"config": attr.label(
mandatory = True,
providers = [_KernelEnvInfo],
doc = "the kernel_config target",
),
"srcs": attr.label_list(mandatory = True, doc = "kernel sources", allow_files = True),
"outdir_tar_gz": attr.output(
mandatory = True,
doc = "the packaged ${OUT_DIR} files",
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
_KernelModuleInfo = provider(fields = {
"kernel_build": "kernel_build attribute of this module",
"modules_staging_archive": "Archive containing staging kernel modules. " +
"Contains the lib/modules/* suffix.",
"kernel_uapi_headers_archive": "Archive containing UAPI headers to use the module.",
})
def _check_kernel_build(kernel_modules, kernel_build, this_label):
"""Check that kernel_modules have the same kernel_build as the given one.
Args:
kernel_modules: the attribute of kernel_module dependencies. Should be
an attribute of a list of labels.
kernel_build: the attribute of kernel_build. Should be an attribute of
a label.
this_label: label of the module being checked.
"""
for kernel_module in kernel_modules:
if kernel_module[_KernelModuleInfo].kernel_build.label != \
kernel_build.label:
fail((
"{this_label} refers to kernel_build {kernel_build}, but " +
"depended kernel_module {dep} refers to kernel_build " +
"{dep_kernel_build}. They must refer to the same kernel_build."
).format(
this_label = this_label,
kernel_build = kernel_build.label,
dep = kernel_module.label,
dep_kernel_build = kernel_module[_KernelModuleInfo].kernel_build.label,
))
def _kernel_module_impl(ctx):
_check_kernel_build(ctx.attr.kernel_module_deps, ctx.attr.kernel_build, ctx.label)
modules_prepare = ctx.attr.kernel_build[_KernelBuildAspectInfo].modules_prepare
inputs = []
inputs += ctx.files.srcs
inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies
inputs += modules_prepare[_KernelEnvInfo].dependencies
inputs += ctx.attr.kernel_build[_KernelBuildInfo].module_srcs
inputs += ctx.files.makefile
inputs += [
ctx.file._search_and_mv_output,
]
for kernel_module_dep in ctx.attr.kernel_module_deps:
inputs += kernel_module_dep[_KernelEnvInfo].dependencies
modules_staging_archive = ctx.actions.declare_file("{}/modules_staging_archive.tar.gz".format(ctx.attr.name))
modules_staging_dir = modules_staging_archive.dirname + "/staging"
kernel_uapi_headers_archive = ctx.actions.declare_file("{}/kernel-uapi-headers.tar.gz".format(ctx.attr.name))
kernel_uapi_headers_dir = kernel_uapi_headers_archive.dirname + "/kernel-uapi-headers.tar.gz_staging"
outdir = modules_staging_archive.dirname # equivalent to declare_directory(ctx.attr.name)
# additional_outputs: archives + [basename(out) for out in outs]
additional_outputs = [
modules_staging_archive,
kernel_uapi_headers_archive,
]
# Original `outs` attribute of `kernel_module` macro.
original_outs = []
for out in ctx.outputs.outs:
# outdir includes target name at the end already. So short_name is the original
# token in `outs` of `kernel_module` macro.
# e.g. kernel_module(name = "foo", outs = ["bar"])
# => _kernel_module(name = "foo", outs = ["foo/bar"])
# => outdir = ".../foo"
# ctx.outputs.outs = [File(".../foo/bar")]
# => short_name = "bar"
short_name = out.path[len(outdir) + 1:]
original_outs.append(short_name)
if "/" in short_name:
additional_outputs.append(ctx.actions.declare_file("{name}/{basename}".format(
name = ctx.attr.name,
basename = out.basename,
)))
module_symvers = ctx.actions.declare_file("{}/Module.symvers".format(ctx.attr.name))
additional_declared_outputs = [
module_symvers,
]
command = ctx.attr.kernel_build[_KernelEnvInfo].setup
command += modules_prepare[_KernelEnvInfo].setup
command += """
# create dirs for modules
mkdir -p {modules_staging_dir} {kernel_uapi_headers_dir}/usr
""".format(
modules_staging_dir = modules_staging_dir,
kernel_uapi_headers_dir = kernel_uapi_headers_dir,
)
for kernel_module_dep in ctx.attr.kernel_module_deps:
command += kernel_module_dep[_KernelEnvInfo].setup
modules_staging_outs = []
for short_name in original_outs:
modules_staging_outs.append("lib/modules/*/extra/" + ctx.attr.ext_mod + "/" + short_name)
command += """
# Set variables
if [ "${{DO_NOT_STRIP_MODULES}}" != "1" ]; then
module_strip_flag="INSTALL_MOD_STRIP=1"
fi
ext_mod_rel=$(rel_path ${{ROOT_DIR}}/{ext_mod} ${{KERNEL_DIR}})
# Actual kernel module build
make -C {ext_mod} ${{TOOL_ARGS}} M=${{ext_mod_rel}} O=${{OUT_DIR}} KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}}
# Install into staging directory
make -C {ext_mod} ${{TOOL_ARGS}} DEPMOD=true M=${{ext_mod_rel}} \
O=${{OUT_DIR}} KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}} \
INSTALL_MOD_PATH=$(realpath {modules_staging_dir}) \
INSTALL_MOD_DIR=extra/{ext_mod} \
KERNEL_UAPI_HEADERS_DIR=$(realpath {kernel_uapi_headers_dir}) \
INSTALL_HDR_PATH=$(realpath {kernel_uapi_headers_dir}/usr) \
${{module_strip_flag}} modules_install
# Archive modules_staging_dir
(
modules_staging_archive=$(realpath {modules_staging_archive})
cd {modules_staging_dir}
if ! mod_order=$(ls lib/modules/*/extra/{ext_mod}/modules.order.*); then
# The modules.order.* file may not exist. Just keep it empty.
mod_order=
fi
tar czf ${{modules_staging_archive}} {modules_staging_outs} ${{mod_order}}
)
# Move files into place
{search_and_mv_output} --srcdir {modules_staging_dir}/lib/modules/*/extra/{ext_mod}/ --dstdir {outdir} {outs}
# Create headers archive
tar czf {kernel_uapi_headers_archive} --directory={kernel_uapi_headers_dir} usr/
# Remove staging dirs because they are not declared
rm -rf {modules_staging_dir} {kernel_uapi_headers_dir}
# Move Module.symvers
mv ${{OUT_DIR}}/${{ext_mod_rel}}/Module.symvers {module_symvers}
""".format(
ext_mod = ctx.attr.ext_mod,
search_and_mv_output = ctx.file._search_and_mv_output.path,
module_symvers = module_symvers.path,
modules_staging_dir = modules_staging_dir,
modules_staging_archive = modules_staging_archive.path,
outdir = outdir,
outs = " ".join(original_outs),
modules_staging_outs = " ".join(modules_staging_outs),
kernel_uapi_headers_archive = kernel_uapi_headers_archive.path,
kernel_uapi_headers_dir = kernel_uapi_headers_dir,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = ctx.outputs.outs + additional_outputs +
additional_declared_outputs,
command = command,
progress_message = "Building external kernel module {}".format(ctx.label),
)
setup = """
# Use a new shell to avoid polluting variables
(
# Set variables
# rel_path requires the existence of ${{ROOT_DIR}}/{ext_mod}, which may not be the case for
# _kernel_modules_install. Make that.
mkdir -p ${{ROOT_DIR}}/{ext_mod}
ext_mod_rel=$(rel_path ${{ROOT_DIR}}/{ext_mod} ${{KERNEL_DIR}})
# Restore Modules.symvers
mkdir -p ${{OUT_DIR}}/${{ext_mod_rel}}
cp {module_symvers} ${{OUT_DIR}}/${{ext_mod_rel}}/Module.symvers
# New shell ends
)
""".format(
ext_mod = ctx.attr.ext_mod,
module_symvers = module_symvers.path,
)
# Only declare outputs in the "outs" list. For additional outputs that this rule created,
# the label is available, but this rule doesn't explicitly return it in the info.
return [
DefaultInfo(files = depset(ctx.outputs.outs)),
_KernelEnvInfo(
dependencies = additional_declared_outputs,
setup = setup,
),
_KernelModuleInfo(
kernel_build = ctx.attr.kernel_build,
modules_staging_archive = modules_staging_archive,
kernel_uapi_headers_archive = kernel_uapi_headers_archive,
),
]
_kernel_module = rule(
implementation = _kernel_module_impl,
doc = """
""",
attrs = {
"srcs": attr.label_list(
mandatory = True,
allow_files = True,
),
"makefile": attr.label_list(
allow_files = True,
),
"kernel_build": attr.label(
mandatory = True,
providers = [_KernelEnvInfo, _KernelBuildInfo],
aspects = [_kernel_build_aspect],
),
"kernel_module_deps": attr.label_list(
providers = [_KernelEnvInfo, _KernelModuleInfo],
),
"ext_mod": attr.string(mandatory = True),
# Not output_list because it is not a list of labels. The list of
# output labels are inferred from name and outs.
"outs": attr.output_list(),
"_search_and_mv_output": attr.label(
allow_single_file = True,
default = Label("//build/kleaf:search_and_mv_output.py"),
doc = "Label referring to the script to process outputs",
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
def kernel_module(
name,
kernel_build,
outs = None,
srcs = None,
kernel_module_deps = None,
**kwargs):
"""Generates a rule that builds an external kernel module.
Example:
```
kernel_module(
name = "nfc",
srcs = glob([
"**/*.c",
"**/*.h",
# If there are Kbuild files, add them
"**/Kbuild",
# If there are additional makefiles in subdirectories, add them
"**/Makefile",
]),
outs = ["nfc.ko"],
kernel_build = "//common:kernel_aarch64",
)
```
Args:
name: Name of this kernel module.
srcs: Source files to build this kernel module. If unspecified or value
is `None`, it is by default the list in the above example:
```
glob([
"**/*.c",
"**/*.h",
"**/Kbuild",
"**/Makefile",
])
```
kernel_build: Label referring to the kernel_build module.
kernel_module_deps: A list of other kernel_module dependencies.
Before building this target, `Modules.symvers` from the targets in
`kernel_module_deps` are restored, so this target can be built against
them.
outs: The expected output files. If unspecified or value is `None`, it
is `["{name}.ko"]` by default.
For each token `out`, the build rule automatically finds a
file named `out` in the legacy kernel modules staging
directory. The file is copied to the output directory of
this package, with the label `name/out`.
- If `out` doesn't contain a slash, subdirectories are searched.
Example:
```
kernel_module(name = "nfc", outs = ["nfc.ko"])
```
The build system copies
```
<legacy modules staging dir>/lib/modules/*/extra/<some subdir>/nfc.ko
```
to
```
<package output dir>/nfc.ko
```
`nfc/nfc.ko` is the label to the file.
- If `out` contains slashes, its value is used. The file is
also copied to the top of package output directory.
For example:
```
kernel_module(name = "nfc", outs = ["foo/nfc.ko"])
```
The build system copies
```
<legacy modules staging dir>/lib/modules/*/extra/foo/nfc.ko
```
to
```
foo/nfc.ko
```
`nfc/foo/nfc.ko` is the label to the file.
The file is also copied to `<package output dir>/nfc.ko`.
`nfc/nfc.ko` is the label to the file.
See `search_and_mv_output.py` for details.
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-attributes).
"""
kwargs.update(
# This should be the exact list of arguments of kernel_module.
# Default arguments of _kernel_module go into _kernel_module_set_defaults.
name = name,
srcs = srcs,
kernel_build = kernel_build,
kernel_module_deps = kernel_module_deps,
outs = ["{name}/{out}".format(name = name, out = out) for out in outs] if outs else [],
)
kwargs = _kernel_module_set_defaults(kwargs)
_kernel_module(**kwargs)
def _kernel_module_set_defaults(kwargs):
"""
Set default values for `_kernel_module` that can't be specified in
`attr.*(default=...)` in rule().
"""
if kwargs.get("makefile") == None:
kwargs["makefile"] = native.glob(["Makefile"])
if kwargs.get("ext_mod") == None:
kwargs["ext_mod"] = native.package_name()
if kwargs.get("outs") == None:
kwargs["outs"] = ["{}.ko".format(kwargs["name"])]
if kwargs.get("srcs") == None:
kwargs["srcs"] = native.glob([
"**/*.c",
"**/*.h",
"**/Kbuild",
"**/Makefile",
])
return kwargs
def _kernel_modules_install_impl(ctx):
_check_kernel_build(ctx.attr.kernel_modules, ctx.attr.kernel_build, ctx.label)
modules_prepare = ctx.attr.kernel_build[_KernelBuildAspectInfo].modules_prepare
# A list of declared files for outputs of kernel_module rules
external_modules = []
inputs = []
inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies
inputs += modules_prepare[_KernelEnvInfo].dependencies
inputs += ctx.attr.kernel_build[_KernelBuildInfo].module_srcs
inputs += [
ctx.file._search_and_mv_output,
ctx.file._check_duplicated_files_in_archives,
ctx.attr.kernel_build[_KernelBuildInfo].modules_staging_archive,
]
for kernel_module in ctx.attr.kernel_modules:
inputs += kernel_module[_KernelEnvInfo].dependencies
inputs += [
kernel_module[_KernelModuleInfo].modules_staging_archive,
]
# Intentionally expand depset.to_list() to figure out what module files
# will be installed to module install directory.
for module_file in kernel_module[DefaultInfo].files.to_list():
declared_file = ctx.actions.declare_file("{}/{}".format(ctx.label.name, module_file.basename))
external_modules.append(declared_file)
modules_staging_archive = ctx.actions.declare_file("{}.tar.gz".format(ctx.label.name))
modules_staging_dir = modules_staging_archive.dirname + "/staging"
command = ""
command += ctx.attr.kernel_build[_KernelEnvInfo].setup
command += modules_prepare[_KernelEnvInfo].setup
command += """
# create dirs for modules
mkdir -p {modules_staging_dir}
# Restore modules_staging_dir from kernel_build
tar xf {kernel_build_modules_staging_archive} -C {modules_staging_dir}
modules_staging_archives="{kernel_build_modules_staging_archive}"
""".format(
modules_staging_dir = modules_staging_dir,
kernel_build_modules_staging_archive =
ctx.attr.kernel_build[_KernelBuildInfo].modules_staging_archive.path,
)
for kernel_module in ctx.attr.kernel_modules:
command += kernel_module[_KernelEnvInfo].setup
command += """
# Restore modules_staging_dir from depended kernel_module
tar xf {modules_staging_archive} -C {modules_staging_dir}
modules_staging_archives="${{modules_staging_archives}} {modules_staging_archive}"
""".format(
modules_staging_archive = kernel_module[_KernelModuleInfo].modules_staging_archive.path,
modules_staging_dir = modules_staging_dir,
)
# TODO(b/194347374): maybe run depmod.sh with CONFIG_SHELL?
command += """
# Check if there are duplicated files in modules_staging_archive of
# depended kernel_build and kernel_module's
{check_duplicated_files_in_archives} ${{modules_staging_archives}}
# Set variables
if [[ ! -f ${{OUT_DIR}}/include/config/kernel.release ]]; then
echo "No ${{OUT_DIR}}/include/config/kernel.release"
exit 1
fi
kernelrelease=$(cat ${{OUT_DIR}}/include/config/kernel.release 2> /dev/null)
mixed_build_prefix=
if [[ ${{KBUILD_MIXED_TREE}} ]]; then
mixed_build_prefix=${{KBUILD_MIXED_TREE}}/
fi
real_modules_staging_dir=$(realpath {modules_staging_dir})
# Run depmod
(
cd ${{OUT_DIR}} # for System.map when mixed_build_prefix is not set
INSTALL_MOD_PATH=${{real_modules_staging_dir}} ${{ROOT_DIR}}/${{KERNEL_DIR}}/scripts/depmod.sh depmod ${{kernelrelease}} ${{mixed_build_prefix}}
)
# Archive modules_staging_dir
tar czf {modules_staging_archive} -C {modules_staging_dir} .
""".format(
modules_staging_dir = modules_staging_dir,
modules_staging_archive = modules_staging_archive.path,
check_duplicated_files_in_archives = ctx.file._check_duplicated_files_in_archives.path,
)
if external_modules:
external_module_dir = external_modules[0].dirname
command += """
# Move external modules to declared output location
{search_and_mv_output} --srcdir {modules_staging_dir}/lib/modules/*/extra --dstdir {outdir} {filenames}
""".format(
modules_staging_dir = modules_staging_dir,
outdir = external_module_dir,
filenames = " ".join([declared_file.basename for declared_file in external_modules]),
search_and_mv_output = ctx.file._search_and_mv_output.path,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = external_modules + [
modules_staging_archive,
],
command = command,
progress_message = "Running depmod {}".format(ctx.label),
)
return [
DefaultInfo(files = depset(external_modules)),
_KernelModuleInfo(
kernel_build = ctx.attr.kernel_build,
modules_staging_archive = modules_staging_archive,
),
]
kernel_modules_install = rule(
implementation = _kernel_modules_install_impl,
doc = """Generates a rule that runs depmod in the module installation directory.
When including this rule to the `data` attribute of a `copy_to_dist_dir` rule,
all external kernel modules specified in `kernel_modules` are included in
distribution. This excludes `module_outs` in `kernel_build` to avoid conflicts.
Example:
```
kernel_modules_install(
name = "foo_modules_install",
kernel_build = ":foo", # A kernel_build rule
kernel_modules = [ # kernel_module rules
"//path/to/nfc:nfc_module",
],
)
kernel_build(
name = "foo",
outs = ["vmlinux"],
module_outs = ["core_module.ko"],
)
copy_to_dist_dir(
name = "foo_dist",
data = [
":foo", # Includes core_module.ko and vmlinux
":foo_modules_install", # Includes nfc_module
],
)
```
In `foo_dist`, specifying `foo_modules_install` in `data` won't include
`core_module.ko`, because it is already included in `foo` in `data`.
""",
attrs = {
"kernel_modules": attr.label_list(
providers = [_KernelEnvInfo, _KernelModuleInfo],
doc = "A list of labels referring to `kernel_module`s to install. Must have the same `kernel_build` as this rule.",
),
"kernel_build": attr.label(
providers = [_KernelEnvInfo, _KernelBuildInfo],
doc = "Label referring to the `kernel_build` module.",
aspects = [_kernel_build_aspect],
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
"_check_duplicated_files_in_archives": attr.label(
allow_single_file = True,
default = Label("//build/kleaf:check_duplicated_files_in_archives.py"),
doc = "Label referring to the script to process outputs",
),
"_search_and_mv_output": attr.label(
allow_single_file = True,
default = Label("//build/kleaf:search_and_mv_output.py"),
doc = "Label referring to the script to process outputs",
),
},
)
def _kernel_uapi_headers_impl(ctx):
out_file = ctx.actions.declare_file("{}/kernel-uapi-headers.tar.gz".format(ctx.label.name))
command = ctx.attr.config[_KernelEnvInfo].setup + """
# Create staging directory
mkdir -p {kernel_uapi_headers_dir}/usr
# Actual headers_install
make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} INSTALL_HDR_PATH=$(realpath {kernel_uapi_headers_dir}/usr) headers_install
# Create archive
tar czf {out_file} --directory={kernel_uapi_headers_dir} usr/
# Delete kernel_uapi_headers_dir because it is not declared
rm -rf {kernel_uapi_headers_dir}
""".format(
out_file = out_file.path,
kernel_uapi_headers_dir = out_file.path + "_staging",
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = ctx.files.srcs + ctx.attr.config[_KernelEnvInfo].dependencies,
outputs = [out_file],
progress_message = "Building UAPI kernel headers %s" % ctx.attr.name,
command = command,
)
return [
DefaultInfo(files = depset([out_file])),
]
_kernel_uapi_headers = rule(
implementation = _kernel_uapi_headers_impl,
doc = """Build kernel-uapi-headers.tar.gz""",
attrs = {
"srcs": attr.label_list(),
"config": attr.label(
mandatory = True,
providers = [_KernelEnvInfo],
doc = "the kernel_config target",
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
def _kernel_headers_impl(ctx):
inputs = []
inputs += ctx.files.srcs
inputs += ctx.attr.env[_KernelEnvInfo].dependencies
inputs += [
ctx.attr.kernel_build[_KernelBuildInfo].out_dir_kernel_headers_tar,
]
out_file = ctx.actions.declare_file("{}/kernel-headers.tar.gz".format(ctx.label.name))
command = ctx.attr.env[_KernelEnvInfo].setup + """
# Restore headers in ${{OUT_DIR}}
mkdir -p ${{OUT_DIR}}
tar xf {out_dir_kernel_headers_tar} -C ${{OUT_DIR}}
# Create archive
(
real_out_file=$(realpath {out_file})
cd ${{ROOT_DIR}}/${{KERNEL_DIR}}
find arch include ${{OUT_DIR}} -name *.h -print0 \
| tar czf ${{real_out_file}} \
--absolute-names \
--dereference \
--transform "s,.*$OUT_DIR,," \
--transform "s,^,kernel-headers/," \
--null -T -
)
""".format(
out_file = out_file.path,
out_dir_kernel_headers_tar = ctx.attr.kernel_build[_KernelBuildInfo].out_dir_kernel_headers_tar.path,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = [out_file],
progress_message = "Building kernel headers %s" % ctx.attr.name,
command = command,
)
return [
DefaultInfo(files = depset([out_file])),
]
_kernel_headers = rule(
implementation = _kernel_headers_impl,
doc = "Build kernel-headers.tar.gz",
attrs = {
"srcs": attr.label_list(),
"kernel_build": attr.label(
mandatory = True,
providers = [_KernelBuildInfo], # for out_dir_kernel_headers_tar only
),
"env": attr.label(
mandatory = True,
providers = [_KernelEnvInfo],
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
def _vmlinux_btf_impl(ctx):
inputs = [
ctx.file.vmlinux,
]
inputs += ctx.attr.env[_KernelEnvInfo].dependencies
out_file = ctx.actions.declare_file("{}/vmlinux.btf".format(ctx.label.name))
out_dir = out_file.dirname
command = ctx.attr.env[_KernelEnvInfo].setup + """
mkdir -p {out_dir}
cp -Lp {vmlinux} {vmlinux_btf}
pahole -J {vmlinux_btf}
llvm-strip --strip-debug {vmlinux_btf}
""".format(
vmlinux = ctx.file.vmlinux.path,
vmlinux_btf = out_file.path,
out_dir = out_dir,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = [out_file],
progress_message = "Building vmlinux.btf {}".format(ctx.label),
command = command,
)
return DefaultInfo(files = depset([out_file]))
_vmlinux_btf = rule(
implementation = _vmlinux_btf_impl,
doc = "Build vmlinux.btf",
attrs = {
"vmlinux": attr.label(
mandatory = True,
allow_single_file = True,
),
"env": attr.label(
mandatory = True,
providers = [_KernelEnvInfo],
),
"_debug_print_scripts": attr.label(default = "//build/kleaf:debug_print_scripts"),
},
)
def _build_modules_image_impl_common(
ctx,
what,
outputs,
build_command,
modules_staging_dir,
implicit_outputs = None,
additional_inputs = None):
"""Command implementation for building images that directly contain modules.
Args:
ctx: ctx
what: what is being built, for logging
outputs: list of `ctx.actions.declare_file`
build_command: the command to build `outputs` and `implicit_outputs`
modules_staging_dir: a staging directory for module installation
implicit_outputs: like `outputs`, but not installed to `DIST_DIR` (not returned in
`DefaultInfo`)
"""
kernel_build = ctx.attr.kernel_modules_install[_KernelModuleInfo].kernel_build
kernel_build_outs = kernel_build[_KernelBuildInfo].outs + kernel_build[_KernelBuildInfo].base_kernel_files
system_map = None
for kernel_build_out in kernel_build_outs:
if kernel_build_out.basename == "System.map":
if system_map != None:
fail("{}: dependent kernel_build {} has multiple System.map in outs:\n {}\n {}".format(
ctx.label,
kernel_build,
system_map.path,
kernel_build_out.path,
))
system_map = kernel_build_out
if system_map == None:
fail("{}: dependent kernel_build {} has no System.map in outs".format(
ctx.label,
kernel_build,
))
modules_staging_archive = ctx.attr.kernel_modules_install[_KernelModuleInfo].modules_staging_archive
inputs = []
if additional_inputs != None:
inputs += additional_inputs
inputs += [
system_map,
modules_staging_archive,
]
inputs += ctx.files.deps
inputs += kernel_build[_KernelEnvInfo].dependencies
command_outputs = []
command_outputs += outputs
if implicit_outputs != None:
command_outputs += implicit_outputs
command = ""
command += kernel_build[_KernelEnvInfo].setup
command += """
# create staging dirs
mkdir -p {modules_staging_dir}
# Restore modules_staging_dir from kernel_modules_install
tar xf {modules_staging_archive} -C {modules_staging_dir}
# Restore System.map to DIST_DIR for run_depmod in create_modules_staging
mkdir -p ${{DIST_DIR}}
cp {system_map} ${{DIST_DIR}}/System.map
{build_command}
# remove staging dirs
rm -rf {modules_staging_dir}
""".format(
modules_staging_dir = modules_staging_dir,
modules_staging_archive = modules_staging_archive.path,
system_map = system_map.path,
build_command = build_command,
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = command_outputs,
progress_message = "Building {} {}".format(what, ctx.label),
command = command,
)
return DefaultInfo(files = depset(outputs))
def _build_modules_image_attrs_common(additional = None):
"""Common attrs for rules that builds images that directly contain modules."""
ret = {
"kernel_modules_install": attr.label(
mandatory = True,
providers = [_KernelModuleInfo],
),
"deps": attr.label_list(
allow_files = True,
),
"_debug_print_scripts": attr.label(
default = "//build/kleaf:debug_print_scripts",
),
}
if additional != None:
ret.update(additional)
return ret
_InitramfsInfo = provider(fields = {
"initramfs_img": "Output image",
"initramfs_staging_archive": "Archive of initramfs staging directory",
})
def _initramfs_impl(ctx):
initramfs_img = ctx.actions.declare_file("{}/initramfs.img".format(ctx.label.name))
modules_load = ctx.actions.declare_file("{}/modules.load".format(ctx.label.name))
vendor_boot_modules_load = ctx.outputs.vendor_boot_modules_load
initramfs_staging_archive = ctx.actions.declare_file("{}/initramfs_staging_archive.tar.gz".format(ctx.label.name))
outputs = [
initramfs_img,
modules_load,
vendor_boot_modules_load,
]
modules_staging_dir = initramfs_img.dirname + "/staging"
initramfs_staging_dir = modules_staging_dir + "/initramfs_staging"
command = """
mkdir -p {initramfs_staging_dir}
# Build initramfs
create_modules_staging "${{MODULES_LIST}}" {modules_staging_dir} \
{initramfs_staging_dir} "${{MODULES_BLOCKLIST}}" "-e"
modules_root_dir=$(echo {initramfs_staging_dir}/lib/modules/*)
cp ${{modules_root_dir}}/modules.load {modules_load}
cp ${{modules_root_dir}}/modules.load {vendor_boot_modules_load}
echo "${{MODULES_OPTIONS}}" > ${{modules_root_dir}}/modules.options
mkbootfs "{initramfs_staging_dir}" >"{modules_staging_dir}/initramfs.cpio"
${{RAMDISK_COMPRESS}} "{modules_staging_dir}/initramfs.cpio" >"{initramfs_img}"
# Archive initramfs_staging_dir
tar czf {initramfs_staging_archive} -C {initramfs_staging_dir} .
# Remove staging directories
rm -rf {initramfs_staging_dir}
""".format(
modules_staging_dir = modules_staging_dir,
initramfs_staging_dir = initramfs_staging_dir,
modules_load = modules_load.path,
vendor_boot_modules_load = vendor_boot_modules_load.path,
initramfs_img = initramfs_img.path,
initramfs_staging_archive = initramfs_staging_archive.path,
)
default_info = _build_modules_image_impl_common(
ctx = ctx,
what = "initramfs",
outputs = outputs,
build_command = command,
modules_staging_dir = modules_staging_dir,
implicit_outputs = [
initramfs_staging_archive,
],
)
return [
default_info,
_InitramfsInfo(
initramfs_img = initramfs_img,
initramfs_staging_archive = initramfs_staging_archive,
),
]
_initramfs = rule(
implementation = _initramfs_impl,
doc = """Build initramfs.
When included in a `copy_to_dist_dir` rule, this rule copies the following to `DIST_DIR`:
- `initramfs.img`
- `modules.load`
- `vendor_boot.modules.load`
An additional label, `{name}/vendor_boot.modules.load`, is declared to point to the
corresponding files.
""",
attrs = _build_modules_image_attrs_common({
"vendor_boot_modules_load": attr.output(),
}),
)
def _system_dlkm_image_impl(ctx):
system_dlkm_img = ctx.actions.declare_file("{}/system_dlkm.img".format(ctx.label.name))
system_dlkm_staging_archive = ctx.actions.declare_file("{}/system_dlkm_staging_archive.tar.gz".format(ctx.label.name))
modules_staging_dir = system_dlkm_img.dirname + "/staging"
system_dlkm_staging_dir = modules_staging_dir + "/system_dlkm_staging"
command = """
mkdir -p {system_dlkm_staging_dir}
# Build system_dlkm.img
create_modules_staging "${{MODULES_LIST}}" {modules_staging_dir} \
{system_dlkm_staging_dir} "${{MODULES_BLOCKLIST}}" "-e"
modules_root_dir=$(ls {system_dlkm_staging_dir}/lib/modules/*)
# Build system_dlkm.img with signed GKI modules
mkfs.erofs -zlz4hc "{system_dlkm_img}" "{system_dlkm_staging_dir}"
# Archive system_dlkm_staging_dir
tar czf {system_dlkm_staging_archive} -C {system_dlkm_staging_dir} .
# Remove staging directories
rm -rf {system_dlkm_staging_dir}
""".format(
modules_staging_dir = modules_staging_dir,
system_dlkm_staging_dir = system_dlkm_staging_dir,
system_dlkm_img = system_dlkm_img.path,
system_dlkm_staging_archive = system_dlkm_staging_archive.path,
)
default_info = _build_modules_image_impl_common(
ctx = ctx,
what = "system_dlkm",
outputs = [system_dlkm_img, system_dlkm_staging_archive],
build_command = command,
modules_staging_dir = modules_staging_dir,
)
return [default_info]
_system_dlkm_image = rule(
implementation = _system_dlkm_image_impl,
doc = """Build system_dlkm.img an erofs image with GKI modules.
When included in a `copy_to_dist_dir` rule, this rule copies the `system_dlkm.img` to `DIST_DIR`.
""",
attrs = _build_modules_image_attrs_common(),
)
def _vendor_dlkm_image_impl(ctx):
vendor_dlkm_img = ctx.actions.declare_file("{}/vendor_dlkm.img".format(ctx.label.name))
vendor_dlkm_modules_load = ctx.actions.declare_file("{}/vendor_dlkm.modules.load".format(ctx.label.name))
modules_staging_dir = vendor_dlkm_img.dirname + "/staging"
vendor_dlkm_staging_dir = modules_staging_dir + "/vendor_dlkm_staging"
command = """
# Restore vendor_boot.modules.load
cp {vendor_boot_modules_load} ${{DIST_DIR}}/vendor_boot.modules.load
# Build vendor_dlkm
mkdir -p {vendor_dlkm_staging_dir}
(
MODULES_STAGING_DIR={modules_staging_dir}
VENDOR_DLKM_STAGING_DIR={vendor_dlkm_staging_dir}
build_vendor_dlkm
)
# Move output files into place
mv "${{DIST_DIR}}/vendor_dlkm.img" {vendor_dlkm_img}
mv "${{DIST_DIR}}/vendor_dlkm.modules.load" {vendor_dlkm_modules_load}
# Remove staging directories
rm -rf {vendor_dlkm_staging_dir}
""".format(
vendor_boot_modules_load = ctx.file.vendor_boot_modules_load.path,
modules_staging_dir = modules_staging_dir,
vendor_dlkm_staging_dir = vendor_dlkm_staging_dir,
vendor_dlkm_img = vendor_dlkm_img.path,
vendor_dlkm_modules_load = vendor_dlkm_modules_load.path,
)
return _build_modules_image_impl_common(
ctx = ctx,
what = "vendor_dlkm",
outputs = [vendor_dlkm_img, vendor_dlkm_modules_load],
build_command = command,
modules_staging_dir = modules_staging_dir,
additional_inputs = [ctx.file.vendor_boot_modules_load],
)
_vendor_dlkm_image = rule(
implementation = _vendor_dlkm_image_impl,
doc = """Build vendor_dlkm image.
Execute `build_vendor_dlkm` in `build_utils.sh`.
When included in a `copy_to_dist_dir` rule, this rule copies a `vendor_dlkm.img` to `DIST_DIR`.
""",
attrs = _build_modules_image_attrs_common({
"vendor_boot_modules_load": attr.label(
allow_single_file = True,
doc = """File to `vendor_boot.modules.load`.
Modules listed in this file is stripped away from the `vendor_dlkm` image.""",
),
}),
)
def _boot_images_impl(ctx):
initramfs_staging_archive = ctx.attr.initramfs[_InitramfsInfo].initramfs_staging_archive
outdir = ctx.actions.declare_directory(ctx.label.name)
modules_staging_dir = outdir.path + "/staging"
initramfs_staging_dir = modules_staging_dir + "/initramfs_staging"
mkbootimg_staging_dir = modules_staging_dir + "/mkbootimg_staging"
outs = []
for out in ctx.outputs.outs:
outs.append(out.short_path[len(outdir.short_path) + 1:])
kernel_build_outs = ctx.attr.kernel_build[_KernelBuildInfo].outs + ctx.attr.kernel_build[_KernelBuildInfo].base_kernel_files
inputs = [
ctx.attr.initramfs[_InitramfsInfo].initramfs_img,
initramfs_staging_archive,
ctx.file.mkbootimg,
ctx.file._search_and_mv_output,
]
inputs += ctx.files.deps
inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies
inputs += kernel_build_outs
command = ""
command += ctx.attr.kernel_build[_KernelEnvInfo].setup
command += """
# Create and restore initramfs_staging_dir
mkdir -p {initramfs_staging_dir}
tar xf {initramfs_staging_archive} -C {initramfs_staging_dir}
# Create and restore DIST_DIR.
# We don't need all of *_for_dist. Copying all declared outputs of kernel_build is
# sufficient.
mkdir -p ${{DIST_DIR}}
cp {kernel_build_outs} ${{DIST_DIR}}
cp {initramfs_img} ${{DIST_DIR}}/initramfs.img
# Build boot images
(
INITRAMFS_STAGING_DIR={initramfs_staging_dir}
MKBOOTIMG_STAGING_DIR=$(realpath {mkbootimg_staging_dir})
build_boot_images
)
{search_and_mv_output} --srcdir ${{DIST_DIR}} --dstdir {outdir} {outs}
# Remove staging directories
rm -rf {modules_staging_dir}
""".format(
initramfs_staging_dir = initramfs_staging_dir,
mkbootimg_staging_dir = mkbootimg_staging_dir,
search_and_mv_output = ctx.file._search_and_mv_output.path,
outdir = outdir.path,
outs = " ".join(outs),
modules_staging_dir = modules_staging_dir,
initramfs_staging_archive = initramfs_staging_archive.path,
initramfs_img = ctx.attr.initramfs[_InitramfsInfo].initramfs_img.path,
kernel_build_outs = " ".join([out.path for out in kernel_build_outs]),
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = ctx.outputs.outs + [outdir],
progress_message = "Building boot images {}".format(ctx.label),
command = command,
)
_boot_images = rule(
implementation = _boot_images_impl,
doc = """Build boot images, including `boot.img`, `vendor_boot.img`, etc.
Execute `build_boot_images` in `build_utils.sh`.""",
attrs = {
"kernel_build": attr.label(
mandatory = True,
providers = [_KernelEnvInfo, _KernelBuildInfo],
),
"initramfs": attr.label(
providers = [_InitramfsInfo],
),
"deps": attr.label_list(
allow_files = True,
),
"outs": attr.output_list(),
"mkbootimg": attr.label(
allow_single_file = True,
default = "//tools/mkbootimg:mkbootimg.py",
),
"_debug_print_scripts": attr.label(
default = "//build/kleaf:debug_print_scripts",
),
"_search_and_mv_output": attr.label(
allow_single_file = True,
default = Label("//build/kleaf:search_and_mv_output.py"),
),
},
)
def _dtbo_impl(ctx):
output = ctx.actions.declare_file("{}/dtbo.img".format(ctx.label.name))
inputs = []
inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies
inputs += ctx.files.srcs
command = ""
command += ctx.attr.kernel_build[_KernelEnvInfo].setup
command += """
# make dtbo
mkdtimg create {output} ${{MKDTIMG_FLAGS}} {srcs}
""".format(
output = output.path,
srcs = " ".join([f.path for f in ctx.files.srcs]),
)
_debug_print_scripts(ctx, command)
ctx.actions.run_shell(
inputs = inputs,
outputs = [output],
progress_message = "Building dtbo {}".format(ctx.label),
command = command,
)
return DefaultInfo(files = depset([output]))
_dtbo = rule(
implementation = _dtbo_impl,
doc = "Build dtbo.",
attrs = {
"kernel_build": attr.label(
mandatory = True,
providers = [_KernelEnvInfo, _KernelBuildInfo],
),
"srcs": attr.label_list(
allow_files = True,
),
"_debug_print_scripts": attr.label(
default = "//build/kleaf:debug_print_scripts",
),
},
)
def kernel_images(
name,
kernel_modules_install,
kernel_build = None,
build_initramfs = None,
build_vendor_dlkm = None,
build_boot_images = None,
build_system_dlkm = None,
build_dtbo = None,
dtbo_srcs = None,
mkbootimg = None,
deps = None,
boot_image_outs = None):
"""Build multiple kernel images.
Args:
name: name of this rule, e.g. `kernel_images`,
kernel_modules_install: A `kernel_modules_install` rule.
The main kernel build is inferred from the `kernel_build` attribute of the
specified `kernel_modules_install` rule. The main kernel build must contain
`System.map` in `outs` (which is included if you use `aarch64_outs` or
`x86_64_outs` from `common_kernels.bzl`).
kernel_build: A `kernel_build` rule. Must specify if `build_boot_images`.
mkbootimg: Path to the mkbootimg.py script which builds boot.img.
Keep in sync with `MKBOOTIMG_PATH`. Only used if `build_boot_images`. If `None`,
default to `//tools/mkbootimg:mkbootimg.py`.
deps: Additional dependencies to build images.
This must include the following:
- For `initramfs`:
- The file specified by `MODULES_LIST`
- The file specified by `MODULES_BLOCKLIST`, if `MODULES_BLOCKLIST` is set
- For `vendor_dlkm` image:
- The file specified by `VENDOR_DLKM_MODULES_LIST`
- The file specified by `VENDOR_DLKM_MODULES_BLOCKLIST`, if set
- The file specified by `VENDOR_DLKM_PROPS`, if set
- The file specified by `selinux_fc` in `VENDOR_DLKM_PROPS`, if set
boot_image_outs: A list of output files that will be installed to `DIST_DIR` when
`build_boot_images` is executed.
If `build_boot_images` is equal to `False`, the default is empty.
If `build_boot_images` is equal to `True`, the default list assumes the following:
- `BOOT_IMAGE_FILENAME` is not set (which takes default value `boot.img`), or is set to
`"boot.img"`
- `SKIP_VENDOR_BOOT` is not set, which builds `vendor_boot.img"
- `RAMDISK_EXT=lz4`. If the build configuration has a different value, replace
`ramdisk.lz4` with `ramdisk.{RAMDISK_EXT}` accordingly.
- `BOOT_IMAGE_HEADER_VERSION >= 4`, which creates `vendor-bootconfig.img` to contain
`VENDOR_BOOTCONFIG`
- The list contains `dtb.img`
build_initramfs: Whether to build initramfs. Keep in sync with `BUILD_INITRAMFS`.
build_system_dlkm: Whether to build system_dlkm.img an erofs image with GKI modules.
build_vendor_dlkm: Whether to build `vendor_dlkm` image. It must be set if
`VENDOR_DLKM_MODULES_LIST` is non-empty.
build_boot_images: Whether to build boot images. It must be set if either `BUILD_BOOT_IMG`
or `BUILD_VENDOR_BOOT_IMG` is set.
This depends on `initramfs` and `kernel_build`. Hence, if this is set to `True`,
`build_initramfs` is implicitly true, and `kernel_build` must be set.
build_dtbo: Whether to build dtbo image. Keep this in sync with `BUILD_DTBO_IMG`.
If `dtbo_srcs` is non-empty, `build_dtbo` is `True` by default. Otherwise it is `False`
by default.
dtbo_srcs: list of `*.dtbo` files used to package the `dtbo.img`. Keep this in sync
with `MKDTIMG_DTBOS`; see example below.
If `dtbo_srcs` is non-empty, `build_dtbo` must not be explicitly set to `False`.
Example:
```
kernel_build(
name = "tuna_kernel",
outs = [
"path/to/foo.dtbo",
"path/to/bar.dtbo",
],
)
kernel_images(
name = "tuna_images",
kernel_build = ":tuna_kernel",
dtbo_srcs = [
":tuna_kernel/path/to/foo.dtbo",
":tuna_kernel/path/to/bar.dtbo",
]
)
```
"""
all_rules = []
if build_boot_images:
if build_initramfs == None:
build_initramfs = True
if not build_initramfs:
fail("{}: Must set build_initramfs to True if build_boot_images".format(name))
if kernel_build == None:
fail("{}: Must set kernel_build if build_boot_images".format(name))
# Set default value for boot_image_outs according to build_boot_images
if boot_image_outs == None:
if not build_boot_images:
boot_image_outs = []
else:
boot_image_outs = [
"boot.img",
"dtb.img",
"ramdisk.lz4",
"vendor_boot.img",
"vendor-bootconfig.img",
]
if build_initramfs:
_initramfs(
name = "{}_initramfs".format(name),
kernel_modules_install = kernel_modules_install,
deps = deps,
vendor_boot_modules_load = "{}_initramfs/vendor_boot.modules.load".format(name),
)
all_rules.append(":{}_initramfs".format(name))
if build_system_dlkm:
_system_dlkm_image(
name = "{}_system_dlkm_image".format(name),
kernel_modules_install = kernel_modules_install,
deps = deps,
)
all_rules.append(":{}_system_dlkm_image".format(name))
if build_vendor_dlkm:
_vendor_dlkm_image(
name = "{}_vendor_dlkm_image".format(name),
kernel_modules_install = kernel_modules_install,
vendor_boot_modules_load = "{}_initramfs/vendor_boot.modules.load".format(name),
deps = deps,
)
all_rules.append(":{}_vendor_dlkm_image".format(name))
# Assume BUILD_BOOT_IMG or BUILD_VENDOR_BOOT_IMG
if build_boot_images:
_boot_images(
name = "{}_boot_images".format(name),
kernel_build = kernel_build,
outs = ["{}_boot_images/{}".format(name, out) for out in boot_image_outs],
deps = deps,
initramfs = ":{}_initramfs".format(name),
mkbootimg = mkbootimg,
)
all_rules.append(":{}_boot_images".format(name))
if build_dtbo == None:
build_dtbo = bool(dtbo_srcs)
if dtbo_srcs:
if not build_dtbo:
fail("{}: build_dtbo must be True if dtbo_srcs is non-empty.")
if build_dtbo:
_dtbo(
name = "{}_dtbo".format(name),
srcs = dtbo_srcs,
kernel_build = kernel_build,
)
all_rules.append(":{}_dtbo".format(name))
native.filegroup(
name = name,
srcs = all_rules,
)
def _kernel_filegroup_impl(ctx):
return [
DefaultInfo(files = depset(ctx.files.srcs)),
KernelFilesInfo(files = ctx.files.srcs),
]
kernel_filegroup = rule(
implementation = _kernel_filegroup_impl,
doc = """Specify a list of kernel prebuilts.
This is similar to [`filegroup`](https://docs.bazel.build/versions/main/be/general.html#filegroup)
that gives a convenient name to a collection of targets, which can be referenced from other rules.
In addition, this rule is conformed with [`KernelFilesInfo`](#kernelfilesinfo), so it can be used
in the `base_kernel` attribute of a [`kernel_build`](#kernel_build).
""",
attrs = {
"srcs": attr.label_list(
allow_files = True,
doc = "The list of labels that are members of this file group.",
),
},
)
def _kernel_compile_commands_impl(ctx):
interceptor_output = ctx.attr.kernel_build[_KernelBuildInfo].interceptor_output
compile_commands = ctx.actions.declare_file(ctx.attr.name + "/compile_commands.json")
inputs = [interceptor_output]
inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies
command = ctx.attr.kernel_build[_KernelEnvInfo].setup
command += """
# Generate compile_commands.json
interceptor_analysis -l {interceptor_output} -o {compile_commands} -t compdb_commands --relative
""".format(
interceptor_output = interceptor_output.path,
compile_commands = compile_commands.path,
)
ctx.actions.run_shell(
inputs = inputs,
outputs = [compile_commands],
command = command,
progress_message = "Building compile_commands.json {}".format(ctx.label),
)
return DefaultInfo(files = depset([compile_commands]))
kernel_compile_commands = rule(
implementation = _kernel_compile_commands_impl,
doc = """
Generate `compile_commands.json` from a `kernel_build`.
""",
attrs = {
"kernel_build": attr.label(
mandatory = True,
doc = "The `kernel_build` rule to extract from.",
providers = [_KernelEnvInfo, _KernelBuildInfo],
),
},
)
def _kernel_kythe_impl(ctx):
compile_commands = ctx.file.compile_commands
all_kzip = ctx.actions.declare_file(ctx.attr.name + "/all.kzip")
runextractor_error = ctx.actions.declare_file(ctx.attr.name + "/runextractor_error.log")
kzip_dir = all_kzip.dirname + "/intermediates"
extracted_kzip_dir = all_kzip.dirname + "/extracted"
transitive_inputs = [src.files for src in ctx.attr.kernel_build[_SrcsInfo].srcs]
inputs = [compile_commands]
inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies
command = ctx.attr.kernel_build[_KernelEnvInfo].setup
command += """
# Copy compile_commands.json to root
cp {compile_commands} ${{ROOT_DIR}}
# Prepare directories
mkdir -p {kzip_dir} {extracted_kzip_dir} ${{OUT_DIR}}
# Define env variables
export KYTHE_ROOT_DIRECTORY=${{ROOT_DIR}}
export KYTHE_OUTPUT_DIRECTORY={kzip_dir}
export KYTHE_CORPUS="{corpus}"
# Generate kzips
runextractor compdb -extractor $(which cxx_extractor) 2> {runextractor_error} || true
# Package it all into a single .kzip, ignoring duplicates.
for zip in $(find {kzip_dir} -name '*.kzip'); do
unzip -qn "${{zip}}" -d {extracted_kzip_dir}
done
soong_zip -C {extracted_kzip_dir} -D {extracted_kzip_dir} -o {all_kzip}
# Clean up directories
rm -rf {kzip_dir}
rm -rf {extracted_kzip_dir}
""".format(
compile_commands = compile_commands.path,
kzip_dir = kzip_dir,
extracted_kzip_dir = extracted_kzip_dir,
corpus = ctx.attr.corpus,
all_kzip = all_kzip.path,
runextractor_error = runextractor_error.path,
)
ctx.actions.run_shell(
inputs = depset(inputs, transitive = transitive_inputs),
outputs = [all_kzip, runextractor_error],
command = command,
progress_message = "Building Kythe source code index (kzip) {}".format(ctx.label),
)
return DefaultInfo(files = depset([
all_kzip,
runextractor_error,
]))
kernel_kythe = rule(
implementation = _kernel_kythe_impl,
doc = """
Extract Kythe source code index (kzip file) from a `kernel_build`.
""",
attrs = {
"kernel_build": attr.label(
mandatory = True,
doc = "The `kernel_build` target to extract from.",
providers = [_KernelEnvInfo, _KernelBuildInfo],
aspects = [_srcs_aspect],
),
"compile_commands": attr.label(
mandatory = True,
allow_single_file = True,
doc = "The `compile_commands.json`, or a `kernel_compile_commands` target.",
),
"corpus": attr.string(
default = "android.googlesource.com/kernel/superproject",
doc = "The value of `KYTHE_CORPUS`. See [kythe.io/examples](https://kythe.io/examples).",
),
},
)