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