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