blob: 406487d315907a199aed68549cfc9e94836767a8 [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.
"""
A rule that runs depmod in the module installation directory.
"""
load("@bazel_skylib//lib:paths.bzl", "paths")
load("//build/kernel/kleaf:directory_with_structure.bzl", dws = "directory_with_structure")
load(
":common_providers.bzl",
"GcovInfo",
"KernelBuildExtModuleInfo",
"KernelBuildInfo",
"KernelCmdsInfo",
"KernelImagesInfo",
"KernelModuleInfo",
"KernelSerializedEnvInfo",
)
load(":debug.bzl", "debug")
load(":gcov_utils.bzl", "gcov_attrs", "get_merge_gcno_step")
load(
":utils.bzl",
"kernel_utils",
"utils",
)
visibility("//build/kernel/kleaf/...")
# To avoid being abused, output is limited to modules.* files
_OUT_ALLOWLIST = [
"modules.dep",
"modules.alias",
"modules.builtin",
"modules.symbols",
"modules.softdep",
]
def _kernel_modules_install_impl(ctx):
kernel_build_infos = None
if ctx.attr.kernel_build:
kernel_build_infos = kernel_utils.create_kernel_module_kernel_build_info(ctx.attr.kernel_build)
elif ctx.attr.kernel_modules:
kernel_build_infos = ctx.attr.kernel_modules[0][KernelModuleInfo].kernel_build_infos
if not kernel_build_infos:
fail("No `kernel_build` or `kernel_modules` provided.")
kernel_utils.check_kernel_build(
[target[KernelModuleInfo] for target in ctx.attr.kernel_modules],
kernel_build_infos.label,
ctx.label,
)
# A list of declared files for outputs of kernel_module rules
external_modules = []
# A list of additional files other than kernel modules.
outs = []
# TODO(b/256688440): Avoid depset[directory_with_structure] to_list
modules_staging_dws_depset = depset(transitive = [
kernel_module[KernelModuleInfo].modules_staging_dws_depset
for kernel_module in ctx.attr.kernel_modules
])
modules_staging_dws_list = modules_staging_dws_depset.to_list()
inputs = []
inputs.append(
kernel_build_infos.ext_module_info.modules_staging_archive,
)
for input_modules_staging_dws in modules_staging_dws_list:
inputs += dws.files(input_modules_staging_dws)
module_files = depset(transitive = [
kernel_module[KernelModuleInfo].files
for kernel_module in ctx.attr.kernel_modules
]).to_list()
for module_file in module_files:
declared_file = ctx.actions.declare_file("{}/{}".format(ctx.label.name, module_file.basename))
external_modules.append(declared_file)
for out in ctx.attr.outs:
if out not in _OUT_ALLOWLIST:
fail(
"""{}: {} is not allowed in outs.
Please refer to the list of allowed files {}""".format(
ctx.label,
out,
_OUT_ALLOWLIST,
),
)
out_file = ctx.actions.declare_file("{}/{}".format(ctx.label.name, out))
outs.append(out_file)
transitive_inputs = [
kernel_build_infos.ext_module_info.modinst_env.inputs,
]
tools = [
ctx.executable._check_duplicated_files_in_archives,
ctx.executable._search_and_cp_output,
]
transitive_tools = [kernel_build_infos.ext_module_info.modinst_env.tools]
modules_staging_dws = dws.make(ctx, "{}/staging".format(ctx.label.name))
command = kernel_utils.setup_serialized_env_cmd(
serialized_env_info = kernel_build_infos.ext_module_info.modinst_env,
restore_out_dir_cmd = utils.get_check_sandbox_cmd(),
)
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}
""".format(
modules_staging_dir = modules_staging_dws.directory.path,
kernel_build_modules_staging_archive =
kernel_build_infos.ext_module_info.modules_staging_archive.path,
)
for input_modules_staging_dws in modules_staging_dws_list:
# Allow directories to be written because we are merging multiple directories into one.
# However, don't allow files to be written because we don't expect modules to produce
# conflicting files. check_duplicated_files_in_archives further enforces this.
command += dws.restore(
input_modules_staging_dws,
dst = modules_staging_dws.directory.path,
options = "-aL --chmod=D+w",
)
# 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 "ERROR: No ${{OUT_DIR}}/include/config/kernel.release" >&2
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
depmod -ae -F "${{mixed_build_prefix}}System.map" -b "${{real_modules_staging_dir}}" ${{kernelrelease}}
)
# Remove symlinks that are dead outside of the sandbox
(
find "{modules_staging_dir}/lib/modules/" -maxdepth 2 -mindepth 2 \\( -name source -o -name build \\) -type l -delete > /dev/null
)
""".format(
modules_staging_archives = " ".join(
[kernel_build_infos.ext_module_info.modules_staging_archive.path] +
[input_modules_staging_dws.directory.path for input_modules_staging_dws in modules_staging_dws_list],
),
modules_staging_dir = modules_staging_dws.directory.path,
check_duplicated_files_in_archives = ctx.executable._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_cp_output} --srcdir {modules_staging_dir}/lib/modules/*/extra --dstdir {outdir} {filenames}
""".format(
modules_staging_dir = modules_staging_dws.directory.path,
outdir = external_module_dir,
filenames = " ".join([declared_file.basename for declared_file in external_modules]),
search_and_cp_output = ctx.executable._search_and_cp_output.path,
)
command += """
# Move additional files to declared output location
for out in {outs}; do
cp -pL {modules_staging_dir}/lib/modules/*/${{out}} {outdir}
done
""".format(
modules_staging_dir = modules_staging_dws.directory.path,
outdir = paths.join(utils.package_bin_dir(ctx), ctx.attr.name),
outs = " ".join(ctx.attr.outs),
)
command += dws.record(modules_staging_dws)
# --gcov related step
merge_gcno_step = get_merge_gcno_step(ctx, ctx.attr.kernel_modules)
inputs += merge_gcno_step.inputs
outs += merge_gcno_step.outputs
tools += merge_gcno_step.tools
command += merge_gcno_step.cmd
debug.print_scripts(ctx, command)
ctx.actions.run_shell(
mnemonic = "KernelModulesInstall",
inputs = depset(inputs, transitive = transitive_inputs),
tools = depset(tools, transitive = transitive_tools),
outputs = external_modules + dws.files(modules_staging_dws) + outs,
command = command,
progress_message = "Running depmod %{label}",
)
# Only analyze headers on external modules.
# To analyze headers on in-tree modules, just run analyze_inputs on the kernel_build directly.
cmds_info_targets = ctx.attr.kernel_modules
cmds_info_srcs = [target[KernelCmdsInfo].srcs for target in cmds_info_targets]
cmds_info_directories = [target[KernelCmdsInfo].directories for target in cmds_info_targets]
cmds_info = KernelCmdsInfo(
srcs = depset(transitive = cmds_info_srcs),
directories = depset(transitive = cmds_info_directories),
)
return [
DefaultInfo(files = depset(external_modules + outs)),
KernelModuleInfo(
kernel_build_infos = kernel_build_infos,
modules_staging_dws_depset = depset([modules_staging_dws]),
packages = depset(transitive = [
target[KernelModuleInfo].packages
for target in ctx.attr.kernel_modules
]),
label = ctx.label,
modules_order = depset(transitive = [
target[KernelModuleInfo].modules_order
for target in ctx.attr.kernel_modules
], order = "postorder"),
),
GcovInfo(
gcno_mapping = merge_gcno_step.gcno_mapping,
gcno_dir = merge_gcno_step.gcno_dir,
),
cmds_info,
]
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 `srcs` attribute of a `pkg_files` rule that is
included in a `pkg_install` 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_modules = [ # kernel_module rules
"//path/to/nfc:nfc_module",
],
)
kernel_build(
name = "foo",
outs = ["vmlinux"],
module_outs = ["core_module.ko"],
)
pkg_files(
name = "foo_dist_files",
srcs = [
":foo", # Includes core_module.ko and vmlinux
":foo_modules_install", # Includes nfc_module
],
)
pkg_install(
name = "foo_dist",
srcs = [":foo_dist_files"],
)
```
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 = [KernelModuleInfo, GcovInfo],
doc = "A list of labels referring to `kernel_module`s to install.",
),
"kernel_build": attr.label(
providers = [
KernelBuildExtModuleInfo,
# Needed by KernelModuleInfo.kernel_build
# TODO(b/247622808): Should put the info in KernelModuleInfo directly.
KernelSerializedEnvInfo,
KernelBuildInfo,
KernelImagesInfo,
],
doc = "Label referring to the `kernel_build` module. Otherwise, it" +
" is inferred from `kernel_modules`.",
),
"_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"),
"_check_duplicated_files_in_archives": attr.label(
default = Label("//build/kernel/kleaf:check_duplicated_files_in_archives"),
doc = "Label referring to the script to process outputs",
cfg = "exec",
executable = True,
),
"_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",
),
"outs": attr.string_list(
doc = """ A list of additional outputs from `make modules_install`.
Since external modules are returned by default,
it can be used to obtain modules.* related files (results of depmod).
Only files with allowed names can be added to outs. (`_OUT_ALLOWLIST`)
```
_OUT_ALLOWLIST = {}
```
Example:
```
kernel_modules_install(
name = "foo_modules_install",
kernel_modules = [":foo_module_list"],
outs = [
"modules.dep",
"modules.alias",
],
)
```
""".format(repr(_OUT_ALLOWLIST)),
),
} | gcov_attrs(),
)