1# Copyright 2015 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"""Utility functions not specific to the rust toolchain.""" 16 17load("@bazel_skylib//lib:paths.bzl", "paths") 18load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain") 19load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo", "RustcOutputDiagnosticsInfo") 20 21UNSUPPORTED_FEATURES = [ 22 "thin_lto", 23 "module_maps", 24 "use_header_modules", 25 "fdo_instrument", 26 "fdo_optimize", 27 # This feature is unsupported by definition. The authors of C++ toolchain 28 # configuration can place any linker flags that should not be applied when 29 # linking Rust targets in a feature with this name. 30 "rules_rust_unsupported_feature", 31] 32 33def find_toolchain(ctx): 34 """Finds the first rust toolchain that is configured. 35 36 Args: 37 ctx (ctx): The ctx object for the current target. 38 39 Returns: 40 rust_toolchain: A Rust toolchain context. 41 """ 42 return ctx.toolchains[Label("//rust:toolchain_type")] 43 44def find_cc_toolchain(ctx): 45 """Extracts a CcToolchain from the current target's context 46 47 Args: 48 ctx (ctx): The current target's rule context object 49 50 Returns: 51 tuple: A tuple of (CcToolchain, FeatureConfiguration) 52 """ 53 cc_toolchain = find_rules_cc_toolchain(ctx) 54 55 feature_configuration = cc_common.configure_features( 56 ctx = ctx, 57 cc_toolchain = cc_toolchain, 58 requested_features = ctx.features, 59 unsupported_features = UNSUPPORTED_FEATURES + ctx.disabled_features, 60 ) 61 return cc_toolchain, feature_configuration 62 63# TODO: Replace with bazel-skylib's `path.dirname`. This requires addressing some 64# dependency issues or generating docs will break. 65def relativize(path, start): 66 """Returns the relative path from start to path. 67 68 Args: 69 path (str): The path to relativize. 70 start (str): The ancestor path against which to relativize. 71 72 Returns: 73 str: The portion of `path` that is relative to `start`. 74 """ 75 src_parts = _path_parts(start) 76 dest_parts = _path_parts(path) 77 n = 0 78 for src_part, dest_part in zip(src_parts, dest_parts): 79 if src_part != dest_part: 80 break 81 n += 1 82 83 relative_path = "" 84 for _ in range(n, len(src_parts)): 85 relative_path += "../" 86 relative_path += "/".join(dest_parts[n:]) 87 88 return relative_path 89 90def _path_parts(path): 91 """Takes a path and returns a list of its parts with all "." elements removed. 92 93 The main use case of this function is if one of the inputs to relativize() 94 is a relative path, such as "./foo". 95 96 Args: 97 path (str): A string representing a unix path 98 99 Returns: 100 list: A list containing the path parts with all "." elements removed. 101 """ 102 path_parts = path.split("/") 103 return [part for part in path_parts if part != "."] 104 105def get_lib_name_default(lib): 106 """Returns the name of a library artifact. 107 108 Args: 109 lib (File): A library file 110 111 Returns: 112 str: The name of the library 113 """ 114 # On macos and windows, dynamic/static libraries always end with the 115 # extension and potential versions will be before the extension, and should 116 # be part of the library name. 117 # On linux, the version usually comes after the extension. 118 # So regardless of the platform we want to find the extension and make 119 # everything left to it the library name. 120 121 # Search for the extension - starting from the right - by removing any 122 # trailing digit. 123 comps = lib.basename.split(".") 124 for comp in reversed(comps): 125 if comp.isdigit(): 126 comps.pop() 127 else: 128 break 129 130 # The library name is now everything minus the extension. 131 libname = ".".join(comps[:-1]) 132 133 if libname.startswith("lib"): 134 return libname[3:] 135 else: 136 return libname 137 138# TODO: Could we remove this function in favor of a "windows" parameter in the 139# above function? It looks like currently lambdas cannot accept local parameters 140# so the following doesn't work: 141# args.add_all( 142# cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration), 143# map_each = lambda x: get_lib_name(x, for_windows = toolchain.target_os.startswith("windows)), 144# format_each = "-ldylib=%s", 145# ) 146def get_lib_name_for_windows(lib): 147 """Returns the name of a library artifact for Windows builds. 148 149 Args: 150 lib (File): A library file 151 152 Returns: 153 str: The name of the library 154 """ 155 # On macos and windows, dynamic/static libraries always end with the 156 # extension and potential versions will be before the extension, and should 157 # be part of the library name. 158 # On linux, the version usually comes after the extension. 159 # So regardless of the platform we want to find the extension and make 160 # everything left to it the library name. 161 162 # Search for the extension - starting from the right - by removing any 163 # trailing digit. 164 comps = lib.basename.split(".") 165 for comp in reversed(comps): 166 if comp.isdigit(): 167 comps.pop() 168 else: 169 break 170 171 # The library name is now everything minus the extension. 172 libname = ".".join(comps[:-1]) 173 174 return libname 175 176def abs(value): 177 """Returns the absolute value of a number. 178 179 Args: 180 value (int): A number. 181 182 Returns: 183 int: The absolute value of the number. 184 """ 185 if value < 0: 186 return -value 187 return value 188 189def determine_output_hash(crate_root, label): 190 """Generates a hash of the crate root file's path. 191 192 Args: 193 crate_root (File): The crate's root file (typically `lib.rs`). 194 label (Label): The label of the target. 195 196 Returns: 197 str: A string representation of the hash. 198 """ 199 200 # Take the absolute value of hash() since it could be negative. 201 h = abs(hash(crate_root.path) + hash(repr(label))) 202 return repr(h) 203 204def get_preferred_artifact(library_to_link, use_pic): 205 """Get the first available library to link from a LibraryToLink object. 206 207 Args: 208 library_to_link (LibraryToLink): See the followg links for additional details: 209 https://docs.bazel.build/versions/master/skylark/lib/LibraryToLink.html 210 use_pic: If set, prefers pic_static_library over static_library. 211 212 Returns: 213 File: Returns the first valid library type (only one is expected) 214 """ 215 if use_pic: 216 # Order consistent with https://github.com/bazelbuild/bazel/blob/815dfdabb7df31d4e99b6fc7616ff8e2f9385d98/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingContext.java#L437. 217 return ( 218 library_to_link.pic_static_library or 219 library_to_link.static_library or 220 library_to_link.interface_library or 221 library_to_link.dynamic_library 222 ) 223 else: 224 return ( 225 library_to_link.static_library or 226 library_to_link.pic_static_library or 227 library_to_link.interface_library or 228 library_to_link.dynamic_library 229 ) 230 231# The normal ctx.expand_location, but with an additional deduplication step. 232# We do this to work around a potential crash, see 233# https://github.com/bazelbuild/bazel/issues/16664 234def dedup_expand_location(ctx, input, targets = []): 235 return ctx.expand_location(input, _deduplicate(targets)) 236 237def _deduplicate(xs): 238 return {x: True for x in xs}.keys() 239 240def concat(xss): 241 return [x for xs in xss for x in xs] 242 243def _expand_location_for_build_script_runner(ctx, env, data): 244 """A trivial helper for `expand_dict_value_locations` and `expand_list_element_locations` 245 246 Args: 247 ctx (ctx): The rule's context object 248 env (str): The value possibly containing location macros to expand. 249 data (sequence of Targets): See one of the parent functions. 250 251 Returns: 252 string: The location-macro expanded version of the string. 253 """ 254 for directive in ("$(execpath ", "$(location "): 255 if directive in env: 256 # build script runner will expand pwd to execroot for us 257 env = env.replace(directive, "$${pwd}/" + directive) 258 return ctx.expand_make_variables( 259 env, 260 dedup_expand_location(ctx, env, data), 261 {}, 262 ) 263 264def expand_dict_value_locations(ctx, env, data): 265 """Performs location-macro expansion on string values. 266 267 $(execroot ...) and $(location ...) are prefixed with ${pwd}, 268 which process_wrapper and build_script_runner will expand at run time 269 to the absolute path. This is necessary because include_str!() is relative 270 to the currently compiled file, and build scripts run relative to the 271 manifest dir, so we can not use execroot-relative paths. 272 273 $(rootpath ...) is unmodified, and is useful for passing in paths via 274 rustc_env that are encoded in the binary with env!(), but utilized at 275 runtime, such as in tests. The absolute paths are not usable in this case, 276 as compilation happens in a separate sandbox folder, so when it comes time 277 to read the file at runtime, the path is no longer valid. 278 279 For detailed documentation, see: 280 - [`expand_location`](https://bazel.build/rules/lib/ctx#expand_location) 281 - [`expand_make_variables`](https://bazel.build/rules/lib/ctx#expand_make_variables) 282 283 Args: 284 ctx (ctx): The rule's context object 285 env (dict): A dict whose values we iterate over 286 data (sequence of Targets): The targets which may be referenced by 287 location macros. This is expected to be the `data` attribute of 288 the target, though may have other targets or attributes mixed in. 289 290 Returns: 291 dict: A dict of environment variables with expanded location macros 292 """ 293 return dict([(k, _expand_location_for_build_script_runner(ctx, v, data)) for (k, v) in env.items()]) 294 295def expand_list_element_locations(ctx, args, data): 296 """Performs location-macro expansion on a list of string values. 297 298 $(execroot ...) and $(location ...) are prefixed with ${pwd}, 299 which process_wrapper and build_script_runner will expand at run time 300 to the absolute path. 301 302 For detailed documentation, see: 303 - [`expand_location`](https://bazel.build/rules/lib/ctx#expand_location) 304 - [`expand_make_variables`](https://bazel.build/rules/lib/ctx#expand_make_variables) 305 306 Args: 307 ctx (ctx): The rule's context object 308 args (list): A list we iterate over 309 data (sequence of Targets): The targets which may be referenced by 310 location macros. This is expected to be the `data` attribute of 311 the target, though may have other targets or attributes mixed in. 312 313 Returns: 314 list: A list of arguments with expanded location macros 315 """ 316 return [_expand_location_for_build_script_runner(ctx, arg, data) for arg in args] 317 318def name_to_crate_name(name): 319 """Converts a build target's name into the name of its associated crate. 320 321 Crate names cannot contain certain characters, such as -, which are allowed 322 in build target names. All illegal characters will be converted to 323 underscores. 324 325 This is a similar conversion as that which cargo does, taking a 326 `Cargo.toml`'s `package.name` and canonicalizing it 327 328 Note that targets can specify the `crate_name` attribute to customize their 329 crate name; in situations where this is important, use the 330 compute_crate_name() function instead. 331 332 Args: 333 name (str): The name of the target. 334 335 Returns: 336 str: The name of the crate for this target. 337 """ 338 for illegal in ("-", "/"): 339 name = name.replace(illegal, "_") 340 return name 341 342def _invalid_chars_in_crate_name(name): 343 """Returns any invalid chars in the given crate name. 344 345 Args: 346 name (str): Name to test. 347 348 Returns: 349 list: List of invalid characters in the crate name. 350 """ 351 352 return dict([(c, ()) for c in name.elems() if not (c.isalnum() or c == "_")]).keys() 353 354def compute_crate_name(workspace_name, label, toolchain, name_override = None): 355 """Returns the crate name to use for the current target. 356 357 Args: 358 workspace_name (string): The current workspace name. 359 label (struct): The label of the current target. 360 toolchain (struct): The toolchain in use for the target. 361 name_override (String): An optional name to use (as an override of label.name). 362 363 Returns: 364 str: The crate name to use for this target. 365 """ 366 if name_override: 367 invalid_chars = _invalid_chars_in_crate_name(name_override) 368 if invalid_chars: 369 fail("Crate name '{}' contains invalid character(s): {}".format( 370 name_override, 371 " ".join(invalid_chars), 372 )) 373 return name_override 374 375 if (toolchain and label and toolchain._rename_first_party_crates and 376 should_encode_label_in_crate_name(workspace_name, label, toolchain._third_party_dir)): 377 crate_name = encode_label_as_crate_name(label.package, label.name) 378 else: 379 crate_name = name_to_crate_name(label.name) 380 381 invalid_chars = _invalid_chars_in_crate_name(crate_name) 382 if invalid_chars: 383 fail( 384 "Crate name '{}' ".format(crate_name) + 385 "derived from Bazel target name '{}' ".format(label.name) + 386 "contains invalid character(s): {}\n".format(" ".join(invalid_chars)) + 387 "Consider adding a crate_name attribute to set a valid crate name", 388 ) 389 return crate_name 390 391def dedent(doc_string): 392 """Remove any common leading whitespace from every line in text. 393 394 This functionality is similar to python's `textwrap.dedent` functionality 395 https://docs.python.org/3/library/textwrap.html#textwrap.dedent 396 397 Args: 398 doc_string (str): A docstring style string 399 400 Returns: 401 str: A string optimized for stardoc rendering 402 """ 403 lines = doc_string.splitlines() 404 if not lines: 405 return doc_string 406 407 # If the first line is empty, use the second line 408 first_line = lines[0] 409 if not first_line: 410 first_line = lines[1] 411 412 # Detect how much space prepends the first line and subtract that from all lines 413 space_count = len(first_line) - len(first_line.lstrip()) 414 415 # If there are no leading spaces, do not alter the docstring 416 if space_count == 0: 417 return doc_string 418 else: 419 # Remove the leading block of spaces from the current line 420 block = " " * space_count 421 return "\n".join([line.replace(block, "", 1).rstrip() for line in lines]) 422 423def make_static_lib_symlink(actions, rlib_file): 424 """Add a .a symlink to an .rlib file. 425 426 The name of the symlink is derived from the <name> of the <name>.rlib file as follows: 427 * `<name>.a`, if <name> starts with `lib` 428 * `lib<name>.a`, otherwise. 429 430 For example, the name of the symlink for 431 * `libcratea.rlib` is `libcratea.a` 432 * `crateb.rlib` is `libcrateb.a`. 433 434 Args: 435 actions (actions): The rule's context actions object. 436 rlib_file (File): The file to symlink, which must end in .rlib. 437 438 Returns: 439 The symlink's File. 440 """ 441 if not rlib_file.basename.endswith(".rlib"): 442 fail("file is not an .rlib: ", rlib_file.basename) 443 basename = rlib_file.basename[:-5] 444 if not basename.startswith("lib"): 445 basename = "lib" + basename 446 dot_a = actions.declare_file(basename + ".a", sibling = rlib_file) 447 actions.symlink(output = dot_a, target_file = rlib_file) 448 return dot_a 449 450def is_exec_configuration(ctx): 451 """Determine if a context is building for the exec configuration. 452 453 This is helpful when processing command line flags that should apply 454 to the target configuration but not the exec configuration. 455 456 Args: 457 ctx (ctx): The ctx object for the current target. 458 459 Returns: 460 True if the exec configuration is detected, False otherwise. 461 """ 462 463 # TODO(djmarcin): Is there any better way to determine cfg=exec? 464 return ctx.genfiles_dir.path.find("-exec") != -1 465 466def transform_deps(deps): 467 """Transforms a [Target] into [DepVariantInfo]. 468 469 This helper function is used to transform ctx.attr.deps and ctx.attr.proc_macro_deps into 470 [DepVariantInfo]. 471 472 Args: 473 deps (list of Targets): Dependencies coming from ctx.attr.deps or ctx.attr.proc_macro_deps 474 475 Returns: 476 list of DepVariantInfos. 477 """ 478 return [DepVariantInfo( 479 crate_info = dep[CrateInfo] if CrateInfo in dep else None, 480 dep_info = dep[DepInfo] if DepInfo in dep else None, 481 build_info = dep[BuildInfo] if BuildInfo in dep else None, 482 cc_info = dep[CcInfo] if CcInfo in dep else None, 483 crate_group_info = dep[CrateGroupInfo] if CrateGroupInfo in dep else None, 484 ) for dep in deps] 485 486def get_import_macro_deps(ctx): 487 """Returns a list of targets to be added to proc_macro_deps. 488 489 Args: 490 ctx (struct): the ctx of the current target. 491 492 Returns: 493 list of Targets. Either empty (if the fake import macro implementation 494 is being used), or a singleton list with the real implementation. 495 """ 496 if ctx.attr._import_macro_dep.label.name == "fake_import_macro_impl": 497 return [] 498 499 return [ctx.attr._import_macro_dep] 500 501def should_encode_label_in_crate_name(workspace_name, label, third_party_dir): 502 """Determines if the crate's name should include the Bazel label, encoded. 503 504 Crate names may only encode the label if the target is in the current repo, 505 the target is not in the third_party_dir, and the current repo is not 506 rules_rust. 507 508 Args: 509 workspace_name (string): The name of the current workspace. 510 label (Label): The package in question. 511 third_party_dir (string): The directory in which third-party packages are kept. 512 513 Returns: 514 True if the crate name should encode the label, False otherwise. 515 """ 516 517 # TODO(hlopko): This code assumes a monorepo; make it work with external 518 # repositories as well. 519 return ( 520 workspace_name != "rules_rust" and 521 not label.workspace_root and 522 not ("//" + label.package + "/").startswith(third_party_dir + "/") 523 ) 524 525# This is a list of pairs, where the first element of the pair is a character 526# that is allowed in Bazel package or target names but not in crate names; and 527# the second element is an encoding of that char suitable for use in a crate 528# name. 529_encodings = ( 530 (":", "x"), 531 ("!", "excl"), 532 ("%", "prc"), 533 ("@", "ao"), 534 ("^", "caret"), 535 ("`", "bt"), 536 (" ", "sp"), 537 ("\"", "dq"), 538 ("#", "octo"), 539 ("$", "dllr"), 540 ("&", "amp"), 541 ("'", "sq"), 542 ("(", "lp"), 543 (")", "rp"), 544 ("*", "astr"), 545 ("-", "d"), 546 ("+", "pl"), 547 (",", "cm"), 548 (";", "sm"), 549 ("<", "la"), 550 ("=", "eq"), 551 (">", "ra"), 552 ("?", "qm"), 553 ("[", "lbk"), 554 ("]", "rbk"), 555 ("{", "lbe"), 556 ("|", "pp"), 557 ("}", "rbe"), 558 ("~", "td"), 559 ("/", "y"), 560 (".", "pd"), 561) 562 563# For each of the above encodings, we generate two substitution rules: one that 564# ensures any occurrences of the encodings themselves in the package/target 565# aren't clobbered by this translation, and one that does the encoding itself. 566# We also include a rule that protects the clobbering-protection rules from 567# getting clobbered. 568_substitutions = [("_z", "_zz_")] + [ 569 subst 570 for (pattern, replacement) in _encodings 571 for subst in ( 572 ("_{}_".format(replacement), "_z{}_".format(replacement)), 573 (pattern, "_{}_".format(replacement)), 574 ) 575] 576 577# Expose the substitutions for testing only. 578substitutions_for_testing = _substitutions 579 580def encode_label_as_crate_name(package, name): 581 """Encodes the package and target names in a format suitable for a crate name. 582 583 Args: 584 package (string): The package of the target in question. 585 name (string): The name of the target in question. 586 587 Returns: 588 A string that encodes the package and target name, to be used as the crate's name. 589 """ 590 return _encode_raw_string(package + ":" + name) 591 592def _encode_raw_string(str): 593 """Encodes a string using the above encoding format. 594 595 Args: 596 str (string): The string to be encoded. 597 598 Returns: 599 An encoded version of the input string. 600 """ 601 return _replace_all(str, _substitutions) 602 603# Expose the underlying encoding function for testing only. 604encode_raw_string_for_testing = _encode_raw_string 605 606def decode_crate_name_as_label_for_testing(crate_name): 607 """Decodes a crate_name that was encoded by encode_label_as_crate_name. 608 609 This is used to check that the encoding is bijective; it is expected to only 610 be used in tests. 611 612 Args: 613 crate_name (string): The name of the crate. 614 615 Returns: 616 A string representing the Bazel label (package and target). 617 """ 618 return _replace_all(crate_name, [(t[1], t[0]) for t in _substitutions]) 619 620def _replace_all(string, substitutions): 621 """Replaces occurrences of the given patterns in `string`. 622 623 There are a few reasons this looks complicated: 624 * The substitutions are performed with some priority, i.e. patterns that are 625 listed first in `substitutions` are higher priority than patterns that are 626 listed later. 627 * We also take pains to avoid doing replacements that overlap with each 628 other, since overlaps invalidate pattern matches. 629 * To avoid hairy offset invalidation, we apply the substitutions 630 right-to-left. 631 * To avoid the "_quote" -> "_quotequote_" rule introducing new pattern 632 matches later in the string during decoding, we take the leftmost 633 replacement, in cases of overlap. (Note that no rule can induce new 634 pattern matches *earlier* in the string.) (E.g. "_quotedot_" encodes to 635 "_quotequote_dot_". Note that "_quotequote_" and "_dot_" both occur in 636 this string, and overlap.). 637 638 Args: 639 string (string): the string in which the replacements should be performed. 640 substitutions: the list of patterns and replacements to apply. 641 642 Returns: 643 A string with the appropriate substitutions performed. 644 """ 645 646 # Find the highest-priority pattern matches for each string index, going 647 # left-to-right and skipping indices that are already involved in a 648 # pattern match. 649 plan = {} 650 matched_indices_set = {} 651 for pattern_start in range(len(string)): 652 if pattern_start in matched_indices_set: 653 continue 654 for (pattern, replacement) in substitutions: 655 if not string.startswith(pattern, pattern_start): 656 continue 657 length = len(pattern) 658 plan[pattern_start] = (length, replacement) 659 matched_indices_set.update([(pattern_start + i, True) for i in range(length)]) 660 break 661 662 # Execute the replacement plan, working from right to left. 663 for pattern_start in sorted(plan.keys(), reverse = True): 664 length, replacement = plan[pattern_start] 665 after_pattern = pattern_start + length 666 string = string[:pattern_start] + replacement + string[after_pattern:] 667 668 return string 669 670def can_build_metadata(toolchain, ctx, crate_type): 671 """Can we build metadata for this rust_library? 672 673 Args: 674 toolchain (toolchain): The rust toolchain 675 ctx (ctx): The rule's context object 676 crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro 677 678 Returns: 679 bool: whether we can build metadata for this rule. 680 """ 681 682 # In order to enable pipelined compilation we require that: 683 # 1) The _pipelined_compilation flag is enabled, 684 # 2) the OS running the rule is something other than windows as we require sandboxing (for now), 685 # 3) process_wrapper is enabled (this is disabled when compiling process_wrapper itself), 686 # 4) the crate_type is rlib or lib. 687 return toolchain._pipelined_compilation and \ 688 toolchain.exec_triple.system != "windows" and \ 689 ctx.attr._process_wrapper and \ 690 crate_type in ("rlib", "lib") 691 692def crate_root_src(name, srcs, crate_type): 693 """Determines the source file for the crate root, should it not be specified in `attr.crate_root`. 694 695 Args: 696 name (str): The name of the target. 697 srcs (list): A list of all sources for the target Crate. 698 crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc). 699 700 Returns: 701 File: The root File object for a given crate. See the following links for more details: 702 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library 703 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries 704 """ 705 default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs" 706 707 crate_root = ( 708 (srcs[0] if len(srcs) == 1 else None) or 709 _shortest_src_with_basename(srcs, default_crate_root_filename) or 710 _shortest_src_with_basename(srcs, name + ".rs") 711 ) 712 if not crate_root: 713 file_names = [default_crate_root_filename, name + ".rs"] 714 fail("Couldn't find {} among `srcs`, please use `crate_root` to specify the root file.".format(" or ".join(file_names))) 715 return crate_root 716 717def _shortest_src_with_basename(srcs, basename): 718 """Finds the shortest among the paths in srcs that match the desired basename. 719 720 Args: 721 srcs (list): A list of File objects 722 basename (str): The target basename to match against. 723 724 Returns: 725 File: The File object with the shortest path that matches `basename` 726 """ 727 shortest = None 728 for f in srcs: 729 if f.basename == basename: 730 if not shortest or len(f.dirname) < len(shortest.dirname): 731 shortest = f 732 return shortest 733 734def determine_lib_name(name, crate_type, toolchain, lib_hash = None): 735 """See https://github.com/bazelbuild/rules_rust/issues/405 736 737 Args: 738 name (str): The name of the current target 739 crate_type (str): The `crate_type` 740 toolchain (rust_toolchain): The current `rust_toolchain` 741 lib_hash (str, optional): The hashed crate root path 742 743 Returns: 744 str: A unique library name 745 """ 746 extension = None 747 prefix = "" 748 if crate_type in ("dylib", "cdylib", "proc-macro"): 749 extension = toolchain.dylib_ext 750 elif crate_type == "staticlib": 751 extension = toolchain.staticlib_ext 752 elif crate_type in ("lib", "rlib"): 753 # All platforms produce 'rlib' here 754 extension = ".rlib" 755 prefix = "lib" 756 elif crate_type == "bin": 757 fail("crate_type of 'bin' was detected in a rust_library. Please compile " + 758 "this crate as a rust_binary instead.") 759 760 if not extension: 761 fail(("Unknown crate_type: {}. If this is a cargo-supported crate type, " + 762 "please file an issue!").format(crate_type)) 763 764 prefix = "lib" 765 if toolchain.target_triple and toolchain.target_os == "windows" and crate_type not in ("lib", "rlib"): 766 prefix = "" 767 if toolchain.target_arch == "wasm32" and crate_type == "cdylib": 768 prefix = "" 769 770 return "{prefix}{name}{lib_hash}{extension}".format( 771 prefix = prefix, 772 name = name, 773 lib_hash = "-" + lib_hash if lib_hash else "", 774 extension = extension, 775 ) 776 777def transform_sources(ctx, srcs, crate_root): 778 """Creates symlinks of the source files if needed. 779 780 Rustc assumes that the source files are located next to the crate root. 781 In case of a mix between generated and non-generated source files, this 782 we violate this assumption, as part of the sources will be located under 783 bazel-out/... . In order to allow for targets that contain both generated 784 and non-generated source files, we generate symlinks for all non-generated 785 files. 786 787 Args: 788 ctx (struct): The current rule's context. 789 srcs (List[File]): The sources listed in the `srcs` attribute 790 crate_root (File): The file specified in the `crate_root` attribute, 791 if it exists, otherwise None 792 793 Returns: 794 Tuple(List[File], File): The transformed srcs and crate_root 795 """ 796 has_generated_sources = len([src for src in srcs if not src.is_source]) > 0 797 798 if not has_generated_sources: 799 return srcs, crate_root 800 801 package_root = paths.join(ctx.label.workspace_root, ctx.label.package) 802 generated_sources = [_symlink_for_non_generated_source(ctx, src, package_root) for src in srcs if src != crate_root] 803 generated_root = crate_root 804 if crate_root: 805 generated_root = _symlink_for_non_generated_source(ctx, crate_root, package_root) 806 generated_sources.append(generated_root) 807 808 return generated_sources, generated_root 809 810def get_edition(attr, toolchain, label): 811 """Returns the Rust edition from either the current rule's attributes or the current `rust_toolchain` 812 813 Args: 814 attr (struct): The current rule's attributes 815 toolchain (rust_toolchain): The `rust_toolchain` for the current target 816 label (Label): The label of the target being built 817 818 Returns: 819 str: The target Rust edition 820 """ 821 if getattr(attr, "edition"): 822 return attr.edition 823 elif not toolchain.default_edition: 824 fail("Attribute `edition` is required for {}.".format(label)) 825 else: 826 return toolchain.default_edition 827 828def _symlink_for_non_generated_source(ctx, src_file, package_root): 829 """Creates and returns a symlink for non-generated source files. 830 831 This rule uses the full path to the source files and the rule directory to compute 832 the relative paths. This is needed, instead of using `short_path`, because of non-generated 833 source files in external repositories possibly returning relative paths depending on the 834 current version of Bazel. 835 836 Args: 837 ctx (struct): The current rule's context. 838 src_file (File): The source file. 839 package_root (File): The full path to the directory containing the current rule. 840 841 Returns: 842 File: The created symlink if a non-generated file, or the file itself. 843 """ 844 845 if src_file.is_source or src_file.root.path != ctx.bin_dir.path: 846 src_short_path = paths.relativize(src_file.path, src_file.root.path) 847 src_symlink = ctx.actions.declare_file(paths.relativize(src_short_path, package_root)) 848 ctx.actions.symlink( 849 output = src_symlink, 850 target_file = src_file, 851 progress_message = "Creating symlink to source file: {}".format(src_file.path), 852 ) 853 return src_symlink 854 else: 855 return src_file 856 857def generate_output_diagnostics(ctx, sibling, require_process_wrapper = True): 858 """Generates a .rustc-output file if it's required. 859 860 Args: 861 ctx: (ctx): The current rule's context object 862 sibling: (File): The file to generate the diagnostics for. 863 require_process_wrapper: (bool): Whether to require the process wrapper 864 in order to generate the .rustc-output file. 865 Returns: 866 Optional[File] The .rustc-object file, if generated. 867 """ 868 869 # Since this feature requires error_format=json, we usually need 870 # process_wrapper, since it can write the json here, then convert it to the 871 # regular error format so the user can see the error properly. 872 if require_process_wrapper and not ctx.attr._process_wrapper: 873 return None 874 provider = ctx.attr._rustc_output_diagnostics[RustcOutputDiagnosticsInfo] 875 if not provider.rustc_output_diagnostics: 876 return None 877 878 return ctx.actions.declare_file( 879 sibling.basename + ".rustc-output", 880 sibling = sibling, 881 ) 882 883def is_std_dylib(file): 884 """Whether the file is a dylib crate for std 885 886 """ 887 basename = file.basename 888 return ( 889 # for linux and darwin 890 basename.startswith("libstd-") and (basename.endswith(".so") or basename.endswith(".dylib")) or 891 # for windows 892 basename.startswith("std-") and basename.endswith(".dll") 893 ) 894