blob: 5fcecacf847a58d79669185aff16c49fddae3fe4 [file] [log] [blame] [edit]
# 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.
"""An external kernel module.
Makefile and Kbuild files are required.
"""
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@kernel_toolchain_info//:dict.bzl", "VARS")
load("//build/kernel/kleaf:directory_with_structure.bzl", dws = "directory_with_structure")
load(
"//build/kernel/kleaf/artifact_tests:kernel_test.bzl",
"kernel_module_test",
)
load(":cache_dir.bzl", "cache_dir")
load(
":common_providers.bzl",
"CompileCommandsInfo",
"CompileCommandsSingleInfo",
"DdkConfigInfo",
"DdkHeadersInfo",
"DdkLibraryInfo",
"DdkSubmoduleInfo",
"GcovInfo",
"KernelBuildExtModuleInfo",
"KernelCmdsInfo",
"KernelEnvAttrInfo",
"KernelModuleInfo",
"KernelModuleSetupInfo",
"KernelSerializedEnvInfo",
"KernelUnstrippedModulesInfo",
"ModuleSymversFileInfo",
"ModuleSymversInfo",
)
load(":compile_commands_utils.bzl", "compile_commands_utils")
load(":ddk/ddk_config/ddk_config_info_subrule.bzl", "empty_ddk_config_info")
load(":debug.bzl", "debug")
load(":gcov_utils.bzl", "gcov_attrs", "get_grab_gcno_step")
load(":hermetic_toolchain.bzl", "hermetic_toolchain")
load(":kernel_build.bzl", "get_grab_cmd_step")
load(":stamp.bzl", "stamp")
load(":utils.bzl", "kernel_utils")
visibility("//build/kernel/kleaf/...")
def _module_output_cmd():
"""If the branch supports MO, return a command that uses it. Otherwise uses VPATH.
6.13 supports MO, which allows building external modules to be built
in a separate output directory. Between 6.10 and 6.13, VPATH needs to be
set to workaround the issue.
"""
if VARS.get("KLEAF_INTERNAL_EXT_MODULE_SEPARATE_BUILD_DIR") != "1":
return "VPATH=${ROOT_DIR}/${KERNEL_DIR}"
return "MO=${OUT_DIR}/${ext_mod_rel}"
def kernel_module(
name,
kernel_build,
outs = None,
srcs = None,
deps = None,
makefile = None,
generate_btf = 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.
deps: A list of other `kernel_module` or `ddk_module` dependencies.
Before building this target, `Modules.symvers` from the targets in
`deps` are restored, so this target can be built against
them.
It is an undefined behavior to put targets of other types to this list
(e.g. `ddk_headers`).
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_cp_output.py` for details.
makefile: `Makefile` for the module. By default, this is `Makefile` in the current package.
This file determines where `make modules` is executed.
This is useful when the Makefile is located in a different package or in a subdirectory.
generate_btf: Allows generation of BTF type information for the module.
If enabled, passes `vmlinux` image to module build, which is required
by BTF generator makefile scripts.
Disabled by default.
Requires `CONFIG_DEBUG_INFO_BTF` enabled in base kernel.
Requires rebuild of module if `vmlinux` changed, which may lead to an
increase of incremental build time.
BTF type information increases size of module binary.
**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 kwargs.get("kernel_module_deps"):
fail("{}: kernel_module_deps is deprecated. Use deps instead.".format(
native.package_relative_label(name),
))
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,
deps = deps,
outs = outs,
makefile = makefile,
generate_btf = generate_btf,
)
kwargs = _kernel_module_set_defaults(kwargs)
main_kwargs = dict(kwargs)
main_kwargs["name"] = name
main_kwargs["outs"] = ["{name}/{out}".format(name = name, out = out) for out in main_kwargs["outs"]]
_kernel_module(**main_kwargs)
kernel_module_test(
name = name + "_test",
modules = [name],
tags = kwargs.get("tags"),
)
def _check_module_symvers_restore_path(kernel_modules, this_label):
all_restore_paths = dict()
for kernel_module in kernel_modules:
for restore_path in kernel_module.module_symvers_info.restore_paths.to_list():
if restore_path not in all_restore_paths:
all_restore_paths[restore_path] = []
all_restore_paths[restore_path].append(str(kernel_module.label))
dups = dict()
for key, values in all_restore_paths.items():
if len(values) > 1:
dups[key] = values
if dups:
fail("""{this_label}: Conflicting dependencies. Dependencies from a package must either be a list of `ddk_module`s only, or a single `kernel_module`.
{conflicts}
""".format(
this_label = this_label,
conflicts = json.encode_indent(list(dups.values()), indent = " "),
))
def _get_implicit_outs(ctx):
"""Gets the list of implicit output files from makefile targets."""
implicit_outs = ctx.attr.internal_ddk_makefiles_dir[DdkSubmoduleInfo].outs.to_list()
implicit_outs_to_srcs = {}
for implicit_out in implicit_outs:
if implicit_out.out not in implicit_outs_to_srcs:
implicit_outs_to_srcs[implicit_out.out] = []
implicit_outs_to_srcs[implicit_out.out].append(implicit_out.src)
duplicated_implicit_outs = {}
for out, srcs in implicit_outs_to_srcs.items():
if len(srcs) > 1:
duplicated_implicit_outs[out] = srcs
if duplicated_implicit_outs:
fail("{}: Multiple submodules define the same output file: {}".format(
ctx.label,
json.encode_indent(duplicated_implicit_outs, indent = " "),
))
return list(implicit_outs_to_srcs.keys())
def _kernel_module_impl(ctx):
if not ctx.attr.internal_ddk_makefiles_dir:
message = """{}: kernel_module() is deprecated. Use ddk_module() instead.
See build/kernel/kleaf/docs/ddk/main.md for using the DDK.
""".format(ctx.label)
if ctx.attr._kernel_module_fail[BuildSettingInfo].value:
fail(message)
# buildifier: disable=print
print("\nWARNING: {}".format(message))
split_deps = kernel_utils.split_kernel_module_deps(ctx.attr.deps, ctx.label)
kernel_module_deps = split_deps.kernel_modules
kernel_module_deps = [kernel_utils.create_kernel_module_dep_info(target) for target in kernel_module_deps]
if ctx.attr.internal_ddk_makefiles_dir:
kernel_module_deps += ctx.attr.internal_ddk_makefiles_dir[DdkSubmoduleInfo].kernel_module_deps.to_list()
kernel_utils.check_kernel_build(
[target.kernel_module_info for target in kernel_module_deps],
ctx.attr.kernel_build.label,
ctx.label,
)
_check_module_symvers_restore_path(kernel_module_deps, ctx.label)
# Define where to build the external module (default to the package name)
if ctx.attr.makefile:
ext_mod_label = ctx.attr.makefile[0].label
else:
ext_mod_label = ctx.label
ext_mod = paths.join(ext_mod_label.workspace_root, ext_mod_label.package)
if not ext_mod:
fail("""{label}: kernel_module must not be defined at the top-level package of the main repository.
Move it to a sub-package, e.g. @{workspace_name}//{label_name}:{label_name}""".format(
label = ctx.label,
workspace_name = ctx.label.workspace_name,
label_name = ctx.label.name,
))
if ctx.files.makefile and ctx.file.internal_ddk_makefiles_dir:
fail("{label}: must not define `makefile` for `ddk_module`".format(ctx.label))
inputs = []
inputs += ctx.files.makefile
inputs += ctx.files.internal_ddk_makefiles_dir
module_srcs = [target.files for target in ctx.attr.srcs]
if not ctx.attr.internal_exclude_kernel_build_module_srcs:
module_srcs.append(ctx.attr.kernel_build[KernelBuildExtModuleInfo].module_hdrs)
module_srcs = depset(transitive = module_srcs)
transitive_inputs = [module_srcs]
for kernel_module_dep in kernel_module_deps:
transitive_inputs.append(kernel_module_dep.kernel_module_setup_info.inputs)
if ctx.attr.internal_ddk_makefiles_dir:
transitive_inputs.append(ctx.attr.internal_ddk_makefiles_dir[DdkSubmoduleInfo].srcs)
tools = [
ctx.executable._check_declared_output_list,
ctx.executable._search_and_cp_output,
ctx.executable._print_gcno_mapping,
]
transitive_tools = []
modules_staging_dws = None
kernel_uapi_headers_dws = None
if ctx.attr.internal_modules_install:
modules_staging_dws = dws.make(ctx, "{}/staging".format(ctx.attr.name))
kernel_uapi_headers_dws = dws.make(ctx, "{}/kernel-uapi-headers.tar.gz_staging".format(ctx.attr.name))
outdir = paths.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, ctx.label.name)
unstripped_dir = None
if ctx.attr.kernel_build[KernelBuildExtModuleInfo].collect_unstripped_modules or \
ctx.attr.internal_collect_unstripped_modules:
unstripped_dir = ctx.actions.declare_directory("{name}/unstripped".format(name = ctx.label.name))
output_files = [] + ctx.outputs.outs
if ctx.attr.internal_ddk_makefiles_dir:
for out in _get_implicit_outs(ctx):
output_files.append(ctx.actions.declare_file("{}/{}".format(ctx.label.name, out)))
# Original `outs` attribute of `kernel_module` macro.
original_outs = []
# apply basename to all of original_outs
original_outs_base = []
for out in output_files:
# 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"
# output_files = [File(".../foo/bar")]
# => short_name = "bar"
short_name = out.path[len(outdir) + 1:]
original_outs.append(short_name)
original_outs_base.append(out.basename)
all_module_names_file = ctx.actions.declare_file("{}/all_module_names.txt".format(ctx.label.name))
ctx.actions.write(
output = all_module_names_file,
content = "\n".join(original_outs) + "\n",
)
inputs.append(all_module_names_file)
command_outputs = []
module_symvers = None
check_no_remaining = None
if ctx.attr.internal_modules_install:
module_symvers = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, ctx.attr.internal_module_symvers_name))
check_no_remaining = ctx.actions.declare_file("{name}/{name}.check_no_remaining".format(name = ctx.attr.name))
command_outputs += [
module_symvers,
check_no_remaining,
]
command_outputs += dws.files(modules_staging_dws)
command_outputs += dws.files(kernel_uapi_headers_dws)
if unstripped_dir:
command_outputs.append(unstripped_dir)
cache_dir_step = cache_dir.get_step(
ctx = ctx,
common_config_tags = ctx.attr.kernel_build[KernelEnvAttrInfo].common_config_tags,
symlink_name = "module_{}".format(ctx.attr.name),
)
grab_cmd_step = get_grab_cmd_step(ctx, "${OUT_DIR}/${ext_mod_rel}")
grab_gcno_step = get_grab_gcno_step(ctx, "${COMMON_OUT_DIR}", is_kernel_build = False)
compile_commands_step = compile_commands_utils.get_step(ctx, "${OUT_DIR}/${ext_mod_rel}")
for step in (
cache_dir_step,
grab_cmd_step,
grab_gcno_step,
compile_commands_step,
):
inputs += step.inputs
command_outputs += step.outputs
tools += step.tools
# Determine the proper script to set up environment
if ctx.attr.internal_ddk_config:
setup_info = ctx.attr.internal_ddk_config[KernelSerializedEnvInfo]
elif ctx.attr.generate_btf:
# All outputs are required for BTF generation, including vmlinux image
setup_info = ctx.attr.kernel_build[KernelBuildExtModuleInfo].mod_full_env
else:
setup_info = ctx.attr.kernel_build[KernelBuildExtModuleInfo].mod_min_env
transitive_inputs.append(setup_info.inputs)
transitive_tools.append(setup_info.tools)
command = kernel_utils.setup_serialized_env_cmd(
serialized_env_info = setup_info,
restore_out_dir_cmd = cache_dir_step.cmd,
)
command += """
# For DDK modules with all sources generated, {ext_mod}/ may not even exist. Create it.
if [[ ! -d "{ext_mod}" ]]; then
mkdir -p "{ext_mod}"
fi
# Set variables
ext_mod_rel=$(realpath ${{ROOT_DIR}}/{ext_mod} --relative-to ${{KERNEL_DIR}})
""".format(
ext_mod = ext_mod,
)
if kernel_uapi_headers_dws:
command += """
# create dirs for modules
mkdir -p {kernel_uapi_headers_dir}/usr
""".format(
kernel_uapi_headers_dir = kernel_uapi_headers_dws.directory.path,
)
for kernel_module_dep in kernel_module_deps:
command += kernel_module_dep.kernel_module_setup_info.setup
grab_unstripped_cmd = ""
if unstripped_dir:
grab_unstripped_cmd = """
mkdir -p {unstripped_dir}
{search_and_cp_output} --srcdir ${{OUT_DIR}}/${{ext_mod_rel}} --dstdir {unstripped_dir} {outs}
""".format(
search_and_cp_output = ctx.executable._search_and_cp_output.path,
unstripped_dir = unstripped_dir.path,
# Use basenames to flatten the unstripped directory, even though outs may contain items with slash.
outs = " ".join(original_outs_base),
)
drop_modules_order_cmd = ""
if ctx.attr.internal_drop_modules_order and modules_staging_dws:
drop_modules_order_cmd = """
# Delete unnecessary modules.order.*, which will be re-generated by depmod.
rm -f {modules_staging_dir}/lib/modules/*/extra/{ext_mod}/modules.order.*
""".format(
ext_mod = ext_mod,
modules_staging_dir = modules_staging_dws.directory.path,
)
scmversion_ret = stamp.ext_mod_write_localversion(ctx, ext_mod)
inputs += scmversion_ret.deps
command += scmversion_ret.cmd
if ctx.file.internal_ddk_makefiles_dir:
command += """
# Restore Makefile and Kbuild
cp -r {ddk_makefiles}/* {ext_mod}/
# Replace env var in cflags/asflags files
# find -exec sed is error-prone due to readdir() issues, so save it to a
# variable first.
# No need to parse .ldflags because we don't write $(ROOT_DIR) to .ldflags;
# see gen_makefiles.py
(
files=$(find {ext_mod} -name '*.cflags_shipped' -o -name '*.asflags_shipped')
sed -i'' -e 's:$(ROOT_DIR):'"${{ROOT_DIR}}"':g' ${{files}}
)
""".format(
ddk_makefiles = ctx.file.internal_ddk_makefiles_dir.path,
ext_mod = ext_mod,
)
module_strip_flag = "INSTALL_MOD_STRIP="
if ctx.attr.kernel_build[KernelBuildExtModuleInfo].strip_modules:
module_strip_flag += "1"
modpost_warn = debug.modpost_warn(ctx)
command += modpost_warn.cmd
command_outputs += modpost_warn.outputs
# Keep a record of the modules.order generated by `make`.
grab_modules_order_cmd = ""
modules_order = None
if modules_staging_dws:
modules_order = ctx.actions.declare_file("{}/modules.order".format(ctx.attr.name))
command_outputs.append(modules_order)
grab_modules_order_cmd = """
# Backup modules.order files before optionally dropping them.
cp -L -p {modules_staging_dir}/lib/modules/*/extra/{ext_mod}/modules.order.* {modules_order}
""".format(
ext_mod = ext_mod,
modules_staging_dir = modules_staging_dws.directory.path,
modules_order = modules_order.path,
)
make_filter = ""
if not ctx.attr.generate_btf:
# Filter out warnings if there is no need for BTF generation
make_filter = " 2> >(sed '/Skipping BTF generation/d' >&2) "
command += """
# Actual kernel module build
make -C {ext_mod} ${{TOOL_ARGS}} M=${{ext_mod_rel}} O=${{OUT_DIR}} {mo_cmd} \\
KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}} \\
{extra_make_goals} \\
{make_filter} {make_redirect}
""".format(
ext_mod = ext_mod,
mo_cmd = _module_output_cmd(),
extra_make_goals = " ".join(ctx.attr.internal_extra_make_goals),
make_filter = make_filter,
make_redirect = modpost_warn.make_redirect,
)
# TODO(b/291955924): make the `make` invocations parallel
command += """
# Build compdb
"""
for goal in compile_commands_utils.additional_make_goals(ctx):
command += """
make -C {ext_mod} ${{TOOL_ARGS}} M=${{ext_mod_rel}} O=${{OUT_DIR}} {mo_cmd} KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}} {goal} {make_filter} {make_redirect}
""".format(
ext_mod = ext_mod,
mo_cmd = _module_output_cmd(),
goal = goal,
make_filter = make_filter,
make_redirect = modpost_warn.make_redirect,
)
command += """
{get_compdb_outputs}
# Grab *.gcno files
{grab_gcno_step_cmd}
""".format(
get_compdb_outputs = compile_commands_step.cmd,
grab_gcno_step_cmd = grab_gcno_step.cmd,
)
# For ddk_library etc., directly copy output files in the main action.
if not ctx.attr.internal_modules_install:
for short_name, out in zip(original_outs, output_files):
if out.extension == "cmd_shipped":
# Remove absolute paths in *.cmd files.
fmt = """
sed -e "s:${{ROOT_DIR}}/${{KERNEL_DIR}}/:"'$(srctree)/:g' \\
-e "s:${{ROOT_DIR}}/:"'$(srctree)/'"$(realpath ${{ROOT_DIR}} --relative-to ${{KERNEL_DIR}})/:g" \\
< ${{OUT_DIR}}/${{ext_mod_rel}}/{short_name_trimmed} > {out}
"""
else:
fmt = "\ncp -aL ${{OUT_DIR}}/${{ext_mod_rel}}/{short_name_trimmed} {out}\n"
command += fmt.format(
short_name_trimmed = short_name.removesuffix("_shipped"),
out = out.path,
)
command_outputs += output_files
# For kernel_module/ddk_module, install to staging directory, and use
# a separate action to copy output files.
if ctx.attr.internal_modules_install:
command += """
# Install into staging directory
make -C {ext_mod} ${{TOOL_ARGS}} DEPMOD=true M=${{ext_mod_rel}} \
O=${{OUT_DIR}} {mo_cmd} \
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
# Check if there are remaining *.ko files
remaining_ko_files=$({check_declared_output_list} \\
--declared $(cat {all_module_names_file}) \\
--actual $(cd {modules_staging_dir}/lib/modules/*/extra/{ext_mod} && find . -type f -name '*.ko' | sed 's:^[.]/::'))
if [[ ${{remaining_ko_files}} ]]; then
echo "ERROR: The following kernel modules are built but not copied. Add these lines to the outs attribute of {label}:" >&2
for ko in ${{remaining_ko_files}}; do
echo ' "'"${{ko}}"'",' >&2
done
echo "Alternatively, install buildozer and execute:" >&2
echo " $ buildozer 'add outs ${{remaining_ko_files}}' {label}" >&2
echo "See https://github.com/bazelbuild/buildtools/blob/master/buildozer/README.md for reference" >&2
exit 1
fi
touch {check_no_remaining}
# Grab unstripped modules
{grab_unstripped_cmd}
# Grab *.cmd
{grab_cmd_cmd}
# Move Module.symvers
rsync -aL ${{OUT_DIR}}/${{ext_mod_rel}}/Module.symvers {module_symvers}
# Grab and then drop modules.order
{grab_modules_order_cmd}
{drop_modules_order_cmd}
""".format(
label = ctx.label,
ext_mod = ext_mod,
mo_cmd = _module_output_cmd(),
generate_btf = int(ctx.attr.generate_btf),
module_symvers = module_symvers.path,
modules_staging_dir = modules_staging_dws.directory.path,
outdir = outdir,
kernel_uapi_headers_dir = kernel_uapi_headers_dws.directory.path,
module_strip_flag = module_strip_flag,
check_declared_output_list = ctx.executable._check_declared_output_list.path,
all_module_names_file = all_module_names_file.path,
grab_unstripped_cmd = grab_unstripped_cmd,
check_no_remaining = check_no_remaining.path,
grab_modules_order_cmd = grab_modules_order_cmd,
drop_modules_order_cmd = drop_modules_order_cmd,
grab_cmd_cmd = grab_cmd_step.cmd,
)
command += dws.record(modules_staging_dws)
command += dws.record(kernel_uapi_headers_dws)
# Unlike other rules (e.g. KernelBuild / ModulesPrepare), a DDK module must be executed
# in a sandbox so that restoring the makefiles does not mutate the source tree. However,
# we can't use linux-sandbox because --cache_dir is mounted as readonly. Hence, use
# the weaker form processwrapper-sandbox instead.
# See https://bazel.build/docs/sandboxing#sandboxing-strategies
strategy = ""
execution_requirements = None
if ctx.attr._config_is_local[BuildSettingInfo].value:
if ctx.file.internal_ddk_makefiles_dir:
strategy = "ProcessWrapperSandbox"
else:
execution_requirements = kernel_utils.local_exec_requirements(ctx)
debug.print_scripts(ctx, command)
ctx.actions.run_shell(
mnemonic = "KernelModule" + strategy,
inputs = depset(inputs, transitive = transitive_inputs),
tools = depset(tools, transitive = transitive_tools),
outputs = command_outputs,
command = command,
progress_message = "Building {}{} %{{label}}".format(
ctx.attr.internal_mnemonic,
ctx.attr.kernel_build[KernelEnvAttrInfo].progress_message_note,
),
execution_requirements = execution_requirements,
)
cp_cmd_outputs = []
if ctx.attr.internal_modules_install:
# For kernel_module/ddk_module, modules are installed to module_staging_dir.
# We need a separate action to copy them out. For ddk_library this is done in the main
# action already.
# Additional outputs because of the value in outs. This is
# [basename(out) for out in outs] - outs
additional_declared_outputs = []
for short_name, out in zip(original_outs, output_files):
if "/" in short_name:
additional_declared_outputs.append(ctx.actions.declare_file("{name}/{basename}".format(
name = ctx.attr.name,
basename = out.basename,
)))
original_outs_base.append(out.basename)
cp_cmd_outputs = output_files + additional_declared_outputs
if cp_cmd_outputs:
hermetic_tools = hermetic_toolchain.get(ctx)
command = hermetic_tools.setup + """
# Copy files into place
{search_and_cp_output} --srcdir {modules_staging_dir}/lib/modules/*/extra/{ext_mod}/ --dstdir {outdir} {outs}
""".format(
search_and_cp_output = ctx.executable._search_and_cp_output.path,
modules_staging_dir = modules_staging_dws.directory.path,
ext_mod = ext_mod,
outdir = outdir,
outs = " ".join(original_outs),
)
debug.print_scripts(ctx, command, what = "cp_outputs")
ctx.actions.run_shell(
mnemonic = "KernelModuleCpOutputs",
inputs = [
# We don't need structure_file here because we only care about files in the directory.
modules_staging_dws.directory,
],
tools = depset(
[ctx.executable._search_and_cp_output],
transitive = [hermetic_tools.deps],
),
outputs = cp_cmd_outputs,
command = command,
progress_message = "Copying outputs %{label}",
)
module_symvers_restore_path = None
setup = ""
setup_deps = []
if module_symvers:
setup += """
# Use a new shell to avoid polluting variables
(
# Set variables
# realpath 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=$(realpath ${{ROOT_DIR}}/{ext_mod} --relative-to ${{KERNEL_DIR}})
""".format(
ext_mod = ext_mod,
)
setup_deps.append(module_symvers)
module_symvers_restore_path = paths.join(ext_mod, ctx.attr.internal_module_symvers_name)
setup += """
# Restore Modules.symvers
mkdir -p $(dirname ${{COMMON_OUT_DIR}}/{module_symvers_restore_path})
rsync -aL {module_symvers} ${{COMMON_OUT_DIR}}/{module_symvers_restore_path}
""".format(
module_symvers = module_symvers.path,
internal_module_symvers_name = ctx.attr.internal_module_symvers_name,
module_symvers_restore_path = module_symvers_restore_path,
)
setup += """
# New shell ends
)
"""
if ctx.attr.internal_ddk_makefiles_dir:
ddk_headers_info = ctx.attr.internal_ddk_makefiles_dir[DdkHeadersInfo]
else:
ddk_headers_info = DdkHeadersInfo(include_infos = depset(), files = depset())
if ctx.attr.internal_ddk_config:
ddk_config_info = ctx.attr.internal_ddk_config[DdkConfigInfo]
else:
ddk_config_info = empty_ddk_config_info(
kernel_build_ddk_config_env =
ctx.attr.kernel_build[KernelBuildExtModuleInfo].ddk_config_env,
)
# 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.
# Also add check_no_remaining in the list of default outputs so that, when
# outs is empty, the KernelModule action is still executed, and so
# is check_declared_output_list.
default_info_files = list(output_files)
if check_no_remaining:
default_info_files.append(check_no_remaining)
if module_symvers:
default_info_files.append(module_symvers)
return [
# Sync list of infos with kernel_module_group.
DefaultInfo(
files = depset(default_info_files + grab_gcno_step.outputs),
# For kernel_module_test
runfiles = ctx.runfiles(files = output_files),
),
KernelModuleSetupInfo(
inputs = depset(setup_deps),
setup = setup,
),
KernelModuleInfo(
kernel_build_infos = kernel_utils.create_kernel_module_kernel_build_info(ctx.attr.kernel_build),
modules_staging_dws_depset = depset([modules_staging_dws]),
kernel_uapi_headers_dws_depset = depset([kernel_uapi_headers_dws]),
files = depset(output_files),
packages = depset([ext_mod]),
label = ctx.label,
modules_order = depset([modules_order]) if modules_order else depset(),
),
KernelUnstrippedModulesInfo(
directories = depset([unstripped_dir], order = "postorder"),
),
ModuleSymversInfo(
# path/to/package/target_name/Module.symvers -> path/to/package/Module.symvers;
# path/to/package/target_name/target_name_Module.symvers -> path/to/package/target_name_Module.symvers;
# This is similar to ${{OUT_DIR}}/${{ext_mod_rel}}
# It is needed to remove the `target_name` because we declare_file({name}/{internal_module_symvers_name}) above.
restore_paths = depset([module_symvers_restore_path]) if module_symvers_restore_path else depset(),
),
ddk_headers_info,
ddk_config_info,
GcovInfo(
gcno_mapping = grab_gcno_step.gcno_mapping,
gcno_dir = grab_gcno_step.gcno_dir,
),
KernelCmdsInfo(
srcs = module_srcs,
directories = depset([grab_cmd_step.cmd_dir]),
),
CompileCommandsInfo(
infos = depset([CompileCommandsSingleInfo(
compile_commands_with_vars = compile_commands_step.compile_commands_with_vars,
compile_commands_common_out_dir = compile_commands_step.compile_commands_common_out_dir,
)]),
),
ModuleSymversFileInfo(
module_symvers = depset([module_symvers]) if module_symvers else depset(),
),
DdkLibraryInfo(
files = depset(default_info_files if ctx.attr.internal_is_ddk_library else []),
),
]
def _kernel_module_additional_attrs():
return cache_dir.attrs() | {
attr_name: attr.label(default = label)
for attr_name, label in compile_commands_utils.config_settings_raw().items()
}
_kernel_module = rule(
implementation = _kernel_module_impl,
doc = """`make` out of tree.""",
attrs = {
"srcs": attr.label_list(
mandatory = True,
allow_files = True,
),
"makefile": attr.label_list(
allow_files = True,
doc = "Used internally. The makefile for this module.",
),
"internal_ddk_makefiles_dir": attr.label(
allow_single_file = True, # A single directory
doc = "A `makefiles` target that denotes a list of makefiles to restore",
),
"internal_module_symvers_name": attr.string(default = "Module.symvers"),
"internal_drop_modules_order": attr.bool(),
"internal_exclude_kernel_build_module_srcs": attr.bool(),
"internal_ddk_config": attr.label(providers = [
KernelSerializedEnvInfo,
DdkConfigInfo,
]),
"internal_collect_unstripped_modules": attr.bool(),
"internal_extra_make_goals": attr.string_list(
doc = "List of extra make goals to build",
),
"internal_is_ddk_library": attr.bool(
doc = "If true, outputs are placed in DdkLibraryInfo",
),
"internal_compdb": attr.string(
doc = """
If `respect_build_setting`, respects build_compile_commands setting.
If `skip`, always skip compdb step regardless of build_compile_commands setting.
""",
default = "respect_build_setting",
values = ["respect_build_setting", "skip"],
),
"internal_modules_install": attr.bool(
doc = """
If true, install modules, copy installed modules as outputs, and do other steps.
If false, copy outputs from $OUT_DIR directly, and skip `make modules_install` and other steps.
""",
default = True,
),
"internal_mnemonic": attr.string(
default = "external kernel module",
doc = "Descriptive string for the mnemonic",
),
"generate_btf": attr.bool(
default = False,
doc = "See [kernel_module.generate_btf](#kernel_module-generate_btf)",
),
"kernel_build": attr.label(
mandatory = True,
providers = [KernelBuildExtModuleInfo],
),
"deps": attr.label_list(),
# 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_cp_output": attr.label(
default = Label("//build/kernel/kleaf:search_and_cp_output"),
cfg = "exec",
executable = True,
doc = "Label referring to the script to process outputs",
),
"_check_declared_output_list": attr.label(
default = Label("//build/kernel/kleaf:check_declared_output_list"),
cfg = "exec",
executable = True,
),
"_preserve_cmd": attr.label(default = "//build/kernel/kleaf/impl:preserve_cmd"),
"_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"),
"_debug_modpost_warn": attr.label(default = "//build/kernel/kleaf:debug_modpost_warn"),
"_kernel_module_fail": attr.label(default = "//build/kernel/kleaf:incompatible_kernel_module_fail"),
} | _kernel_module_additional_attrs() | gcov_attrs(),
toolchains = [hermetic_toolchain.type],
subrules = [
empty_ddk_config_info,
stamp.ext_mod_get_localversion_file,
],
)
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 and kwargs.get("internal_ddk_makefiles_dir") == None:
kwargs["makefile"] = native.glob(["Makefile"])
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