blob: 9265f9fba3e11146ff08308deab25236ad1e2122 [file] [log] [blame]
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# 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.
# buildifier: disable=module-docstring
load("//rust:rust.bzl", "rust_library")
# buildifier: disable=bzl-visibility
load("//rust/private:utils.bzl", "find_toolchain", "get_preferred_artifact")
# TODO(hlopko): use the more robust logic from rustc.bzl also here, through a reasonable API.
def _get_libs_for_static_executable(dep):
"""find the libraries used for linking a static executable.
Args:
dep (Target): A cc_library target.
Returns:
depset: A depset[File]
"""
linker_inputs = dep[CcInfo].linking_context.linker_inputs.to_list()
return depset([get_preferred_artifact(lib) for li in linker_inputs for lib in li.libraries])
def rust_bindgen_library(
name,
header,
cc_lib,
bindgen_flags = None,
clang_flags = None,
**kwargs):
"""Generates a rust source file for `header`, and builds a rust_library.
Arguments are the same as `rust_bindgen`, and `kwargs` are passed directly to rust_library.
Args:
name (str): A unique name for this target.
header (str): The label of the .h file to generate bindings for.
cc_lib (str): The label of the cc_library that contains the .h file. This is used to find the transitive includes.
bindgen_flags (list, optional): Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.
clang_flags (list, optional): Flags to pass directly to the clang executable.
**kwargs: Arguments to forward to the underlying `rust_library` rule.
"""
rust_bindgen(
name = name + "__bindgen",
header = header,
cc_lib = cc_lib,
bindgen_flags = bindgen_flags or [],
clang_flags = clang_flags or [],
)
rust_library(
name = name,
srcs = [name + "__bindgen.rs"],
tags = ["__bindgen"],
deps = [cc_lib],
**kwargs
)
def _rust_bindgen_impl(ctx):
rust_toolchain = find_toolchain(ctx)
# nb. We can't grab the cc_library`s direct headers, so a header must be provided.
cc_lib = ctx.attr.cc_lib
header = ctx.file.header
cc_header_list = ctx.attr.cc_lib[CcInfo].compilation_context.headers.to_list()
if header not in cc_header_list:
fail("Header {} is not in {}'s transitive headers.".format(ctx.attr.header, cc_lib), "header")
toolchain = ctx.toolchains[Label("//bindgen:bindgen_toolchain")]
bindgen_bin = toolchain.bindgen
rustfmt_bin = toolchain.rustfmt or rust_toolchain.rustfmt
clang_bin = toolchain.clang
libclang = toolchain.libclang
# TODO: This rule shouldn't need to depend on libstdc++
# This rule requires an explicit dependency on a libstdc++ because
# 1. It is a runtime dependency of libclang.so
# 2. We cannot locate it in the cc_toolchain yet
# Depending on how libclang.so was compiled, it may try to locate its libstdc++ dependency
# in a way that makes our handling here unnecessary (eg. system /usr/lib/x86_64-linux-gnu/libstdc++.so.6)
libstdcxx = toolchain.libstdcxx
# rustfmt is not where bindgen expects to find it, so we format manually
bindgen_args = ["--no-rustfmt-bindings"] + ctx.attr.bindgen_flags
clang_args = ctx.attr.clang_flags
output = ctx.outputs.out
# libclang should only have 1 output file
libclang_dir = _get_libs_for_static_executable(libclang).to_list()[0].dirname
include_directories = cc_lib[CcInfo].compilation_context.includes.to_list()
quote_include_directories = cc_lib[CcInfo].compilation_context.quote_includes.to_list()
system_include_directories = cc_lib[CcInfo].compilation_context.system_includes.to_list()
# Vanilla usage of bindgen produces formatted output, here we do the same if we have `rustfmt` in our toolchain.
if rustfmt_bin:
unformatted_output = ctx.actions.declare_file(output.basename + ".unformatted")
else:
unformatted_output = output
args = ctx.actions.args()
args.add_all(bindgen_args)
args.add(header.path)
args.add("--output", unformatted_output.path)
args.add("--")
args.add_all(include_directories, before_each = "-I")
args.add_all(quote_include_directories, before_each = "-iquote")
args.add_all(system_include_directories, before_each = "-isystem")
args.add_all(clang_args)
env = {
"CLANG_PATH": clang_bin.path,
"LIBCLANG_PATH": libclang_dir,
"RUST_BACKTRACE": "1",
}
if libstdcxx:
env["LD_LIBRARY_PATH"] = ":".join([f.dirname for f in _get_libs_for_static_executable(libstdcxx).to_list()])
ctx.actions.run(
executable = bindgen_bin,
inputs = depset(
[header],
transitive = [
cc_lib[CcInfo].compilation_context.headers,
_get_libs_for_static_executable(libclang),
] + [
_get_libs_for_static_executable(libstdcxx),
] if libstdcxx else [],
),
outputs = [unformatted_output],
mnemonic = "RustBindgen",
progress_message = "Generating bindings for {}..".format(header.path),
env = env,
arguments = [args],
tools = [clang_bin],
)
if rustfmt_bin:
rustfmt_args = ctx.actions.args()
rustfmt_args.add("--stdout-file", output.path)
rustfmt_args.add("--")
rustfmt_args.add(rustfmt_bin.path)
rustfmt_args.add("--emit", "stdout")
rustfmt_args.add("--quiet")
rustfmt_args.add(unformatted_output.path)
ctx.actions.run(
executable = ctx.executable._process_wrapper,
inputs = [unformatted_output],
outputs = [output],
arguments = [rustfmt_args],
tools = [rustfmt_bin],
mnemonic = "Rustfmt",
)
rust_bindgen = rule(
doc = "Generates a rust source file from a cc_library and a header.",
implementation = _rust_bindgen_impl,
attrs = {
"bindgen_flags": attr.string_list(
doc = "Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.",
),
"cc_lib": attr.label(
doc = "The cc_library that contains the .h file. This is used to find the transitive includes.",
providers = [CcInfo],
),
"clang_flags": attr.string_list(
doc = "Flags to pass directly to the clang executable.",
),
"header": attr.label(
doc = "The .h file to generate bindings for.",
allow_single_file = True,
),
"_process_wrapper": attr.label(
default = Label("//util/process_wrapper"),
executable = True,
allow_single_file = True,
cfg = "exec",
),
},
outputs = {"out": "%{name}.rs"},
toolchains = [
str(Label("//bindgen:bindgen_toolchain")),
str(Label("//rust:toolchain")),
],
)
def _rust_bindgen_toolchain_impl(ctx):
return platform_common.ToolchainInfo(
bindgen = ctx.executable.bindgen,
clang = ctx.executable.clang,
libclang = ctx.attr.libclang,
libstdcxx = ctx.attr.libstdcxx,
rustfmt = ctx.executable.rustfmt,
)
rust_bindgen_toolchain = rule(
_rust_bindgen_toolchain_impl,
doc = "The tools required for the `rust_bindgen` rule.",
attrs = {
"bindgen": attr.label(
doc = "The label of a `bindgen` executable.",
executable = True,
cfg = "exec",
),
"clang": attr.label(
doc = "The label of a `clang` executable.",
executable = True,
cfg = "exec",
),
"libclang": attr.label(
doc = "A cc_library that provides bindgen's runtime dependency on libclang.",
cfg = "exec",
providers = [CcInfo],
),
"libstdcxx": attr.label(
doc = "A cc_library that satisfies libclang's libstdc++ dependency.",
cfg = "exec",
providers = [CcInfo],
),
"rustfmt": attr.label(
doc = "The label of a `rustfmt` executable. If this is provided, generated sources will be formatted.",
executable = True,
cfg = "exec",
mandatory = False,
),
},
)