| # 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. |
| |
| """Rust Bindgen rules""" |
| |
| load( |
| "@bazel_tools//tools/build_defs/cc:action_names.bzl", |
| "CPP_COMPILE_ACTION_NAME", |
| ) |
| load("@rules_cc//cc:defs.bzl", "CcInfo", "cc_library") |
| load("//rust:defs.bzl", "rust_library") |
| load("//rust:rust_common.bzl", "BuildInfo") |
| |
| # buildifier: disable=bzl-visibility |
| load("//rust/private:rustc.bzl", "get_linker_and_args") |
| |
| # buildifier: disable=bzl-visibility |
| load("//rust/private:utils.bzl", "find_cc_toolchain", "get_lib_name_default", "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, use_pic = False) for li in linker_inputs for lib in li.libraries]) |
| |
| def rust_bindgen_library( |
| name, |
| header, |
| cc_lib, |
| bindgen_flags = None, |
| bindgen_features = None, |
| clang_flags = None, |
| wrap_static_fns = False, |
| **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. |
| bindgen_features (list, optional): The `features` attribute for the `rust_bindgen` target. |
| clang_flags (list, optional): Flags to pass directly to the clang executable. |
| 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", |
| **kwargs: Arguments to forward to the underlying `rust_library` rule. |
| """ |
| |
| tags = kwargs.get("tags") or [] |
| if "tags" in kwargs: |
| kwargs.pop("tags") |
| |
| sub_tags = tags + ([] if "manual" in tags else ["manual"]) |
| |
| bindgen_kwargs = {} |
| for shared in ( |
| "target_compatible_with", |
| "exec_compatible_with", |
| ): |
| if shared in kwargs: |
| bindgen_kwargs.update({shared: kwargs[shared]}) |
| |
| rust_bindgen( |
| name = name + "__bindgen", |
| header = header, |
| cc_lib = cc_lib, |
| bindgen_flags = bindgen_flags or [], |
| features = bindgen_features, |
| clang_flags = clang_flags or [], |
| tags = sub_tags, |
| wrap_static_fns = wrap_static_fns, |
| **bindgen_kwargs |
| ) |
| |
| tags = depset(tags + ["__bindgen", "no-clippy", "no-rustfmt"]).to_list() |
| |
| deps = kwargs.get("deps") or [] |
| if "deps" in kwargs: |
| kwargs.pop("deps") |
| |
| if wrap_static_fns: |
| native.filegroup( |
| name = name + "__bindgen_c_thunks", |
| srcs = [":" + name + "__bindgen"], |
| output_group = "bindgen_c_thunks", |
| ) |
| |
| cc_library( |
| name = name + "__bindgen_c_thunks_library", |
| srcs = [":" + name + "__bindgen_c_thunks"], |
| deps = [cc_lib], |
| ) |
| |
| rust_library( |
| name = name, |
| srcs = [name + "__bindgen.rs"], |
| deps = deps + [":" + name + "__bindgen"] + ([":" + name + "__bindgen_c_thunks_library"] if wrap_static_fns else []), |
| tags = tags, |
| **kwargs |
| ) |
| |
| def _get_user_link_flags(cc_lib): |
| linker_flags = [] |
| |
| for linker_input in cc_lib[CcInfo].linking_context.linker_inputs.to_list(): |
| linker_flags.extend(linker_input.user_link_flags) |
| |
| return linker_flags |
| |
| def _generate_cc_link_build_info(ctx, cc_lib): |
| """Produce the eqivilant cargo_build_script providers for use in linking the library. |
| |
| Args: |
| ctx (ctx): The rule's context object |
| cc_lib (Target): The `rust_bindgen.cc_lib` target. |
| |
| Returns: |
| The `BuildInfo` provider. |
| """ |
| compile_data = [] |
| |
| rustc_flags = [] |
| linker_search_paths = [] |
| |
| for linker_input in cc_lib[CcInfo].linking_context.linker_inputs.to_list(): |
| for lib in linker_input.libraries: |
| if lib.static_library: |
| rustc_flags.append("-lstatic={}".format(get_lib_name_default(lib.static_library))) |
| linker_search_paths.append(lib.static_library.dirname) |
| compile_data.append(lib.static_library) |
| elif lib.pic_static_library: |
| rustc_flags.append("-lstatic={}".format(get_lib_name_default(lib.pic_static_library))) |
| linker_search_paths.append(lib.pic_static_library.dirname) |
| compile_data.append(lib.pic_static_library) |
| |
| if not compile_data: |
| fail("No static libraries found in {}".format( |
| cc_lib.label, |
| )) |
| |
| rustc_flags_file = ctx.actions.declare_file("{}.rustc_flags".format(ctx.label.name)) |
| ctx.actions.write( |
| output = rustc_flags_file, |
| content = "\n".join(rustc_flags), |
| ) |
| |
| link_search_paths = ctx.actions.declare_file("{}.link_search_paths".format(ctx.label.name)) |
| ctx.actions.write( |
| output = link_search_paths, |
| content = "\n".join([ |
| "-Lnative=${{pwd}}/{}".format(path) |
| for path in depset(linker_search_paths).to_list() |
| ]), |
| ) |
| |
| return BuildInfo( |
| compile_data = depset(compile_data), |
| dep_env = None, |
| flags = rustc_flags_file, |
| # linker_flags is provided via CcInfo |
| linker_flags = None, |
| link_search_paths = link_search_paths, |
| out_dir = None, |
| rustc_env = None, |
| ) |
| |
| def _rust_bindgen_impl(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:toolchain_type")] |
| bindgen_bin = toolchain.bindgen |
| clang_bin = toolchain.clang |
| libclang = toolchain.libclang |
| libstdcxx = toolchain.libstdcxx |
| |
| output = ctx.outputs.out |
| |
| cc_toolchain, feature_configuration = find_cc_toolchain(ctx = ctx) |
| |
| tools = depset(([clang_bin] if clang_bin else []), transitive = [cc_toolchain.all_files]) |
| |
| # libclang should only have 1 output file |
| libclang_dir = _get_libs_for_static_executable(libclang).to_list()[0].dirname |
| |
| env = { |
| "LIBCLANG_PATH": libclang_dir, |
| "RUST_BACKTRACE": "1", |
| } |
| if clang_bin: |
| env["CLANG_PATH"] = clang_bin.path |
| |
| args = ctx.actions.args() |
| |
| # Configure Bindgen Arguments |
| args.add_all(ctx.attr.bindgen_flags) |
| args.add(header) |
| args.add("--output", output) |
| |
| wrap_static_fns = getattr(ctx.attr, "wrap_static_fns", False) |
| |
| c_output = None |
| if wrap_static_fns: |
| if "--wrap-static-fns" in ctx.attr.bindgen_flags: |
| fail("Do not pass `--wrap-static-fns` to `bindgen_flags, it's added automatically." + |
| "The generated C file is accesible in the `bindgen_c_thunks` output group.") |
| c_output = ctx.actions.declare_file(ctx.label.name + ".bindgen_c_thunks.c") |
| args.add("--experimental") |
| args.add("--wrap-static-fns") |
| args.add("--wrap-static-fns-path") |
| args.add(c_output.path) |
| |
| # Vanilla usage of bindgen produces formatted output, here we do the same if we have `rustfmt` in our toolchain. |
| rustfmt_toolchain = ctx.toolchains[Label("//rust/rustfmt:toolchain_type")] |
| if rustfmt_toolchain and toolchain.default_rustfmt: |
| # Bindgen is able to find rustfmt using the RUSTFMT environment variable |
| env.update({"RUSTFMT": rustfmt_toolchain.rustfmt.path}) |
| tools = depset(transitive = [tools, rustfmt_toolchain.all_files]) |
| else: |
| args.add("--no-rustfmt-bindings") |
| |
| # Configure Clang Arguments |
| args.add("--") |
| |
| compile_variables = cc_common.create_compile_variables( |
| cc_toolchain = cc_toolchain, |
| feature_configuration = feature_configuration, |
| include_directories = cc_lib[CcInfo].compilation_context.includes, |
| quote_include_directories = cc_lib[CcInfo].compilation_context.quote_includes, |
| system_include_directories = depset( |
| transitive = [cc_lib[CcInfo].compilation_context.system_includes], |
| direct = cc_toolchain.built_in_include_directories, |
| ), |
| user_compile_flags = ctx.attr.clang_flags, |
| ) |
| compile_flags = cc_common.get_memory_inefficient_command_line( |
| feature_configuration = feature_configuration, |
| action_name = CPP_COMPILE_ACTION_NAME, |
| variables = compile_variables, |
| ) |
| |
| # Bindgen forcibly uses clang. |
| # It's possible that the selected cc_toolchain isn't clang, and may specify flags which clang doesn't recognise. |
| # Ideally we could depend on a more specific toolchain, requesting one which is specifically clang via some constraint. |
| # Unfortunately, we can't currently rely on this, so instead we filter only to flags we know clang supports. |
| # We can add extra flags here as needed. |
| flags_known_to_clang = ("-I", "-iquote", "-isystem", "--sysroot", "--gcc-toolchain") |
| open_arg = False |
| for arg in compile_flags: |
| if open_arg: |
| args.add(arg) |
| open_arg = False |
| continue |
| |
| # The cc_toolchain merged these flags into its returned flags - don't strip these out. |
| if arg in ctx.attr.clang_flags: |
| args.add(arg) |
| continue |
| |
| if not arg.startswith(flags_known_to_clang): |
| continue |
| |
| args.add(arg) |
| |
| if arg in flags_known_to_clang: |
| open_arg = True |
| continue |
| |
| _, _, linker_env = get_linker_and_args(ctx, ctx.attr, "bin", cc_toolchain, feature_configuration, None) |
| env.update(**linker_env) |
| |
| # Set the dynamic linker search path so that clang uses the libstdcxx from the toolchain. |
| # DYLD_LIBRARY_PATH is LD_LIBRARY_PATH on macOS. |
| if libstdcxx: |
| env["LD_LIBRARY_PATH"] = ":".join([f.dirname for f in _get_libs_for_static_executable(libstdcxx).to_list()]) |
| env["DYLD_LIBRARY_PATH"] = env["LD_LIBRARY_PATH"] |
| |
| 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 = [output] + ([c_output] if wrap_static_fns else []), |
| mnemonic = "RustBindgen", |
| progress_message = "Generating bindings for {}..".format(header.path), |
| env = env, |
| arguments = [args], |
| tools = tools, |
| # ctx.actions.run now require (through a buildifier check) that we |
| # specify this |
| toolchain = None, |
| ) |
| |
| return [ |
| _generate_cc_link_build_info(ctx, cc_lib), |
| # As in https://github.com/bazelbuild/rules_rust/pull/2361, we want |
| # to link cc_lib to the direct parent (rlib) using `-lstatic=<cc_lib>` |
| # rustc flag. Hence, we do not need to provide the whole CcInfo of |
| # cc_lib because it will cause the downstream binary to link the cc_lib |
| # again. The CcInfo here only contains the custom link flags (i.e. |
| # linkopts attribute) specified by users in cc_lib. |
| CcInfo( |
| linking_context = cc_common.create_linking_context( |
| linker_inputs = depset([cc_common.create_linker_input( |
| owner = ctx.label, |
| user_link_flags = _get_user_link_flags(cc_lib), |
| )]), |
| ), |
| ), |
| OutputGroupInfo( |
| bindgen_bindings = depset([output]), |
| bindgen_c_thunks = depset(([c_output] if wrap_static_fns else [])), |
| ), |
| ] |
| |
| 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], |
| mandatory = True, |
| ), |
| "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, |
| mandatory = True, |
| ), |
| "wrap_static_fns": attr.bool( |
| 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).", |
| default = False, |
| ), |
| "_cc_toolchain": attr.label( |
| default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| ), |
| "_process_wrapper": attr.label( |
| default = Label("//util/process_wrapper"), |
| executable = True, |
| allow_single_file = True, |
| cfg = "exec", |
| ), |
| }, |
| outputs = {"out": "%{name}.rs"}, |
| fragments = ["cpp"], |
| toolchains = [ |
| config_common.toolchain_type("//bindgen:toolchain_type"), |
| config_common.toolchain_type("//rust:toolchain_type"), |
| config_common.toolchain_type("//rust/rustfmt:toolchain_type", mandatory = False), |
| config_common.toolchain_type("@bazel_tools//tools/cpp:toolchain_type"), |
| ], |
| ) |
| |
| 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, |
| default_rustfmt = ctx.attr.default_rustfmt, |
| ) |
| |
| rust_bindgen_toolchain = rule( |
| _rust_bindgen_toolchain_impl, |
| doc = """\ |
| The tools required for the `rust_bindgen` rule. |
| |
| This rule depends on the [`bindgen`](https://crates.io/crates/bindgen) binary crate, and it |
| in turn depends on both a clang binary and the clang library. To obtain these dependencies, |
| `rust_bindgen_dependencies` imports bindgen and its dependencies. |
| |
| ```python |
| load("@rules_rust//bindgen:defs.bzl", "rust_bindgen_toolchain") |
| |
| rust_bindgen_toolchain( |
| name = "bindgen_toolchain_impl", |
| bindgen = "//my/rust:bindgen", |
| clang = "//my/clang:clang", |
| libclang = "//my/clang:libclang.so", |
| libstdcxx = "//my/cpp:libstdc++", |
| ) |
| |
| toolchain( |
| name = "bindgen_toolchain", |
| toolchain = "bindgen_toolchain_impl", |
| toolchain_type = "@rules_rust//bindgen:toolchain_type", |
| ) |
| ``` |
| |
| This toolchain will then need to be registered in the current `WORKSPACE`. |
| For additional information, see the [Bazel toolchains documentation](https://docs.bazel.build/versions/master/toolchains.html). |
| """, |
| 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", |
| allow_files = True, |
| ), |
| "default_rustfmt": attr.bool( |
| doc = "If set, `rust_bindgen` targets will always format generated sources with `rustfmt`.", |
| mandatory = False, |
| default = True, |
| ), |
| "libclang": attr.label( |
| doc = "A cc_library that provides bindgen's runtime dependency on libclang.", |
| cfg = "exec", |
| providers = [CcInfo], |
| allow_files = True, |
| ), |
| "libstdcxx": attr.label( |
| 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.", |
| cfg = "exec", |
| providers = [CcInfo], |
| mandatory = False, |
| allow_files = True, |
| ), |
| }, |
| ) |