blob: a396300295f07edf49847b6dc24a6c66c9a1815f [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001# Copyright 2019 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Adam Snaider1c095c92023-07-08 02:09:58 -040015"""Rust Bindgen rules"""
16
Brian Silvermancc09f182022-03-09 15:40:20 -080017load("//rust:defs.bzl", "rust_library")
18
19# buildifier: disable=bzl-visibility
20load("//rust/private:rustc.bzl", "get_linker_and_args")
21
22# buildifier: disable=bzl-visibility
Adam Snaider1c095c92023-07-08 02:09:58 -040023load("//rust/private:utils.bzl", "find_cc_toolchain", "get_preferred_artifact")
Brian Silvermancc09f182022-03-09 15:40:20 -080024
25# TODO(hlopko): use the more robust logic from rustc.bzl also here, through a reasonable API.
26def _get_libs_for_static_executable(dep):
27 """find the libraries used for linking a static executable.
28
29 Args:
30 dep (Target): A cc_library target.
31
32 Returns:
33 depset: A depset[File]
34 """
35 linker_inputs = dep[CcInfo].linking_context.linker_inputs.to_list()
36 return depset([get_preferred_artifact(lib, use_pic = False) for li in linker_inputs for lib in li.libraries])
37
38def rust_bindgen_library(
39 name,
40 header,
41 cc_lib,
42 bindgen_flags = None,
43 clang_flags = None,
Brian Silvermancc09f182022-03-09 15:40:20 -080044 **kwargs):
45 """Generates a rust source file for `header`, and builds a rust_library.
46
47 Arguments are the same as `rust_bindgen`, and `kwargs` are passed directly to rust_library.
48
49 Args:
50 name (str): A unique name for this target.
51 header (str): The label of the .h file to generate bindings for.
52 cc_lib (str): The label of the cc_library that contains the .h file. This is used to find the transitive includes.
53 bindgen_flags (list, optional): Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.
54 clang_flags (list, optional): Flags to pass directly to the clang executable.
Brian Silvermancc09f182022-03-09 15:40:20 -080055 **kwargs: Arguments to forward to the underlying `rust_library` rule.
56 """
57
58 tags = kwargs.get("tags") or []
59 if "tags" in kwargs:
60 kwargs.pop("tags")
61
62 deps = kwargs.get("deps") or []
63 if "deps" in kwargs:
64 kwargs.pop("deps")
65
66 rust_bindgen(
67 name = name + "__bindgen",
68 header = header,
69 cc_lib = cc_lib,
70 bindgen_flags = bindgen_flags or [],
71 clang_flags = clang_flags or [],
Brian Silvermancc09f182022-03-09 15:40:20 -080072 tags = tags,
73 )
74
75 rust_library(
76 name = name,
77 srcs = [name + "__bindgen.rs"],
Brian Silverman5f6f2762022-08-13 19:30:05 -070078 tags = tags + ["__bindgen", "noclippy"],
Brian Silvermancc09f182022-03-09 15:40:20 -080079 deps = deps + [cc_lib],
80 **kwargs
81 )
82
83def _rust_bindgen_impl(ctx):
Adam Snaider1c095c92023-07-08 02:09:58 -040084 toolchain = ctx.toolchains[Label("//bindgen:toolchain_type")]
Brian Silvermancc09f182022-03-09 15:40:20 -080085
86 # nb. We can't grab the cc_library`s direct headers, so a header must be provided.
87 cc_lib = ctx.attr.cc_lib
88 header = ctx.file.header
89 cc_header_list = ctx.attr.cc_lib[CcInfo].compilation_context.headers.to_list()
90 if header not in cc_header_list:
91 fail("Header {} is not in {}'s transitive headers.".format(ctx.attr.header, cc_lib), "header")
92
Brian Silverman5f6f2762022-08-13 19:30:05 -070093 toolchain = ctx.toolchains[Label("//bindgen:toolchain_type")]
Brian Silvermancc09f182022-03-09 15:40:20 -080094 bindgen_bin = toolchain.bindgen
Brian Silvermancc09f182022-03-09 15:40:20 -080095 clang_bin = toolchain.clang
96 libclang = toolchain.libclang
97 libstdcxx = toolchain.libstdcxx
98
Brian Silvermancc09f182022-03-09 15:40:20 -080099 output = ctx.outputs.out
Adam Snaider1c095c92023-07-08 02:09:58 -0400100 tools = depset([clang_bin])
Brian Silvermancc09f182022-03-09 15:40:20 -0800101
102 # libclang should only have 1 output file
103 libclang_dir = _get_libs_for_static_executable(libclang).to_list()[0].dirname
104 include_directories = cc_lib[CcInfo].compilation_context.includes.to_list()
105 quote_include_directories = cc_lib[CcInfo].compilation_context.quote_includes.to_list()
106 system_include_directories = cc_lib[CcInfo].compilation_context.system_includes.to_list()
107
Brian Silvermancc09f182022-03-09 15:40:20 -0800108 env = {
109 "CLANG_PATH": clang_bin.path,
110 "LIBCLANG_PATH": libclang_dir,
111 "RUST_BACKTRACE": "1",
112 }
Adam Snaider1c095c92023-07-08 02:09:58 -0400113
114 args = ctx.actions.args()
115
116 # Configure Bindgen Arguments
117 args.add_all(ctx.attr.bindgen_flags)
118 args.add(header.path)
119 args.add("--output", output)
120
121 # Vanilla usage of bindgen produces formatted output, here we do the same if we have `rustfmt` in our toolchain.
122 rustfmt_toolchain = ctx.toolchains[Label("//rust/rustfmt:toolchain_type")]
123 if toolchain.default_rustfmt:
124 # Bindgen is able to find rustfmt using the RUSTFMT environment variable
125 env.update({"RUSTFMT": rustfmt_toolchain.rustfmt.path})
126 tools = depset(transitive = [tools, rustfmt_toolchain.all_files])
127 else:
128 args.add("--no-rustfmt-bindings")
129
130 # Configure Clang Arguments
131 args.add("--")
132 args.add_all(include_directories, before_each = "-I")
133 args.add_all(quote_include_directories, before_each = "-iquote")
134 args.add_all(system_include_directories, before_each = "-isystem")
135 args.add_all(ctx.attr.clang_flags)
136
Brian Silvermancc09f182022-03-09 15:40:20 -0800137 cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
Adam Snaider1c095c92023-07-08 02:09:58 -0400138 _, _, linker_env = get_linker_and_args(ctx, ctx.attr, "bin", cc_toolchain, feature_configuration, None)
Brian Silvermancc09f182022-03-09 15:40:20 -0800139 env.update(**linker_env)
140
Adam Snaider1c095c92023-07-08 02:09:58 -0400141 # Allow sysroots configured by the toolchain to be added to Clang arguments.
142 if "no-rust-bindgen-cc-sysroot" not in ctx.features:
143 if cc_toolchain.sysroot:
144 tools = depset(transitive = [tools, cc_toolchain.all_files])
145 sysroot_args = ["--sysroot", cc_toolchain.sysroot]
146 for arg in ctx.attr.clang_flags:
147 if arg.startswith("--sysroot"):
148 sysroot_args = []
149 break
150 args.add_all(sysroot_args)
151
Brian Silvermancc09f182022-03-09 15:40:20 -0800152 # Set the dynamic linker search path so that clang uses the libstdcxx from the toolchain.
153 # DYLD_LIBRARY_PATH is LD_LIBRARY_PATH on macOS.
154 if libstdcxx:
155 env["LD_LIBRARY_PATH"] = ":".join([f.dirname for f in _get_libs_for_static_executable(libstdcxx).to_list()])
156 env["DYLD_LIBRARY_PATH"] = env["LD_LIBRARY_PATH"]
157
158 ctx.actions.run(
159 executable = bindgen_bin,
160 inputs = depset(
161 [header],
162 transitive = [
163 cc_lib[CcInfo].compilation_context.headers,
164 _get_libs_for_static_executable(libclang),
165 ] + ([
166 _get_libs_for_static_executable(libstdcxx),
167 ] if libstdcxx else []),
168 ),
Adam Snaider1c095c92023-07-08 02:09:58 -0400169 outputs = [output],
Brian Silvermancc09f182022-03-09 15:40:20 -0800170 mnemonic = "RustBindgen",
171 progress_message = "Generating bindings for {}..".format(header.path),
172 env = env,
173 arguments = [args],
Adam Snaider1c095c92023-07-08 02:09:58 -0400174 tools = tools,
Brian Silvermancc09f182022-03-09 15:40:20 -0800175 )
176
Brian Silvermancc09f182022-03-09 15:40:20 -0800177rust_bindgen = rule(
178 doc = "Generates a rust source file from a cc_library and a header.",
179 implementation = _rust_bindgen_impl,
180 attrs = {
181 "bindgen_flags": attr.string_list(
182 doc = "Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.",
183 ),
184 "cc_lib": attr.label(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700185 doc = "The cc_library that contains the `.h` file. This is used to find the transitive includes.",
Brian Silvermancc09f182022-03-09 15:40:20 -0800186 providers = [CcInfo],
187 ),
188 "clang_flags": attr.string_list(
189 doc = "Flags to pass directly to the clang executable.",
190 ),
191 "header": attr.label(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700192 doc = "The `.h` file to generate bindings for.",
Brian Silvermancc09f182022-03-09 15:40:20 -0800193 allow_single_file = True,
194 ),
Brian Silvermancc09f182022-03-09 15:40:20 -0800195 "_cc_toolchain": attr.label(
196 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
197 ),
198 "_process_wrapper": attr.label(
199 default = Label("//util/process_wrapper"),
200 executable = True,
201 allow_single_file = True,
202 cfg = "exec",
203 ),
204 },
205 outputs = {"out": "%{name}.rs"},
206 fragments = ["cpp"],
207 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700208 str(Label("//bindgen:toolchain_type")),
209 str(Label("//rust:toolchain_type")),
Adam Snaider1c095c92023-07-08 02:09:58 -0400210 str(Label("//rust/rustfmt:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800211 "@bazel_tools//tools/cpp:toolchain_type",
212 ],
213 incompatible_use_toolchain_transition = True,
214)
215
216def _rust_bindgen_toolchain_impl(ctx):
217 return platform_common.ToolchainInfo(
218 bindgen = ctx.executable.bindgen,
219 clang = ctx.executable.clang,
220 libclang = ctx.attr.libclang,
221 libstdcxx = ctx.attr.libstdcxx,
Adam Snaider1c095c92023-07-08 02:09:58 -0400222 default_rustfmt = ctx.attr.default_rustfmt,
Brian Silvermancc09f182022-03-09 15:40:20 -0800223 )
224
225rust_bindgen_toolchain = rule(
226 _rust_bindgen_toolchain_impl,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700227 doc = """\
228The tools required for the `rust_bindgen` rule.
229
230This rule depends on the [`bindgen`](https://crates.io/crates/bindgen) binary crate, and it
231in turn depends on both a clang binary and the clang library. To obtain these dependencies,
232`rust_bindgen_dependencies` imports bindgen and its dependencies.
233
234```python
235load("@rules_rust//bindgen:bindgen.bzl", "rust_bindgen_toolchain")
236
237rust_bindgen_toolchain(
238 name = "bindgen_toolchain_impl",
239 bindgen = "//my/rust:bindgen",
240 clang = "//my/clang:clang",
241 libclang = "//my/clang:libclang.so",
242 libstdcxx = "//my/cpp:libstdc++",
243)
244
245toolchain(
246 name = "bindgen_toolchain",
247 toolchain = "bindgen_toolchain_impl",
248 toolchain_type = "@rules_rust//bindgen:toolchain_type",
249)
250```
251
252This toolchain will then need to be registered in the current `WORKSPACE`.
253For additional information, see the [Bazel toolchains documentation](https://docs.bazel.build/versions/master/toolchains.html).
254""",
Brian Silvermancc09f182022-03-09 15:40:20 -0800255 attrs = {
256 "bindgen": attr.label(
257 doc = "The label of a `bindgen` executable.",
258 executable = True,
259 cfg = "exec",
260 ),
261 "clang": attr.label(
262 doc = "The label of a `clang` executable.",
263 executable = True,
264 cfg = "exec",
265 ),
Adam Snaider1c095c92023-07-08 02:09:58 -0400266 "default_rustfmt": attr.bool(
267 doc = "If set, `rust_bindgen` targets will always format generated sources with `rustfmt`.",
268 mandatory = False,
269 ),
Brian Silvermancc09f182022-03-09 15:40:20 -0800270 "libclang": attr.label(
271 doc = "A cc_library that provides bindgen's runtime dependency on libclang.",
272 cfg = "exec",
273 providers = [CcInfo],
274 ),
275 "libstdcxx": attr.label(
276 doc = "A cc_library that satisfies libclang's libstdc++ dependency. This is used to make the execution of clang hermetic. If None, system libraries will be used instead.",
277 cfg = "exec",
278 providers = [CcInfo],
279 mandatory = False,
280 ),
Brian Silvermancc09f182022-03-09 15:40:20 -0800281 },
282)