blob: a0e93d514a2dc9337f47406c2319aac5670147fc [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
15# buildifier: disable=module-docstring
16load("//rust:defs.bzl", "rust_library")
17
18# buildifier: disable=bzl-visibility
19load("//rust/private:rustc.bzl", "get_linker_and_args")
20
21# buildifier: disable=bzl-visibility
22load("//rust/private:utils.bzl", "find_cc_toolchain", "find_toolchain", "get_preferred_artifact")
23
24# TODO(hlopko): use the more robust logic from rustc.bzl also here, through a reasonable API.
25def _get_libs_for_static_executable(dep):
26 """find the libraries used for linking a static executable.
27
28 Args:
29 dep (Target): A cc_library target.
30
31 Returns:
32 depset: A depset[File]
33 """
34 linker_inputs = dep[CcInfo].linking_context.linker_inputs.to_list()
35 return depset([get_preferred_artifact(lib, use_pic = False) for li in linker_inputs for lib in li.libraries])
36
37def rust_bindgen_library(
38 name,
39 header,
40 cc_lib,
41 bindgen_flags = None,
42 clang_flags = None,
43 rustfmt = True,
44 **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.
55 rustfmt (bool, optional): Enable or disable running rustfmt on the generated file.
56 **kwargs: Arguments to forward to the underlying `rust_library` rule.
57 """
58
59 tags = kwargs.get("tags") or []
60 if "tags" in kwargs:
61 kwargs.pop("tags")
62
63 deps = kwargs.get("deps") or []
64 if "deps" in kwargs:
65 kwargs.pop("deps")
66
67 rust_bindgen(
68 name = name + "__bindgen",
69 header = header,
70 cc_lib = cc_lib,
71 bindgen_flags = bindgen_flags or [],
72 clang_flags = clang_flags or [],
73 rustfmt = rustfmt,
74 tags = tags,
75 )
76
77 rust_library(
78 name = name,
79 srcs = [name + "__bindgen.rs"],
80 tags = tags + ["__bindgen"],
81 deps = deps + [cc_lib],
82 **kwargs
83 )
84
85def _rust_bindgen_impl(ctx):
86 rust_toolchain = find_toolchain(ctx)
87
88 # nb. We can't grab the cc_library`s direct headers, so a header must be provided.
89 cc_lib = ctx.attr.cc_lib
90 header = ctx.file.header
91 cc_header_list = ctx.attr.cc_lib[CcInfo].compilation_context.headers.to_list()
92 if header not in cc_header_list:
93 fail("Header {} is not in {}'s transitive headers.".format(ctx.attr.header, cc_lib), "header")
94
95 toolchain = ctx.toolchains[Label("//bindgen:bindgen_toolchain")]
96 bindgen_bin = toolchain.bindgen
97 rustfmt_bin = toolchain.rustfmt or rust_toolchain.rustfmt
98 clang_bin = toolchain.clang
99 libclang = toolchain.libclang
100 libstdcxx = toolchain.libstdcxx
101
102 # rustfmt is not where bindgen expects to find it, so we format manually
103 bindgen_args = ["--no-rustfmt-bindings"] + ctx.attr.bindgen_flags
104 clang_args = ctx.attr.clang_flags
105
106 output = ctx.outputs.out
107
108 # libclang should only have 1 output file
109 libclang_dir = _get_libs_for_static_executable(libclang).to_list()[0].dirname
110 include_directories = cc_lib[CcInfo].compilation_context.includes.to_list()
111 quote_include_directories = cc_lib[CcInfo].compilation_context.quote_includes.to_list()
112 system_include_directories = cc_lib[CcInfo].compilation_context.system_includes.to_list()
113
114 # Vanilla usage of bindgen produces formatted output, here we do the same if we have `rustfmt` in our toolchain.
115 if ctx.attr.rustfmt and rustfmt_bin:
116 unformatted_output = ctx.actions.declare_file(output.basename + ".unformatted")
117 else:
118 unformatted_output = output
119
120 args = ctx.actions.args()
121 args.add_all(bindgen_args)
122 args.add(header.path)
123 args.add("--output", unformatted_output.path)
124 args.add("--")
125 args.add_all(include_directories, before_each = "-I")
126 args.add_all(quote_include_directories, before_each = "-iquote")
127 args.add_all(system_include_directories, before_each = "-isystem")
128 args.add_all(clang_args)
129
130 env = {
131 "CLANG_PATH": clang_bin.path,
132 "LIBCLANG_PATH": libclang_dir,
133 "RUST_BACKTRACE": "1",
134 }
135 cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
136 _, _, linker_env = get_linker_and_args(ctx, ctx.attr, cc_toolchain, feature_configuration, None)
137 env.update(**linker_env)
138
139 # Set the dynamic linker search path so that clang uses the libstdcxx from the toolchain.
140 # DYLD_LIBRARY_PATH is LD_LIBRARY_PATH on macOS.
141 if libstdcxx:
142 env["LD_LIBRARY_PATH"] = ":".join([f.dirname for f in _get_libs_for_static_executable(libstdcxx).to_list()])
143 env["DYLD_LIBRARY_PATH"] = env["LD_LIBRARY_PATH"]
144
145 ctx.actions.run(
146 executable = bindgen_bin,
147 inputs = depset(
148 [header],
149 transitive = [
150 cc_lib[CcInfo].compilation_context.headers,
151 _get_libs_for_static_executable(libclang),
152 ] + ([
153 _get_libs_for_static_executable(libstdcxx),
154 ] if libstdcxx else []),
155 ),
156 outputs = [unformatted_output],
157 mnemonic = "RustBindgen",
158 progress_message = "Generating bindings for {}..".format(header.path),
159 env = env,
160 arguments = [args],
161 tools = [clang_bin],
162 )
163
164 if ctx.attr.rustfmt and rustfmt_bin:
165 rustfmt_args = ctx.actions.args()
166 rustfmt_args.add("--stdout-file", output.path)
167 rustfmt_args.add("--")
168 rustfmt_args.add(rustfmt_bin.path)
169 rustfmt_args.add("--emit", "stdout")
170 rustfmt_args.add("--quiet")
171 rustfmt_args.add(unformatted_output.path)
172
173 ctx.actions.run(
174 executable = ctx.executable._process_wrapper,
175 inputs = [unformatted_output],
176 outputs = [output],
177 arguments = [rustfmt_args],
178 tools = [rustfmt_bin],
179 mnemonic = "Rustfmt",
180 )
181
182rust_bindgen = rule(
183 doc = "Generates a rust source file from a cc_library and a header.",
184 implementation = _rust_bindgen_impl,
185 attrs = {
186 "bindgen_flags": attr.string_list(
187 doc = "Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.",
188 ),
189 "cc_lib": attr.label(
190 doc = "The cc_library that contains the .h file. This is used to find the transitive includes.",
191 providers = [CcInfo],
192 ),
193 "clang_flags": attr.string_list(
194 doc = "Flags to pass directly to the clang executable.",
195 ),
196 "header": attr.label(
197 doc = "The .h file to generate bindings for.",
198 allow_single_file = True,
199 ),
200 "rustfmt": attr.bool(
201 doc = "Enable or disable running rustfmt on the generated file.",
202 default = True,
203 ),
204 "_cc_toolchain": attr.label(
205 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
206 ),
207 "_process_wrapper": attr.label(
208 default = Label("//util/process_wrapper"),
209 executable = True,
210 allow_single_file = True,
211 cfg = "exec",
212 ),
213 },
214 outputs = {"out": "%{name}.rs"},
215 fragments = ["cpp"],
216 toolchains = [
217 str(Label("//bindgen:bindgen_toolchain")),
218 str(Label("//rust:toolchain")),
219 "@bazel_tools//tools/cpp:toolchain_type",
220 ],
221 incompatible_use_toolchain_transition = True,
222)
223
224def _rust_bindgen_toolchain_impl(ctx):
225 return platform_common.ToolchainInfo(
226 bindgen = ctx.executable.bindgen,
227 clang = ctx.executable.clang,
228 libclang = ctx.attr.libclang,
229 libstdcxx = ctx.attr.libstdcxx,
230 rustfmt = ctx.executable.rustfmt,
231 )
232
233rust_bindgen_toolchain = rule(
234 _rust_bindgen_toolchain_impl,
235 doc = "The tools required for the `rust_bindgen` rule.",
236 attrs = {
237 "bindgen": attr.label(
238 doc = "The label of a `bindgen` executable.",
239 executable = True,
240 cfg = "exec",
241 ),
242 "clang": attr.label(
243 doc = "The label of a `clang` executable.",
244 executable = True,
245 cfg = "exec",
246 ),
247 "libclang": attr.label(
248 doc = "A cc_library that provides bindgen's runtime dependency on libclang.",
249 cfg = "exec",
250 providers = [CcInfo],
251 ),
252 "libstdcxx": attr.label(
253 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.",
254 cfg = "exec",
255 providers = [CcInfo],
256 mandatory = False,
257 ),
258 "rustfmt": attr.label(
259 doc = "The label of a `rustfmt` executable. If this is not provided, falls back to the rust_toolchain rustfmt.",
260 executable = True,
261 cfg = "exec",
262 mandatory = False,
263 ),
264 },
265)