blob: a5969c29204af17d0b644561073b4171493f9be0 [file] [log] [blame]
# 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.
"""Generates Makefile and Kbuild files for a DDK module."""
load("@bazel_skylib//lib:paths.bzl", "paths")
load("@bazel_skylib//lib:sets.bzl", "sets")
load("@kernel_toolchain_info//:dict.bzl", "VARS")
load(
":common_providers.bzl",
"DdkConditionalFilegroupInfo",
"DdkHeadersInfo",
"DdkIncludeInfo",
"DdkLibraryInfo",
"DdkSubmoduleInfo",
"ModuleSymversFileInfo",
"ModuleSymversInfo",
)
load(":constants.bzl", "DDK_MODULE_SRCS_ALLOWED_EXTENSIONS")
load(
":ddk/ddk_headers.bzl",
"DDK_INCLUDE_INFO_ORDER",
"ddk_headers_common_impl",
"get_ddk_transitive_include_infos",
"get_headers_depset",
)
load(":utils.bzl", "kernel_utils")
visibility("//build/kernel/kleaf/...")
_DEBUG_INFO_FOR_PROFILING_COPTS = [
"-fdebug-info-for-profiling",
"-mllvm",
"-enable-npm-pgo-inline-deferral=false",
"-mllvm",
"-improved-fs-discriminator=true",
]
_PKVM_EL2_OUT = "kvm_nvhe.o"
def _gather_prefixed_includes_common(ddk_include_info, info_attr_name):
"""Calculates -I flags.
If there are any generated headers in ddk_include_info, the list
of -I's are duplicated, with each token prepended with the root of the
generated header.
This is sometimes an overestimate, but the fs sandbox should ensure that only
the correct files are visible.
This can also be problematic if multiple generated headers have conflicting name / include
paths and they are under different transitions (therefore File.root is different). In that
unlikely edge case, it is advised that users use individual ddk_headers() to separate them.
"""
ret = []
generated_roots = sets.make()
# This depset.to_list() is evaluated at the execution phase.
for file in ddk_include_info.direct_files.to_list():
if file.is_source or file.extension != "h":
continue
sets.insert(generated_roots, file.root.path)
for include_root in [""] + sets.to_list(generated_roots):
for include_dir in getattr(ddk_include_info, info_attr_name):
ret.append(paths.normalize(
paths.join(include_root, ddk_include_info.prefix, include_dir),
))
return ret
def gather_prefixed_includes(ddk_include_info):
"""Returns a list of ddk_include_info.includes prefixed with ddk_include_info.prefix"""
return _gather_prefixed_includes_common(ddk_include_info, "includes")
def _gather_prefixed_linux_includes(ddk_include_info):
"""Returns a list of ddk_include_info.linux_includes prefixed with ddk_include_info.prefix"""
return _gather_prefixed_includes_common(ddk_include_info, "linux_includes")
def _handle_opts(ctx, file_name, opts, pre_opts_json = None):
"""Common function for handling copts, removed_copts, and asopts.
Args:
ctx: ctx
file_name: The declared JSON file name
opts: list of flags
pre_opts_json: Additional list prepended to the JSON list.
"""
# We treat $(location) differently because paths must be relative to the Makefile
# under {package}, e.g. for -include option.
expand_targets = []
expand_targets += ctx.attr.module_srcs
expand_targets += ctx.attr.module_hdrs
expand_targets += ctx.attr.module_deps
if ctx.attr.module_crate_root:
expand_targets.append(ctx.attr.module_crate_root)
json_content = list(pre_opts_json) if pre_opts_json else []
for copt in opts:
expanded = ctx.expand_location(copt, targets = expand_targets)
json_content.append({
"expanded": expanded,
"orig": copt,
})
out = ctx.actions.declare_file("{}/{}".format(ctx.attr.name, file_name))
ctx.actions.write(
output = out,
content = json.encode_indent(json_content, indent = " "),
)
return out
def _get_autofdo_copts(ctx):
"""Returns content in copt_file for AutoFDO."""
copt_content = []
if ctx.attr.module_debug_info_for_profiling:
copt_content += [{
"expanded": flag,
"orig": flag,
} for flag in _DEBUG_INFO_FOR_PROFILING_COPTS]
if ctx.file.module_autofdo_profile:
copt_content += [{
"expanded": "-fprofile-sample-accurate",
"orig": "-fprofile-sample-accurate",
}, {
"expanded": "-fprofile-sample-use={}".format(ctx.file.module_autofdo_profile.path),
"orig": "-fprofile-sample-use=$(execpath {})".format(ctx.attr.module_autofdo_profile.label),
}]
return copt_content
def _check_no_ddk_headers_in_srcs(ctx, module_label):
for target in ctx.attr.module_srcs:
if DdkHeadersInfo in target:
fail(("{}: {} is a ddk_headers or ddk_module but specified in srcs. " +
"Specify it in deps instead.").format(
module_label,
target.label,
))
def _check_empty_with_submodules(ctx, module_label, kernel_module_deps):
"""Checks that, if the outer target contains submodules, it should be empty.
That is, the top level `ddk_module` should not declare any
- inputs (including srcs, hdrs, and crate_root),
- outputs (including out, hdrs, includes), or
- copts (including includes and local_defines).
They should all be declared in individual `ddk_submodule`'s.
"""
if kernel_module_deps:
fail("{}: with submodules, deps on other kernel modules should be specified in individual ddk_submodule: {}".format(
module_label,
[dep.label for dep in kernel_module_deps],
))
if not ctx.attr.top_level_makefile:
fail("{}: with submodules, top_level_makefile must be set. " +
"(Did you specify another ddk_submodule in the deps?)")
for attr_name in (
"srcs",
"crate_root",
"out",
"hdrs",
"includes",
"local_defines",
"copts",
):
attr_val = getattr(ctx.attr, "module_" + attr_name)
if attr_val:
fail("{}: with submodules, {} should be specified in individual ddk_submodule: {}".format(
module_label,
attr_name,
attr_val,
))
def _check_non_empty_without_submodules(ctx, module_label):
"""Checks that, if the outer target does not contain submodules, it should not be empty.
That is, a `ddk_module` without submodules, or a `ddk_submodule`, should declare outputs.
"""
if not ctx.attr.module_out:
fail(("{}: out is not specified. Perhaps add\n" +
" out = \"{}.ko\"").format(
module_label,
module_label.name,
))
def _check_submodule_same_package(module_label, submodule_deps):
"""Checks that submodules are in the same package.
`gen_makefiles.py` assumes that `$(srctree)/$(src)` is the same for both submodules
and modules, then merge them. Until we resolve the paths properly so
`$(srctree)/$(src)` is no longer dependent on (b/251526635),
this assumption needs to be in place.
"""
# TODO(b/251526635): Remove this assumption.
bad = []
for submodule in submodule_deps:
if submodule.label.workspace_name != module_label.workspace_name or \
submodule.label.package != module_label.package:
bad.append(submodule.label)
if bad:
fail("{}: submodules must be in the same package: {}".format(module_label, bad))
def _handle_module_srcs(ctx, ddk_library_deps):
"""Parses module_srcs and module_crate_root, and ddk_library module_deps.
For each item in ddk_module.srcs and ddk_module.crate_root:
- If source file (.c .S .o_shipped .o.cmd_shipped, and .rs):
- If generated file, add it to srcs_json gen. Put it in gen_srcs_depset.
This makes it available to gen_makefiles.py so it can be copied into output_makefiles
directory.
- If not generated file, add it to srcs_json files
- Regardless of whether it is generated or not, set config/value if it is in
conditional_srcs
- If header (.h):
- If not generated file, add it to srcs_json files
- If generated file, do not add it to srcs_json. Ignore the file.
Returns:
struct of
- srcs_json: a file containing the JSON content about sources
- gen_srcs_depset: depset of generated, non .h files
- src_matrix: list of list of files. The sum of values is the list of files from:
- srcs
- ddk_library deps
- crate_root
"""
src_matrix = []
srcs_json_list = []
gen_srcs_depsets = []
crate_root_targets = [ctx.attr.module_crate_root] if ctx.attr.module_crate_root else []
for targets, info, dep_type in (
(ctx.attr.module_srcs, DefaultInfo, "srcs"),
(crate_root_targets, DefaultInfo, "crate_root"),
(ddk_library_deps, DdkLibraryInfo, "library"),
):
for target in targets:
# TODO(b/353811700): avoid depset expansion
target_files = target[info].files.to_list()
src_matrix.append(target_files)
ret = _handle_target_files_as_srcs(target, target_files, dep_type)
srcs_json_list.append(ret.srcs_json_dict)
gen_srcs_depsets.append(ret.gen_srcs_depset)
srcs_json = ctx.actions.declare_file("{}/srcs.json".format(ctx.attr.name))
ctx.actions.write(
output = srcs_json,
content = json.encode_indent(srcs_json_list, indent = " "),
)
return struct(
srcs_json = srcs_json,
gen_srcs_depset = depset(transitive = gen_srcs_depsets),
src_matrix = src_matrix,
)
def _handle_target_files_as_srcs(target, target_files, dep_type):
"""Processes `target` as a source.
Args:
target: the dependency
target_files: the list of files from the dependency
dep_type: Type of the dependency:
* srcs
* crate_root
* library, for deps with DdkLibraryInfo
Returns:
A struct with these fields:
* srcs_json_dict: Dictionary of metadata for the list of files for this target,
provided to gen_makefiles.py
* gen_srcs_depset: depset of generated files
"""
srcs_json_dict = {}
source_files = []
generated_sources = []
for file in target_files:
# Headers in srcs are not passed to gen_makefiles.py.
# Generated headers handled by _gather_prefixed_includes_common.
# They are not appended to the source list, but additional -I are added.
# Non-generated headers don't need any special handling.
if file.extension == "h" and dep_type == "srcs":
continue
# For the remaining files in srcs / crate_root / ddk_library deps etc.,
# pass them to gen_makefiles.py to generate proper rules. Generated
# files needs special handling so put them in a different list.
if file.is_source:
source_files.append(file)
else:
generated_sources.append(file)
if source_files:
srcs_json_dict["files"] = [file.path for file in source_files]
if generated_sources:
srcs_json_dict["gen"] = {file.short_path: file.path for file in generated_sources}
if DdkConditionalFilegroupInfo in target:
srcs_json_dict["config"] = target[DdkConditionalFilegroupInfo].config
srcs_json_dict["value"] = target[DdkConditionalFilegroupInfo].value
if dep_type != "srcs":
srcs_json_dict["type"] = dep_type
return struct(
srcs_json_dict = srcs_json_dict,
gen_srcs_depset = depset(generated_sources),
)
def _get_ddk_library_out_list_impl(subrule_ctx, src_rel_pkg):
"""Returns a list of outputs for ddk_library.
Args:
subrule_ctx: subrule_ctx
src_rel_pkg: path to the source file, relative to the package.
The suffix doesn't matter. <stem>.o_shipped and .<stem>.o.cmd_shipped is returned.
"""
object = paths.replace_extension(src_rel_pkg, ".o_shipped")
cmd_file_basename = "." + paths.replace_extension(paths.basename(src_rel_pkg), ".o.cmd_shipped")
cmd_file = paths.join(paths.dirname(src_rel_pkg), cmd_file_basename)
return [
struct(out = object, src = subrule_ctx.label),
struct(out = cmd_file, src = subrule_ctx.label),
]
_get_ddk_library_out_list = subrule(implementation = _get_ddk_library_out_list_impl)
def _get_outs_list_impl(
subrule_ctx,
*,
module_pkvm_el2,
target_type,
src_matrix,
module_out,
submodule_deps):
"""Figures out the list of outputs from the action.
Args:
subrule_ctx: subrule_ctx
module_pkvm_el2: building pKVM EL2 ddk_library
target_type: type of outer target
src_matrix: list of list of sources
module_out: out of outer target
submodule_deps: list of ddk_submodule deps.
"""
if module_pkvm_el2:
return depset(_get_ddk_library_out_list(_PKVM_EL2_OUT))
if target_type == "library":
my_pkg_path = paths.join(subrule_ctx.label.workspace_root, subrule_ctx.label.package)
outs_depset_direct = []
for srcs_list in src_matrix:
for src in srcs_list:
# All sources must be below this package.
# Use short_path here because we don't care about bin_dir for generated sources.
# path/to/foo.c -> [path/to/foo.o_shipped, path/to/.foo.o.cmd_shipped]
src_rel_pkg = paths.relativize(src.short_path, my_pkg_path)
outs_depset_direct += _get_ddk_library_out_list(src_rel_pkg)
return depset(outs_depset_direct)
if target_type in ("module", "submodule"):
outs_depset_direct = []
if module_out:
outs_depset_direct.append(struct(out = module_out, src = subrule_ctx.label))
outs_depset_transitive = [dep[DdkSubmoduleInfo].outs for dep in submodule_deps]
return depset(outs_depset_direct, transitive = outs_depset_transitive)
fail("Unrecognized type {}".format(target_type))
_get_outs_list = subrule(
implementation = _get_outs_list_impl,
subrules = [
_get_ddk_library_out_list,
],
)
def _makefiles_impl(ctx):
module_label = Label(str(ctx.label).removesuffix("_makefiles"))
_check_no_ddk_headers_in_srcs(ctx, module_label)
output_makefiles = ctx.actions.declare_directory("{}/makefiles".format(ctx.attr.name))
split_deps = kernel_utils.split_kernel_module_deps(ctx.attr.module_deps, module_label)
kernel_module_deps = split_deps.kernel_modules
submodule_deps = split_deps.submodules
hdr_deps = split_deps.hdrs
module_symvers_deps = split_deps.module_symvers_deps
ddk_library_deps = split_deps.ddk_library_deps
if submodule_deps:
_check_empty_with_submodules(ctx, module_label, kernel_module_deps)
else:
_check_non_empty_without_submodules(ctx, module_label)
_check_submodule_same_package(module_label, submodule_deps)
# Depset of files from module_srcs
srcs_files = depset(transitive = [target.files for target in ctx.attr.module_srcs])
crate_root_files = ctx.attr.module_crate_root.files if ctx.attr.module_crate_root else depset()
direct_include_infos = [DdkIncludeInfo(
prefix = paths.join(module_label.workspace_root, module_label.package),
# Applies to headers of this target only but not headers/include_dirs
# inherited from dependencies.
direct_files = depset(transitive = [srcs_files, crate_root_files]),
includes = ctx.attr.module_includes,
linux_includes = ctx.attr.module_linux_includes,
)]
# Because of left-to-right ordering (DDK_INCLUDE_INFO_ORDER), kernel_build with
# lowest priority is placed at the end of the list.
transitive_include_info_targets = ctx.attr.module_deps + ctx.attr.module_hdrs
if ctx.attr.kernel_build:
transitive_include_info_targets.append(ctx.attr.kernel_build)
include_infos = depset(
direct_include_infos,
transitive = get_ddk_transitive_include_infos(transitive_include_info_targets),
order = DDK_INCLUDE_INFO_ORDER,
)
submodule_linux_includes = {}
for dep in submodule_deps:
out = dep[DdkSubmoduleInfo].out
if not out:
continue
dirname = paths.dirname(out)
submodule_linux_includes.setdefault(dirname, []).append(dep[DdkSubmoduleInfo].linux_includes_include_infos)
module_symvers_depset = depset(transitive = [
target[ModuleSymversInfo].restore_paths
for target in module_symvers_deps
])
module_srcs_ret = _handle_module_srcs(ctx, ddk_library_deps)
args = ctx.actions.args()
# Though flag_per_line is designed for the absl flags library and
# gen_makefiles.py uses absl flags library, this outputs the following
# in the output params file:
# --foo=value1 value2
# ... which is interpreted as --foo="value1 value2" instead of storing
# individual values. Hence, use multiline so the output becomes:
# --foo
# value1
# value2
args.set_param_file_format("multiline")
args.use_param_file("--flagfile=%s")
args.add("--kernel-module-srcs-json", module_srcs_ret.srcs_json)
if ctx.attr.module_out:
args.add("--kernel-module-out", ctx.attr.module_out)
args.add("--output-makefiles", output_makefiles.path)
args.add("--package", paths.join(ctx.label.workspace_root, ctx.label.package))
if ctx.attr.top_level_makefile:
args.add("--produce-top-level-makefile")
if ctx.attr.kbuild_has_linux_include:
args.add("--kbuild-has-linux-include")
for dirname, linux_includes_include_infos_list in submodule_linux_includes.items():
args.add("--submodule-linux-include-dirs", dirname)
args.add_all(
depset(transitive = linux_includes_include_infos_list),
map_each = _gather_prefixed_linux_includes,
uniquify = True,
)
args.add_all(
"--linux-include-dirs",
include_infos,
map_each = _gather_prefixed_linux_includes,
uniquify = True,
)
args.add_all(
"--include-dirs",
include_infos,
map_each = gather_prefixed_includes,
uniquify = True,
)
if ctx.attr.top_level_makefile:
args.add_all("--module-symvers-list", module_symvers_depset)
args.add_all("--local-defines", ctx.attr.module_local_defines)
copts_file = _handle_opts(
ctx = ctx,
file_name = "copts.json",
opts = ctx.attr.module_copts,
pre_opts_json = _get_autofdo_copts(ctx),
)
args.add("--copts-file", copts_file)
removed_copts_file = _handle_opts(ctx, "removed_copts.json", ctx.attr.module_removed_copts)
args.add("--removed-copts-file", removed_copts_file)
asopts_file = _handle_opts(ctx, "asopts.json", ctx.attr.module_asopts)
args.add("--asopts-file", asopts_file)
linkopts_file = _handle_opts(ctx, "linkopts.json", ctx.attr.module_linkopts)
args.add("--linkopts-file", linkopts_file)
submodule_makefiles = depset(transitive = [dep.files for dep in submodule_deps])
args.add_all("--submodule-makefiles", submodule_makefiles, expand_directories = False)
if ctx.attr.internal_target_fail_message:
args.add("--internal-target-fail-message", ctx.attr.internal_target_fail_message)
if ctx.attr.target_type == "library":
args.add("--is-library")
if ctx.attr.module_pkvm_el2:
args.add("--pkvm-el2-out", _PKVM_EL2_OUT)
if VARS.get("KLEAF_INTERNAL_COPY_RULE_HACK") == "1":
args.add("--copy-rule-hack")
ctx.actions.run(
mnemonic = "DdkMakefiles",
inputs = depset([
copts_file,
removed_copts_file,
asopts_file,
linkopts_file,
module_srcs_ret.srcs_json,
], transitive = [submodule_makefiles, module_srcs_ret.gen_srcs_depset]),
outputs = [output_makefiles],
executable = ctx.executable._gen_makefile,
arguments = [args],
progress_message = "Generating Makefile %{label}",
)
outs_depset = _get_outs_list(
module_pkvm_el2 = ctx.attr.module_pkvm_el2,
target_type = ctx.attr.target_type,
src_matrix = module_srcs_ret.src_matrix,
module_out = ctx.attr.module_out,
submodule_deps = submodule_deps,
)
# All files needed to build this .ko file
srcs_depset_transitive = [srcs_files, crate_root_files]
srcs_depset_transitive += [dep[DdkSubmoduleInfo].srcs for dep in submodule_deps]
# Add targets with DdkHeadersInfo in deps
srcs_depset_transitive += [hdr[DdkHeadersInfo].files for hdr in hdr_deps]
# Add all files from hdrs (use DdkHeadersInfo if available,
# otherwise use default files).
srcs_depset_transitive.append(get_headers_depset(ctx.attr.module_hdrs))
# Add ddk_module_headers files from kernel_build
if ctx.attr.kernel_build:
srcs_depset_transitive.append(ctx.attr.kernel_build[DdkHeadersInfo].files)
if ctx.attr.module_autofdo_profile:
srcs_depset_transitive.append(ctx.attr.module_autofdo_profile.files)
ddk_headers_info = ddk_headers_common_impl(
ctx.label,
# hdrs of the ddk_module + hdrs of submodules.
# Don't export kernel_build[DdkHeadersInfo] to avoid raising its priority;
# dependent makefiles() target will put kernel_build[DdkHeadersInfo] at the end.
ctx.attr.module_hdrs + submodule_deps,
# includes of the ddk_module. The includes of submodules are handled by adding
# them to hdrs.
ctx.attr.module_includes,
# linux_includes are not exported to targets depended on this module.
[],
)
return [
DefaultInfo(files = depset([output_makefiles])),
DdkSubmoduleInfo(
outs = outs_depset,
out = None if ctx.attr.target_type == "library" else ctx.attr.module_out,
srcs = depset(transitive = srcs_depset_transitive),
kernel_module_deps = depset(
[kernel_utils.create_kernel_module_dep_info(target) for target in kernel_module_deps],
transitive = [dep[DdkSubmoduleInfo].kernel_module_deps for dep in submodule_deps],
),
linux_includes_include_infos = include_infos,
),
ModuleSymversInfo(
restore_paths = module_symvers_depset,
),
ModuleSymversFileInfo(
module_symvers = depset(transitive = [
target[ModuleSymversFileInfo].module_symvers
for target in module_symvers_deps
]),
),
ddk_headers_info,
]
makefiles = rule(
implementation = _makefiles_impl,
doc = "Generate `Makefile` and `Kbuild` files for `ddk_module`",
attrs = {
"kernel_build": attr.label(
providers = [DdkHeadersInfo],
# This is not set on ddk_submodule, but only on the overarching ddk_module.
mandatory = False,
),
# module_X is the X attribute of the ddk_module. Prefixed with `module_`
# because they aren't real srcs / hdrs / deps to the makefiles rule.
"module_srcs": attr.label_list(allow_files = DDK_MODULE_SRCS_ALLOWED_EXTENSIONS),
# allow_files = True because https://github.com/bazelbuild/bazel/issues/7516
"module_crate_root": attr.label(allow_single_file = True),
"module_hdrs": attr.label_list(allow_files = True),
"module_includes": attr.string_list(),
"module_linux_includes": attr.string_list(),
"module_deps": attr.label_list(),
"module_out": attr.string(),
"module_local_defines": attr.string_list(),
"module_copts": attr.string_list(),
"module_removed_copts": attr.string_list(),
"module_asopts": attr.string_list(),
"module_linkopts": attr.string_list(),
"module_autofdo_profile": attr.label(allow_single_file = True),
"module_debug_info_for_profiling": attr.bool(),
"module_pkvm_el2": attr.bool(),
"top_level_makefile": attr.bool(),
"kbuild_has_linux_include": attr.bool(
doc = "Whether to add LINUXINCLUDE to Kbuild",
default = True,
),
"internal_target_fail_message": attr.string(
doc = "For testing only. Assert that this target to fail to build with the given message.",
),
"target_type": attr.string(
default = "module",
values = [
"module",
"submodule",
"library",
],
),
"_gen_makefile": attr.label(
default = "//build/kernel/kleaf/impl:ddk/gen_makefiles",
executable = True,
cfg = "exec",
),
},
subrules = [
_get_outs_list,
],
)