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