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