blob: 6f835169f7abb13a9ecf3c435f13f851c22193c4 [file] [log] [blame] [edit]
# Part of the Crubit project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""`cc_bindings_from_rust` rule."""
load(
"@rules_rust//rust:rust_common.bzl",
"BuildInfo",
"CrateInfo",
)
# buildifier: disable=bzl-visibility
load(
"@rules_rust//rust/private:providers.bzl",
"DepInfo",
"DepVariantInfo",
)
# buildifier: disable=bzl-visibility
load(
"@rules_rust//rust/private:rustc.bzl",
"ExtraRustcFlagsInfo",
"collect_deps",
"collect_inputs",
"construct_arguments",
)
# buildifier: disable=bzl-visibility
load(
"@rules_rust//rust/private:utils.bzl",
"find_toolchain",
)
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load(
"//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_cli_flag_aspect_hint.bzl",
"collect_cc_bindings_from_rust_cli_flags",
)
load(
"//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_library_config_aspect_hint.bzl",
"crate_name_to_library_config",
)
load(
"//cc_bindings_from_rs/bazel_support:providers.bzl",
"CcBindingsFromRustInfo",
"GeneratedBindingsInfo",
)
load(
"//features:crubit_feature_hint.bzl",
"find_crubit_features",
)
load(
"//rs_bindings_from_cc/bazel_support:compile_rust.bzl",
"compile_rust",
)
load(
"//rs_bindings_from_cc/bazel_support:providers.bzl",
"RustBindingsFromCcInfo",
)
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
# Targets which do not receive C++ bindings at all.
targets_to_remove = [
]
def _get_dep_bindings_infos(ctx):
"""Returns `CcBindingsFromRustInfo`s of direct, non-transitive dependencies.
Only information about direct, non-transitive dependencies is needed,
because bindings for the public APIs may need to refer to types from
such dependencies (e.g. `fn foo(param: TypeFromDirectDependency)`),
but they cannot refer to types from transitive dependencies.
Args:
ctx: The rule context.
Returns:
A list of `CcBindingsFromRustInfo`s of all the direct, non-transitive Rust
dependencies (dependencies of the Rust crate being used as input for
`cc_bindings_from_rs`).
"""
return [
dep[CcBindingsFromRustInfo]
for dep in ctx.rule.attr.deps + getattr(ctx.rule.attr, "cc_deps", [])
if CcBindingsFromRustInfo in dep
]
def _generate_bindings(ctx, target, basename, inputs, args, rustc_env):
"""Invokes the `cc_bindings_from_rs` tool to generate C++ bindings for a Rust crate.
Args:
ctx: The rule context.
target: The target crate.
basename: The basename for the generated files
inputs: `cc_bindings_from_rs` inputs specific to the target `crate`
args: `rustc` and `process_wrapper` arguments from construct_arguments.
rustc_env: `rustc` environment to use when running `cc_bindings_from_rs`
Returns:
A tuple of (GeneratedBindingsInfo, features).
"""
h_out_file = ctx.actions.declare_file(basename + "_cc_api.h")
new_h_out_file = ctx.actions.declare_file(basename + ".h")
rs_out_file = ctx.actions.declare_file(basename + "_cc_api_impl.rs")
crubit_args = ctx.actions.args()
crubit_args.add("--h-out", h_out_file)
crubit_args.add("--rs-out", rs_out_file)
crubit_args.add("--crubit-support-path-format", "\"support/{header}\"")
crubit_args.add("--clang-format-exe-path", ctx.file._clang_format)
crubit_args.add("--rustfmt-exe-path", ctx.file._rustfmt)
crubit_args.add("--rustfmt-config-path", ctx.file._rustfmt_cfg)
for dep_bindings_info in _get_dep_bindings_infos(ctx):
for header in dep_bindings_info.headers:
arg = dep_bindings_info.crate_key + "=" + header.short_path
crubit_args.add("--bindings-from-dependency", arg)
for feature in dep_bindings_info.features:
arg = dep_bindings_info.crate_key + "=" + feature
crubit_args.add("--crate-feature", arg)
features = find_crubit_features(target, ctx)
# During rollout of feature-gating, we temporarily enable all features on all targets that
# do not otherwise specify what features they enable.
#
# Once feature gating is completely implemented, we can apply the features to everything,
# and remove the magic default.
# TODO(b/262878759): Remove `all` once all users have the correct features applied.
if not features:
features.append("all")
for feature in features:
crubit_args.add("--crate-feature", "self=" + feature)
outputs = [h_out_file, rs_out_file]
if ctx.attr._generate_error_report[BuildSettingInfo].value:
error_report_output = ctx.actions.declare_file(basename + "_cc_api_error_report.json")
crubit_args.add(
"--error-report-out",
error_report_output.path,
)
outputs.append(error_report_output)
config = crate_name_to_library_config(target, ctx)
current_config = config.get("self", None)
for crate_name, crate_config in config.items():
if crate_config.namespace:
crubit_args.add("--crate-namespace", crate_name + "=" + crate_config.namespace)
for flag in collect_cc_bindings_from_rust_cli_flags(target, ctx):
crubit_args.add(flag)
toolchain = ctx.toolchains["//cc_bindings_from_rs/bazel_support:toolchain_type"].cc_bindings_from_rs_toolchain_info
ctx.actions.run(
outputs = outputs,
inputs = depset(
[ctx.file._clang_format, ctx.file._rustfmt, ctx.file._rustfmt_cfg],
transitive = [inputs],
),
env = rustc_env,
tools = [toolchain.binary],
executable = ctx.executable._process_wrapper,
mnemonic = "CcBindingsFromRust",
progress_message = "Generating C++ bindings from Rust: %s" % h_out_file,
# We don't use `args.all` here, because we want to do a couple of things:
#
# 1. specifically separate the crubit_args from the rustc_args, via `--`, putting crubit
# args first.
# 2. change the rustc path to instead point to crubit.
#
# That said, if we passed arguments to crubit via environment variables or via flags that
# can be interleaved with rustc flags in any order, and if we used toolchain.binary
# as the tool_path for construct_arguments, then this could be `args.all` instead.
arguments = [args.process_wrapper_flags, "--", toolchain.binary.path, crubit_args, "--", args.rustc_flags],
toolchain = "//cc_bindings_from_rs/bazel_support:toolchain_type",
)
include_statement = "#include \"%s\" // IWYU pragma: export" % h_out_file.short_path
ctx.actions.run_shell(
outputs = [new_h_out_file],
inputs = [],
progress_message = "Generate a wrapper header of %s as %s" % (h_out_file.path, new_h_out_file.path),
command = "echo '%s' > %s" % (include_statement, new_h_out_file.path),
)
generated_bindings_info = GeneratedBindingsInfo(
h_file = h_out_file,
new_h_file = new_h_out_file,
rust_file = rs_out_file,
)
return generated_bindings_info, features, current_config
def _make_cc_info_for_h_out_file(ctx, h_out_file, new_h_out_file, cc_infos):
"""Creates and returns CcInfo for the generated ..._cc_api.h header file.
Args:
ctx: The rule context.
h_out_file: The generated "..._cc_api.h" header file
cc_infos: cc_infos for dependencies of the h_out_file - should include both:
1) the target `crate` and
2) the compiled Rust glue crate (`..._cc_api_impl.rs` file).
Returns:
A CcInfo provider.
"""
cc_info = cc_common.merge_cc_infos(cc_infos = [
dep[CcInfo]
for dep in ctx.attr._cc_deps_for_bindings
] + cc_infos)
cc_toolchain = find_cpp_toolchain(ctx)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
)
(compilation_context, compilation_outputs) = cc_common.compile(
name = ctx.label.name,
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
public_hdrs = [h_out_file, new_h_out_file],
compilation_contexts = [cc_info.compilation_context],
)
(linking_context, _) = cc_common.create_linking_context_from_compilation_outputs(
name = ctx.label.name,
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
compilation_outputs = compilation_outputs,
linking_contexts = [cc_info.linking_context],
)
debug_context = cc_common.merge_debug_context([
cc_info.debug_context(),
cc_common.create_debug_context(compilation_outputs),
])
return CcInfo(
compilation_context = compilation_context,
linking_context = linking_context,
debug_context = debug_context,
)
def _compile_rs_out_file(ctx, rs_out_file, target):
"""Compiles the generated "..._cc_api_impl.rs" file.
Args:
ctx: The rule context.
rs_out_file: The generated "..._cc_api_impl.rs" file
target: The target crate, e.g. as provided to `ctx.attr.crate`.
Returns:
CcInfo for the generated "..._cc_api_impl.rs".
"""
deps = [
DepVariantInfo(
crate_info = dep[CrateInfo],
dep_info = dep[DepInfo],
cc_info = dep[CcInfo],
build_info = None,
)
for dep in ctx.attr._rs_deps_for_bindings + [target]
]
# The `..._cc_api_impl.rs` file needs to depend on all the deps of the target crate.
deps += target[CrateInfo].deps.to_list()
dep_variant_info = compile_rust(
ctx,
attr = ctx.rule.attr,
src = rs_out_file,
extra_srcs = [],
deps = deps,
crate_name = target[CrateInfo].name + "_cc_api_impl",
include_coverage = True,
force_all_deps_direct = False,
)
return dep_variant_info.cc_info
def _cc_bindings_from_rust_aspect_impl(target, ctx):
basename = target.label.name
if CrateInfo not in target:
return []
if str(target.label) in targets_to_remove:
return []
toolchain = find_toolchain(ctx)
crate_info = target[CrateInfo]
cc_toolchain = find_cpp_toolchain(ctx)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
)
dep_info, build_info, linkstamps = collect_deps(
deps = crate_info.deps,
proc_macro_deps = crate_info.proc_macro_deps,
aliases = crate_info.aliases,
)
compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
ctx = ctx,
file = ctx.file,
files = ctx.files,
linkstamps = linkstamps,
toolchain = toolchain,
cc_toolchain = cc_toolchain,
feature_configuration = feature_configuration,
crate_info = crate_info,
dep_info = dep_info,
build_info = build_info,
stamp = False,
experimental_use_cc_common_link = False,
)
# TODO(b/282958841): The `collect_inputs` call above should take the `data`
# dependency into account.
data_files = [target.files for target in getattr(ctx.rule.attr, "data", [])]
compile_inputs = depset(transitive = [compile_inputs] + data_files)
args, env = construct_arguments(
ctx = ctx,
attr = ctx.rule.attr,
file = ctx.file,
toolchain = toolchain,
tool_path = toolchain.rustc.path,
cc_toolchain = cc_toolchain,
emit = [],
feature_configuration = feature_configuration,
crate_info = crate_info,
dep_info = dep_info,
linkstamp_outs = linkstamp_outs,
ambiguous_libs = ambiguous_libs,
# TODO(lukasza): Do we need to pass an output_hash here?
# b/254690602 suggests that we want to include a hash in
# the names of namespaces generated by cc_bindings_from_rs.
output_hash = "",
rust_flags = ctx.attr._extra_rustc_flags[ExtraRustcFlagsInfo].extra_rustc_flags +
ctx.attr._extra_rustc_flag[ExtraRustcFlagsInfo].extra_rustc_flags,
out_dir = out_dir,
build_env_files = build_env_files,
build_flags_files = build_flags_files,
force_all_deps_direct = False,
stamp = False,
use_json_output = False,
skip_expanding_rustc_env = True,
)
bindings_info, features, config = _generate_bindings(
ctx,
target,
basename,
compile_inputs,
args,
env,
)
impl_cc_info = _compile_rs_out_file(ctx, bindings_info.rust_file, target)
cc_info = _make_cc_info_for_h_out_file(
ctx,
bindings_info.h_file,
bindings_info.new_h_file,
cc_infos = [target[CcInfo], impl_cc_info] + [
dep_bindings_info.cc_info
for dep_bindings_info in _get_dep_bindings_infos(ctx)
],
)
return [
CcBindingsFromRustInfo(
cc_info = cc_info,
crate_key = crate_info.name,
headers = [bindings_info.h_file],
features = features,
configuration = config,
),
bindings_info,
OutputGroupInfo(out = depset([bindings_info.h_file, bindings_info.rust_file, bindings_info.new_h_file])),
]
cc_bindings_from_rust_aspect = aspect(
implementation = _cc_bindings_from_rust_aspect_impl,
doc = "Aspect for generating C++ bindings for a Rust library.",
attr_aspects = ["deps"],
attrs = {
"_cc_toolchain": attr.label(
default = "@bazel_tools//tools/cpp:current_cc_toolchain",
),
"_clang_format": attr.label(
default = "//third_party/crosstool/google3_users:stable_clang-format",
executable = True,
allow_single_file = True,
cfg = "exec",
),
"_cc_deps_for_bindings": attr.label_list(
doc = "Dependencies needed to build the C++ sources generated by cc_bindings_from_rs.",
default = [
"//support/internal:bindings_support",
"//support/rs_std:rs_char",
],
),
"_process_wrapper": attr.label(
default = "@rules_rust//util/process_wrapper",
executable = True,
allow_single_file = True,
cfg = "exec",
),
"_rs_deps_for_bindings": attr.label_list(
doc = "Dependencies needed to build the Rust sources generated by cc_bindings_from_rs.",
default = [],
),
"_rustfmt": attr.label(
default = "//third_party/crosstool/rust/unstable:genrustfmt_for_crubit_aspects",
executable = True,
allow_single_file = True,
cfg = "exec",
),
"_rustfmt_cfg": attr.label(
default = "//nowhere:rustfmt.toml",
allow_single_file = True,
),
"_extra_rustc_flags": attr.label(
default = Label("@rules_rust//:extra_rustc_flags"),
),
"_extra_rustc_flag": attr.label(
default = Label("@rules_rust//:extra_rustc_flag"),
),
"_generate_error_report": attr.label(
default = "//cc_bindings_from_rs/bazel_support:generate_error_report",
),
"_globally_enabled_features": attr.label(
default = "//common/bazel_support:globally_enabled_features",
),
},
toolchains = [
"@rules_rust//rust:toolchain_type",
"//cc_bindings_from_rs/bazel_support:toolchain_type",
] + use_cpp_toolchain(),
fragments = ["cpp"],
)
def _cc_bindings_from_rust_rule_impl(ctx):
crate = ctx.attr.crate
return [
crate[CcBindingsFromRustInfo].cc_info,
# If we try to generate rust bindings of c++ bindings of this rust crate, we get back
# the original rust crate again.
RustBindingsFromCcInfo(
cc_info = None,
dep_variant_info = DepVariantInfo(
crate_info = crate[CrateInfo] if CrateInfo in crate else None,
dep_info = crate[DepInfo] if DepInfo in crate else None,
build_info = crate[BuildInfo] if BuildInfo in crate else None,
cc_info = crate[CcInfo] if CcInfo in crate else None,
),
target_args = depset([]),
namespaces = None,
),
]
cc_bindings_from_rust = rule(
implementation = _cc_bindings_from_rust_rule_impl,
doc = "Rule for generating C++ bindings for a Rust library.",
attrs = {
"crate": attr.label(
doc = "Rust library to generate C++ bindings for",
allow_files = False,
mandatory = True,
providers = [CcBindingsFromRustInfo],
aspects = [cc_bindings_from_rust_aspect],
),
},
)