• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 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"""Rust Bindgen rules"""
16
17load(
18    "@bazel_tools//tools/build_defs/cc:action_names.bzl",
19    "CPP_COMPILE_ACTION_NAME",
20)
21load("@rules_cc//cc:defs.bzl", "CcInfo")
22load("//rust:defs.bzl", "rust_library")
23load("//rust:rust_common.bzl", "BuildInfo")
24
25# buildifier: disable=bzl-visibility
26load("//rust/private:rustc.bzl", "get_linker_and_args")
27
28# buildifier: disable=bzl-visibility
29load("//rust/private:utils.bzl", "find_cc_toolchain", "get_lib_name_default", "get_preferred_artifact")
30
31# TODO(hlopko): use the more robust logic from rustc.bzl also here, through a reasonable API.
32def _get_libs_for_static_executable(dep):
33    """find the libraries used for linking a static executable.
34
35    Args:
36        dep (Target): A cc_library target.
37
38    Returns:
39        depset: A depset[File]
40    """
41    linker_inputs = dep[CcInfo].linking_context.linker_inputs.to_list()
42    return depset([get_preferred_artifact(lib, use_pic = False) for li in linker_inputs for lib in li.libraries])
43
44def rust_bindgen_library(
45        name,
46        header,
47        cc_lib,
48        bindgen_flags = None,
49        bindgen_features = None,
50        clang_flags = None,
51        **kwargs):
52    """Generates a rust source file for `header`, and builds a rust_library.
53
54    Arguments are the same as `rust_bindgen`, and `kwargs` are passed directly to rust_library.
55
56    Args:
57        name (str): A unique name for this target.
58        header (str): The label of the .h file to generate bindings for.
59        cc_lib (str): The label of the cc_library that contains the .h file. This is used to find the transitive includes.
60        bindgen_flags (list, optional): Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.
61        bindgen_features (list, optional): The `features` attribute for the `rust_bindgen` target.
62        clang_flags (list, optional): Flags to pass directly to the clang executable.
63        **kwargs: Arguments to forward to the underlying `rust_library` rule.
64    """
65
66    tags = kwargs.get("tags") or []
67    if "tags" in kwargs:
68        kwargs.pop("tags")
69
70    sub_tags = tags + ([] if "manual" in tags else ["manual"])
71
72    deps = kwargs.get("deps") or []
73    if "deps" in kwargs:
74        kwargs.pop("deps")
75
76    bindgen_kwargs = {}
77    if "leak_symbols" in kwargs:
78        bindgen_kwargs.update({"leak_symbols": kwargs.pop("leak_symbols")})
79
80    rust_bindgen(
81        name = name + "__bindgen",
82        header = header,
83        cc_lib = cc_lib,
84        bindgen_flags = bindgen_flags or [],
85        features = bindgen_features,
86        clang_flags = clang_flags or [],
87        tags = sub_tags,
88        **bindgen_kwargs
89    )
90
91    for custom_tag in ["__bindgen", "no-clippy", "no-rustfmt"]:
92        tags = tags + ([] if custom_tag in tags else [custom_tag])
93
94    rust_library(
95        name = name,
96        srcs = [name + "__bindgen.rs"],
97        deps = deps + [name + "__bindgen"],
98        tags = tags,
99        **kwargs
100    )
101
102def _get_user_link_flags(cc_lib):
103    linker_flags = []
104
105    for linker_input in cc_lib[CcInfo].linking_context.linker_inputs.to_list():
106        linker_flags.extend(linker_input.user_link_flags)
107
108    return linker_flags
109
110def _generate_cc_link_build_info(ctx, cc_lib):
111    """Produce the eqivilant cargo_build_script providers for use in linking the library.
112
113    Args:
114        ctx (ctx): The rule's context object
115        cc_lib (Target): The `rust_bindgen.cc_lib` target.
116
117    Returns:
118        The `BuildInfo` provider.
119    """
120    compile_data = []
121
122    rustc_flags = []
123    linker_search_paths = []
124
125    for linker_input in cc_lib[CcInfo].linking_context.linker_inputs.to_list():
126        for lib in linker_input.libraries:
127            if lib.static_library:
128                rustc_flags.append("-lstatic={}".format(get_lib_name_default(lib.static_library)))
129                linker_search_paths.append(lib.static_library.dirname)
130                compile_data.append(lib.static_library)
131            elif lib.pic_static_library:
132                rustc_flags.append("-lstatic={}".format(get_lib_name_default(lib.pic_static_library)))
133                linker_search_paths.append(lib.pic_static_library.dirname)
134                compile_data.append(lib.pic_static_library)
135
136    if not compile_data:
137        fail("No static libraries found in {}".format(
138            cc_lib.label,
139        ))
140
141    rustc_flags_file = ctx.actions.declare_file("{}.rustc_flags".format(ctx.label.name))
142    ctx.actions.write(
143        output = rustc_flags_file,
144        content = "\n".join(rustc_flags),
145    )
146
147    link_search_paths = ctx.actions.declare_file("{}.link_search_paths".format(ctx.label.name))
148    ctx.actions.write(
149        output = link_search_paths,
150        content = "\n".join([
151            "-Lnative=${{pwd}}/{}".format(path)
152            for path in depset(linker_search_paths).to_list()
153        ]),
154    )
155
156    return BuildInfo(
157        compile_data = depset(compile_data),
158        dep_env = None,
159        flags = rustc_flags_file,
160        # linker_flags is provided via CcInfo
161        linker_flags = None,
162        link_search_paths = link_search_paths,
163        out_dir = None,
164        rustc_env = None,
165    )
166
167def _rust_bindgen_impl(ctx):
168    # nb. We can't grab the cc_library`s direct headers, so a header must be provided.
169    cc_lib = ctx.attr.cc_lib
170    header = ctx.file.header
171    cc_header_list = ctx.attr.cc_lib[CcInfo].compilation_context.headers.to_list()
172    if header not in cc_header_list:
173        fail("Header {} is not in {}'s transitive headers.".format(ctx.attr.header, cc_lib), "header")
174
175    toolchain = ctx.toolchains[Label("//bindgen:toolchain_type")]
176    bindgen_bin = toolchain.bindgen
177    clang_bin = toolchain.clang
178    libclang = toolchain.libclang
179    libstdcxx = toolchain.libstdcxx
180
181    output = ctx.outputs.out
182
183    cc_toolchain, feature_configuration = find_cc_toolchain(ctx = ctx)
184
185    tools = depset([clang_bin], transitive = [cc_toolchain.all_files])
186
187    # libclang should only have 1 output file
188    libclang_dir = _get_libs_for_static_executable(libclang).to_list()[0].dirname
189
190    env = {
191        "CLANG_PATH": clang_bin.path,
192        "LIBCLANG_PATH": libclang_dir,
193        "RUST_BACKTRACE": "1",
194    }
195
196    args = ctx.actions.args()
197
198    # Configure Bindgen Arguments
199    args.add_all(ctx.attr.bindgen_flags)
200    args.add(header)
201    args.add("--output", output)
202
203    # Vanilla usage of bindgen produces formatted output, here we do the same if we have `rustfmt` in our toolchain.
204    rustfmt_toolchain = ctx.toolchains[Label("//rust/rustfmt:toolchain_type")]
205    if toolchain.default_rustfmt:
206        # Bindgen is able to find rustfmt using the RUSTFMT environment variable
207        env.update({"RUSTFMT": rustfmt_toolchain.rustfmt.path})
208        tools = depset(transitive = [tools, rustfmt_toolchain.all_files])
209    else:
210        args.add("--no-rustfmt-bindings")
211
212    # Configure Clang Arguments
213    args.add("--")
214
215    compile_variables = cc_common.create_compile_variables(
216        cc_toolchain = cc_toolchain,
217        feature_configuration = feature_configuration,
218        include_directories = cc_lib[CcInfo].compilation_context.includes,
219        quote_include_directories = cc_lib[CcInfo].compilation_context.quote_includes,
220        system_include_directories = cc_lib[CcInfo].compilation_context.system_includes,
221        user_compile_flags = ctx.attr.clang_flags,
222    )
223    compile_flags = cc_common.get_memory_inefficient_command_line(
224        feature_configuration = feature_configuration,
225        action_name = CPP_COMPILE_ACTION_NAME,
226        variables = compile_variables,
227    )
228
229    # Bindgen forcibly uses clang.
230    # It's possible that the selected cc_toolchain isn't clang, and may specify flags which clang doesn't recognise.
231    # Ideally we could depend on a more specific toolchain, requesting one which is specifically clang via some constraint.
232    # Unfortunately, we can't currently rely on this, so instead we filter only to flags we know clang supports.
233    # We can add extra flags here as needed.
234    flags_known_to_clang = ("-I", "-iquote", "-isystem", "--sysroot", "--gcc-toolchain")
235    open_arg = False
236    for arg in compile_flags:
237        if open_arg:
238            args.add(arg)
239            open_arg = False
240            continue
241
242        # The cc_toolchain merged these flags into its returned flags - don't strip these out.
243        if arg in ctx.attr.clang_flags:
244            args.add(arg)
245            continue
246
247        if not arg.startswith(flags_known_to_clang):
248            continue
249
250        args.add(arg)
251
252        if arg in flags_known_to_clang:
253            open_arg = True
254            continue
255
256    _, _, linker_env = get_linker_and_args(ctx, ctx.attr, "bin", cc_toolchain, feature_configuration, None)
257    env.update(**linker_env)
258
259    # Set the dynamic linker search path so that clang uses the libstdcxx from the toolchain.
260    # DYLD_LIBRARY_PATH is LD_LIBRARY_PATH on macOS.
261    if libstdcxx:
262        env["LD_LIBRARY_PATH"] = ":".join([f.dirname for f in _get_libs_for_static_executable(libstdcxx).to_list()])
263        env["DYLD_LIBRARY_PATH"] = env["LD_LIBRARY_PATH"]
264
265    ctx.actions.run(
266        executable = bindgen_bin,
267        inputs = depset(
268            [header],
269            transitive = [
270                cc_lib[CcInfo].compilation_context.headers,
271                _get_libs_for_static_executable(libclang),
272            ] + ([
273                _get_libs_for_static_executable(libstdcxx),
274            ] if libstdcxx else []),
275        ),
276        outputs = [output],
277        mnemonic = "RustBindgen",
278        progress_message = "Generating bindings for {}..".format(header.path),
279        env = env,
280        arguments = [args],
281        tools = tools,
282    )
283
284    if ctx.attr.leak_symbols:
285        # buildifier: disable=print
286        print("WARN: rust_bindgen.leak_symbols is set to True for {} - please file an issue at https://github.com/bazelbuild/rules_rust/issues explaining why this was necessary, as this support will be removed soon.".format(ctx.label))
287        providers = [cc_common.merge_cc_infos(
288            direct_cc_infos = [cc_lib[CcInfo]],
289        )]
290    else:
291        providers = [
292            _generate_cc_link_build_info(ctx, cc_lib),
293            # As in https://github.com/bazelbuild/rules_rust/pull/2361, we want
294            # to link cc_lib to the direct parent (rlib) using `-lstatic=<cc_lib>` rustc flag
295            # Hence, we do not need to provide the whole CcInfo of cc_lib because
296            # it will cause the downstream binary to link the cc_lib again
297            # (same effect as setting `leak_symbols` attribute above)
298            # The CcInfo here only contains the custom link flags (i.e. linkopts attribute)
299            # specified by users in cc_lib
300            CcInfo(
301                linking_context = cc_common.create_linking_context(
302                    linker_inputs = depset([cc_common.create_linker_input(
303                        owner = ctx.label,
304                        user_link_flags = _get_user_link_flags(cc_lib),
305                    )]),
306                ),
307            ),
308        ]
309
310    return providers + [
311        OutputGroupInfo(
312            bindgen_bindings = depset([output]),
313        ),
314    ]
315
316rust_bindgen = rule(
317    doc = "Generates a rust source file from a cc_library and a header.",
318    implementation = _rust_bindgen_impl,
319    attrs = {
320        "bindgen_flags": attr.string_list(
321            doc = "Flags to pass directly to the bindgen executable. See https://rust-lang.github.io/rust-bindgen/ for details.",
322        ),
323        "cc_lib": attr.label(
324            doc = "The cc_library that contains the `.h` file. This is used to find the transitive includes.",
325            providers = [CcInfo],
326            mandatory = True,
327        ),
328        "clang_flags": attr.string_list(
329            doc = "Flags to pass directly to the clang executable.",
330        ),
331        "header": attr.label(
332            doc = "The `.h` file to generate bindings for.",
333            allow_single_file = True,
334            mandatory = True,
335        ),
336        "leak_symbols": attr.bool(
337            doc = (
338                "If True, `cc_lib` will be exposed and linked into all downstream consumers of the target vs. the " +
339                "`rust_library` directly consuming it."
340            ),
341            default = False,
342        ),
343        "_cc_toolchain": attr.label(
344            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
345        ),
346        "_process_wrapper": attr.label(
347            default = Label("//util/process_wrapper"),
348            executable = True,
349            allow_single_file = True,
350            cfg = "exec",
351        ),
352    },
353    outputs = {"out": "%{name}.rs"},
354    fragments = ["cpp"],
355    toolchains = [
356        str(Label("//bindgen:toolchain_type")),
357        str(Label("//rust:toolchain_type")),
358        str(Label("//rust/rustfmt:toolchain_type")),
359        "@bazel_tools//tools/cpp:toolchain_type",
360    ],
361)
362
363def _rust_bindgen_toolchain_impl(ctx):
364    return platform_common.ToolchainInfo(
365        bindgen = ctx.executable.bindgen,
366        clang = ctx.executable.clang,
367        libclang = ctx.attr.libclang,
368        libstdcxx = ctx.attr.libstdcxx,
369        default_rustfmt = ctx.attr.default_rustfmt,
370    )
371
372rust_bindgen_toolchain = rule(
373    _rust_bindgen_toolchain_impl,
374    doc = """\
375The tools required for the `rust_bindgen` rule.
376
377This rule depends on the [`bindgen`](https://crates.io/crates/bindgen) binary crate, and it
378in turn depends on both a clang binary and the clang library. To obtain these dependencies,
379`rust_bindgen_dependencies` imports bindgen and its dependencies.
380
381```python
382load("@rules_rust//bindgen:defs.bzl", "rust_bindgen_toolchain")
383
384rust_bindgen_toolchain(
385    name = "bindgen_toolchain_impl",
386    bindgen = "//my/rust:bindgen",
387    clang = "//my/clang:clang",
388    libclang = "//my/clang:libclang.so",
389    libstdcxx = "//my/cpp:libstdc++",
390)
391
392toolchain(
393    name = "bindgen_toolchain",
394    toolchain = "bindgen_toolchain_impl",
395    toolchain_type = "@rules_rust//bindgen:toolchain_type",
396)
397```
398
399This toolchain will then need to be registered in the current `WORKSPACE`.
400For additional information, see the [Bazel toolchains documentation](https://docs.bazel.build/versions/master/toolchains.html).
401""",
402    attrs = {
403        "bindgen": attr.label(
404            doc = "The label of a `bindgen` executable.",
405            executable = True,
406            cfg = "exec",
407        ),
408        "clang": attr.label(
409            doc = "The label of a `clang` executable.",
410            executable = True,
411            cfg = "exec",
412            allow_files = True,
413        ),
414        "default_rustfmt": attr.bool(
415            doc = "If set, `rust_bindgen` targets will always format generated sources with `rustfmt`.",
416            mandatory = False,
417            default = True,
418        ),
419        "libclang": attr.label(
420            doc = "A cc_library that provides bindgen's runtime dependency on libclang.",
421            cfg = "exec",
422            providers = [CcInfo],
423            allow_files = True,
424        ),
425        "libstdcxx": attr.label(
426            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.",
427            cfg = "exec",
428            providers = [CcInfo],
429            mandatory = False,
430            allow_files = True,
431        ),
432    },
433)
434