| # Copyright 2015 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Utility functions not specific to the rust toolchain.""" |
| |
| load("@bazel_skylib//lib:paths.bzl", "paths") |
| load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain") |
| load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo", "RustcOutputDiagnosticsInfo") |
| |
| UNSUPPORTED_FEATURES = [ |
| "thin_lto", |
| "module_maps", |
| "use_header_modules", |
| "fdo_instrument", |
| "fdo_optimize", |
| # This feature is unsupported by definition. The authors of C++ toolchain |
| # configuration can place any linker flags that should not be applied when |
| # linking Rust targets in a feature with this name. |
| "rules_rust_unsupported_feature", |
| ] |
| |
| def find_toolchain(ctx): |
| """Finds the first rust toolchain that is configured. |
| |
| Args: |
| ctx (ctx): The ctx object for the current target. |
| |
| Returns: |
| rust_toolchain: A Rust toolchain context. |
| """ |
| return ctx.toolchains[Label("//rust:toolchain_type")] |
| |
| def find_cc_toolchain(ctx, extra_unsupported_features = tuple()): |
| """Extracts a CcToolchain from the current target's context |
| |
| Args: |
| ctx (ctx): The current target's rule context object |
| extra_unsupported_features (sequence of str): Extra featrures to disable |
| |
| Returns: |
| tuple: A tuple of (CcToolchain, FeatureConfiguration) |
| """ |
| cc_toolchain = find_rules_cc_toolchain(ctx) |
| |
| feature_configuration = cc_common.configure_features( |
| ctx = ctx, |
| cc_toolchain = cc_toolchain, |
| requested_features = ctx.features, |
| unsupported_features = UNSUPPORTED_FEATURES + ctx.disabled_features + |
| list(extra_unsupported_features), |
| ) |
| return cc_toolchain, feature_configuration |
| |
| # TODO: Replace with bazel-skylib's `path.dirname`. This requires addressing some |
| # dependency issues or generating docs will break. |
| def relativize(path, start): |
| """Returns the relative path from start to path. |
| |
| Args: |
| path (str): The path to relativize. |
| start (str): The ancestor path against which to relativize. |
| |
| Returns: |
| str: The portion of `path` that is relative to `start`. |
| """ |
| src_parts = _path_parts(start) |
| dest_parts = _path_parts(path) |
| n = 0 |
| for src_part, dest_part in zip(src_parts, dest_parts): |
| if src_part != dest_part: |
| break |
| n += 1 |
| |
| relative_path = "" |
| for _ in range(n, len(src_parts)): |
| relative_path += "../" |
| relative_path += "/".join(dest_parts[n:]) |
| |
| return relative_path |
| |
| def _path_parts(path): |
| """Takes a path and returns a list of its parts with all "." elements removed. |
| |
| The main use case of this function is if one of the inputs to relativize() |
| is a relative path, such as "./foo". |
| |
| Args: |
| path (str): A string representing a unix path |
| |
| Returns: |
| list: A list containing the path parts with all "." elements removed. |
| """ |
| path_parts = path.split("/") |
| return [part for part in path_parts if part != "."] |
| |
| def get_lib_name_default(lib): |
| """Returns the name of a library artifact. |
| |
| Args: |
| lib (File): A library file |
| |
| Returns: |
| str: The name of the library |
| """ |
| # On macos and windows, dynamic/static libraries always end with the |
| # extension and potential versions will be before the extension, and should |
| # be part of the library name. |
| # On linux, the version usually comes after the extension. |
| # So regardless of the platform we want to find the extension and make |
| # everything left to it the library name. |
| |
| # Search for the extension - starting from the right - by removing any |
| # trailing digit. |
| comps = lib.basename.split(".") |
| for comp in reversed(comps): |
| if comp.isdigit(): |
| comps.pop() |
| else: |
| break |
| |
| # The library name is now everything minus the extension. |
| libname = ".".join(comps[:-1]) |
| |
| if libname.startswith("lib"): |
| return libname[3:] |
| else: |
| return libname |
| |
| # TODO: Could we remove this function in favor of a "windows" parameter in the |
| # above function? It looks like currently lambdas cannot accept local parameters |
| # so the following doesn't work: |
| # args.add_all( |
| # cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration), |
| # map_each = lambda x: get_lib_name(x, for_windows = toolchain.target_os.startswith("windows)), |
| # format_each = "-ldylib=%s", |
| # ) |
| def get_lib_name_for_windows(lib): |
| """Returns the name of a library artifact for Windows builds. |
| |
| Args: |
| lib (File): A library file |
| |
| Returns: |
| str: The name of the library |
| """ |
| # On macos and windows, dynamic/static libraries always end with the |
| # extension and potential versions will be before the extension, and should |
| # be part of the library name. |
| # On linux, the version usually comes after the extension. |
| # So regardless of the platform we want to find the extension and make |
| # everything left to it the library name. |
| |
| # Search for the extension - starting from the right - by removing any |
| # trailing digit. |
| comps = lib.basename.split(".") |
| for comp in reversed(comps): |
| if comp.isdigit(): |
| comps.pop() |
| else: |
| break |
| |
| # The library name is now everything minus the extension. |
| libname = ".".join(comps[:-1]) |
| |
| return libname |
| |
| def abs(value): |
| """Returns the absolute value of a number. |
| |
| Args: |
| value (int): A number. |
| |
| Returns: |
| int: The absolute value of the number. |
| """ |
| if value < 0: |
| return -value |
| return value |
| |
| def determine_output_hash(crate_root, label): |
| """Generates a hash of the crate root file's path. |
| |
| Args: |
| crate_root (File): The crate's root file (typically `lib.rs`). |
| label (Label): The label of the target. |
| |
| Returns: |
| str: A string representation of the hash. |
| """ |
| |
| # Take the absolute value of hash() since it could be negative. |
| h = abs(hash(crate_root.path) + hash(repr(label))) |
| return repr(h) |
| |
| def get_preferred_artifact(library_to_link, use_pic): |
| """Get the first available library to link from a LibraryToLink object. |
| |
| Args: |
| library_to_link (LibraryToLink): See the followg links for additional details: |
| https://docs.bazel.build/versions/master/skylark/lib/LibraryToLink.html |
| use_pic: If set, prefers pic_static_library over static_library. |
| |
| Returns: |
| File: Returns the first valid library type (only one is expected) |
| """ |
| if use_pic: |
| # Order consistent with https://github.com/bazelbuild/bazel/blob/815dfdabb7df31d4e99b6fc7616ff8e2f9385d98/src/main/java/com/google/devtools/build/lib/rules/cpp/CcLinkingContext.java#L437. |
| return ( |
| library_to_link.pic_static_library or |
| library_to_link.static_library or |
| library_to_link.interface_library or |
| library_to_link.dynamic_library |
| ) |
| else: |
| return ( |
| library_to_link.static_library or |
| library_to_link.pic_static_library or |
| library_to_link.interface_library or |
| library_to_link.dynamic_library |
| ) |
| |
| # The normal ctx.expand_location, but with an additional deduplication step. |
| # We do this to work around a potential crash, see |
| # https://github.com/bazelbuild/bazel/issues/16664 |
| def dedup_expand_location(ctx, input, targets = []): |
| return ctx.expand_location(input, _deduplicate(targets)) |
| |
| def _deduplicate(xs): |
| return {x: True for x in xs}.keys() |
| |
| def concat(xss): |
| return [x for xs in xss for x in xs] |
| |
| def _expand_location_for_build_script_runner(ctx, env, data): |
| """A trivial helper for `expand_dict_value_locations` and `expand_list_element_locations` |
| |
| Args: |
| ctx (ctx): The rule's context object |
| env (str): The value possibly containing location macros to expand. |
| data (sequence of Targets): See one of the parent functions. |
| |
| Returns: |
| string: The location-macro expanded version of the string. |
| """ |
| for directive in ("$(execpath ", "$(location "): |
| if directive in env: |
| # build script runner will expand pwd to execroot for us |
| env = env.replace(directive, "$${pwd}/" + directive) |
| return ctx.expand_make_variables( |
| env, |
| dedup_expand_location(ctx, env, data), |
| {}, |
| ) |
| |
| def expand_dict_value_locations(ctx, env, data): |
| """Performs location-macro expansion on string values. |
| |
| $(execroot ...) and $(location ...) are prefixed with ${pwd}, |
| which process_wrapper and build_script_runner will expand at run time |
| to the absolute path. This is necessary because include_str!() is relative |
| to the currently compiled file, and build scripts run relative to the |
| manifest dir, so we can not use execroot-relative paths. |
| |
| $(rootpath ...) is unmodified, and is useful for passing in paths via |
| rustc_env that are encoded in the binary with env!(), but utilized at |
| runtime, such as in tests. The absolute paths are not usable in this case, |
| as compilation happens in a separate sandbox folder, so when it comes time |
| to read the file at runtime, the path is no longer valid. |
| |
| For detailed documentation, see: |
| - [`expand_location`](https://bazel.build/rules/lib/ctx#expand_location) |
| - [`expand_make_variables`](https://bazel.build/rules/lib/ctx#expand_make_variables) |
| |
| Args: |
| ctx (ctx): The rule's context object |
| env (dict): A dict whose values we iterate over |
| data (sequence of Targets): The targets which may be referenced by |
| location macros. This is expected to be the `data` attribute of |
| the target, though may have other targets or attributes mixed in. |
| |
| Returns: |
| dict: A dict of environment variables with expanded location macros |
| """ |
| return dict([(k, _expand_location_for_build_script_runner(ctx, v, data)) for (k, v) in env.items()]) |
| |
| def expand_list_element_locations(ctx, args, data): |
| """Performs location-macro expansion on a list of string values. |
| |
| $(execroot ...) and $(location ...) are prefixed with ${pwd}, |
| which process_wrapper and build_script_runner will expand at run time |
| to the absolute path. |
| |
| For detailed documentation, see: |
| - [`expand_location`](https://bazel.build/rules/lib/ctx#expand_location) |
| - [`expand_make_variables`](https://bazel.build/rules/lib/ctx#expand_make_variables) |
| |
| Args: |
| ctx (ctx): The rule's context object |
| args (list): A list we iterate over |
| data (sequence of Targets): The targets which may be referenced by |
| location macros. This is expected to be the `data` attribute of |
| the target, though may have other targets or attributes mixed in. |
| |
| Returns: |
| list: A list of arguments with expanded location macros |
| """ |
| return [_expand_location_for_build_script_runner(ctx, arg, data) for arg in args] |
| |
| def name_to_crate_name(name): |
| """Converts a build target's name into the name of its associated crate. |
| |
| Crate names cannot contain certain characters, such as -, which are allowed |
| in build target names. All illegal characters will be converted to |
| underscores. |
| |
| This is a similar conversion as that which cargo does, taking a |
| `Cargo.toml`'s `package.name` and canonicalizing it |
| |
| Note that targets can specify the `crate_name` attribute to customize their |
| crate name; in situations where this is important, use the |
| compute_crate_name() function instead. |
| |
| Args: |
| name (str): The name of the target. |
| |
| Returns: |
| str: The name of the crate for this target. |
| """ |
| for illegal in ("-", "/"): |
| name = name.replace(illegal, "_") |
| return name |
| |
| def _invalid_chars_in_crate_name(name): |
| """Returns any invalid chars in the given crate name. |
| |
| Args: |
| name (str): Name to test. |
| |
| Returns: |
| list: List of invalid characters in the crate name. |
| """ |
| |
| return dict([(c, ()) for c in name.elems() if not (c.isalnum() or c == "_")]).keys() |
| |
| def compute_crate_name(workspace_name, label, toolchain, name_override = None): |
| """Returns the crate name to use for the current target. |
| |
| Args: |
| workspace_name (string): The current workspace name. |
| label (struct): The label of the current target. |
| toolchain (struct): The toolchain in use for the target. |
| name_override (String): An optional name to use (as an override of label.name). |
| |
| Returns: |
| str: The crate name to use for this target. |
| """ |
| if name_override: |
| invalid_chars = _invalid_chars_in_crate_name(name_override) |
| if invalid_chars: |
| fail("Crate name '{}' contains invalid character(s): {}".format( |
| name_override, |
| " ".join(invalid_chars), |
| )) |
| return name_override |
| |
| if (toolchain and label and toolchain._rename_first_party_crates and |
| should_encode_label_in_crate_name(workspace_name, label, toolchain._third_party_dir)): |
| crate_name = encode_label_as_crate_name(label.package, label.name) |
| else: |
| crate_name = name_to_crate_name(label.name) |
| |
| invalid_chars = _invalid_chars_in_crate_name(crate_name) |
| if invalid_chars: |
| fail( |
| "Crate name '{}' ".format(crate_name) + |
| "derived from Bazel target name '{}' ".format(label.name) + |
| "contains invalid character(s): {}\n".format(" ".join(invalid_chars)) + |
| "Consider adding a crate_name attribute to set a valid crate name", |
| ) |
| return crate_name |
| |
| def dedent(doc_string): |
| """Remove any common leading whitespace from every line in text. |
| |
| This functionality is similar to python's `textwrap.dedent` functionality |
| https://docs.python.org/3/library/textwrap.html#textwrap.dedent |
| |
| Args: |
| doc_string (str): A docstring style string |
| |
| Returns: |
| str: A string optimized for stardoc rendering |
| """ |
| lines = doc_string.splitlines() |
| if not lines: |
| return doc_string |
| |
| # If the first line is empty, use the second line |
| first_line = lines[0] |
| if not first_line: |
| first_line = lines[1] |
| |
| # Detect how much space prepends the first line and subtract that from all lines |
| space_count = len(first_line) - len(first_line.lstrip()) |
| |
| # If there are no leading spaces, do not alter the docstring |
| if space_count == 0: |
| return doc_string |
| else: |
| # Remove the leading block of spaces from the current line |
| block = " " * space_count |
| return "\n".join([line.replace(block, "", 1).rstrip() for line in lines]) |
| |
| def make_static_lib_symlink(ctx_package, actions, rlib_file): |
| """Add a .a symlink to an .rlib file. |
| |
| The name of the symlink is derived from the <name> of the <name>.rlib file as follows: |
| * `<name>.a`, if <name> starts with `lib` |
| * `lib<name>.a`, otherwise. |
| |
| For example, the name of the symlink for |
| * `libcratea.rlib` is `libcratea.a` |
| * `crateb.rlib` is `libcrateb.a`. |
| |
| Args: |
| ctx_package (string): The rule's context package name. |
| actions (actions): The rule's context actions object. |
| rlib_file (File): The file to symlink, which must end in .rlib. |
| |
| Returns: |
| The symlink's File. |
| """ |
| |
| if not rlib_file.basename.endswith(".rlib"): |
| fail("file is not an .rlib: ", rlib_file.basename) |
| basename = rlib_file.basename[:-5] |
| if not basename.startswith("lib"): |
| basename = "lib" + basename |
| |
| # The .a symlink below is created as a sibling to the .rlib file. |
| # Bazel doesn't allow creating a symlink outside of the rule's package, |
| # so if the .rlib file comes from a different package, first symlink it |
| # to the rule's package. The name of the new .rlib symlink is derived |
| # as the name of the original .rlib relative to its package. |
| if rlib_file.owner.package != ctx_package: |
| new_path = rlib_file.short_path.removeprefix(rlib_file.owner.package).removeprefix("/") |
| new_rlib_file = actions.declare_file(new_path) |
| actions.symlink(output = new_rlib_file, target_file = rlib_file) |
| rlib_file = new_rlib_file |
| |
| dot_a = actions.declare_file(basename + ".a", sibling = rlib_file) |
| actions.symlink(output = dot_a, target_file = rlib_file) |
| return dot_a |
| |
| def is_exec_configuration(ctx): |
| """Determine if a context is building for the exec configuration. |
| |
| This is helpful when processing command line flags that should apply |
| to the target configuration but not the exec configuration. |
| |
| Args: |
| ctx (ctx): The ctx object for the current target. |
| |
| Returns: |
| True if the exec configuration is detected, False otherwise. |
| """ |
| |
| # TODO(djmarcin): Is there any better way to determine cfg=exec? |
| return ctx.genfiles_dir.path.find("-exec") != -1 |
| |
| def transform_deps(deps): |
| """Transforms a [Target] into [DepVariantInfo]. |
| |
| This helper function is used to transform ctx.attr.deps and ctx.attr.proc_macro_deps into |
| [DepVariantInfo]. |
| |
| Args: |
| deps (list of Targets): Dependencies coming from ctx.attr.deps or ctx.attr.proc_macro_deps |
| |
| Returns: |
| list of DepVariantInfos. |
| """ |
| return [DepVariantInfo( |
| crate_info = dep[CrateInfo] if CrateInfo in dep else None, |
| dep_info = dep[DepInfo] if DepInfo in dep else None, |
| build_info = dep[BuildInfo] if BuildInfo in dep else None, |
| cc_info = dep[CcInfo] if CcInfo in dep else None, |
| crate_group_info = dep[CrateGroupInfo] if CrateGroupInfo in dep else None, |
| ) for dep in deps] |
| |
| def get_import_macro_deps(ctx): |
| """Returns a list of targets to be added to proc_macro_deps. |
| |
| Args: |
| ctx (struct): the ctx of the current target. |
| |
| Returns: |
| list of Targets. Either empty (if the fake import macro implementation |
| is being used), or a singleton list with the real implementation. |
| """ |
| if not hasattr(ctx.attr, "_import_macro_dep"): |
| return [] |
| |
| if ctx.attr._import_macro_dep.label.name == "fake_import_macro_impl": |
| return [] |
| |
| return [ctx.attr._import_macro_dep] |
| |
| def should_encode_label_in_crate_name(workspace_name, label, third_party_dir): |
| """Determines if the crate's name should include the Bazel label, encoded. |
| |
| Crate names may only encode the label if the target is in the current repo, |
| the target is not in the third_party_dir, and the current repo is not |
| rules_rust. |
| |
| Args: |
| workspace_name (string): The name of the current workspace. |
| label (Label): The package in question. |
| third_party_dir (string): The directory in which third-party packages are kept. |
| |
| Returns: |
| True if the crate name should encode the label, False otherwise. |
| """ |
| |
| # TODO(hlopko): This code assumes a monorepo; make it work with external |
| # repositories as well. |
| return ( |
| workspace_name != "rules_rust" and |
| not label.workspace_root and |
| not ("//" + label.package + "/").startswith(third_party_dir + "/") |
| ) |
| |
| # This is a list of pairs, where the first element of the pair is a character |
| # that is allowed in Bazel package or target names but not in crate names; and |
| # the second element is an encoding of that char suitable for use in a crate |
| # name. |
| _encodings = ( |
| (":", "x"), |
| ("!", "excl"), |
| ("%", "prc"), |
| ("@", "ao"), |
| ("^", "caret"), |
| ("`", "bt"), |
| (" ", "sp"), |
| ("\"", "dq"), |
| ("#", "octo"), |
| ("$", "dllr"), |
| ("&", "amp"), |
| ("'", "sq"), |
| ("(", "lp"), |
| (")", "rp"), |
| ("*", "astr"), |
| ("-", "d"), |
| ("+", "pl"), |
| (",", "cm"), |
| (";", "sm"), |
| ("<", "la"), |
| ("=", "eq"), |
| (">", "ra"), |
| ("?", "qm"), |
| ("[", "lbk"), |
| ("]", "rbk"), |
| ("{", "lbe"), |
| ("|", "pp"), |
| ("}", "rbe"), |
| ("~", "td"), |
| ("/", "y"), |
| (".", "pd"), |
| ) |
| |
| # For each of the above encodings, we generate two substitution rules: one that |
| # ensures any occurrences of the encodings themselves in the package/target |
| # aren't clobbered by this translation, and one that does the encoding itself. |
| # We also include a rule that protects the clobbering-protection rules from |
| # getting clobbered. |
| _substitutions = [("_z", "_zz_")] + [ |
| subst |
| for (pattern, replacement) in _encodings |
| for subst in ( |
| ("_{}_".format(replacement), "_z{}_".format(replacement)), |
| (pattern, "_{}_".format(replacement)), |
| ) |
| ] |
| |
| # Expose the substitutions for testing only. |
| substitutions_for_testing = _substitutions |
| |
| def encode_label_as_crate_name(package, name): |
| """Encodes the package and target names in a format suitable for a crate name. |
| |
| Args: |
| package (string): The package of the target in question. |
| name (string): The name of the target in question. |
| |
| Returns: |
| A string that encodes the package and target name, to be used as the crate's name. |
| """ |
| return _encode_raw_string(package + ":" + name) |
| |
| def _encode_raw_string(str): |
| """Encodes a string using the above encoding format. |
| |
| Args: |
| str (string): The string to be encoded. |
| |
| Returns: |
| An encoded version of the input string. |
| """ |
| return _replace_all(str, _substitutions) |
| |
| # Expose the underlying encoding function for testing only. |
| encode_raw_string_for_testing = _encode_raw_string |
| |
| def decode_crate_name_as_label_for_testing(crate_name): |
| """Decodes a crate_name that was encoded by encode_label_as_crate_name. |
| |
| This is used to check that the encoding is bijective; it is expected to only |
| be used in tests. |
| |
| Args: |
| crate_name (string): The name of the crate. |
| |
| Returns: |
| A string representing the Bazel label (package and target). |
| """ |
| return _replace_all(crate_name, [(t[1], t[0]) for t in _substitutions]) |
| |
| def _replace_all(string, substitutions): |
| """Replaces occurrences of the given patterns in `string`. |
| |
| There are a few reasons this looks complicated: |
| * The substitutions are performed with some priority, i.e. patterns that are |
| listed first in `substitutions` are higher priority than patterns that are |
| listed later. |
| * We also take pains to avoid doing replacements that overlap with each |
| other, since overlaps invalidate pattern matches. |
| * To avoid hairy offset invalidation, we apply the substitutions |
| right-to-left. |
| * To avoid the "_quote" -> "_quotequote_" rule introducing new pattern |
| matches later in the string during decoding, we take the leftmost |
| replacement, in cases of overlap. (Note that no rule can induce new |
| pattern matches *earlier* in the string.) (E.g. "_quotedot_" encodes to |
| "_quotequote_dot_". Note that "_quotequote_" and "_dot_" both occur in |
| this string, and overlap.). |
| |
| Args: |
| string (string): the string in which the replacements should be performed. |
| substitutions: the list of patterns and replacements to apply. |
| |
| Returns: |
| A string with the appropriate substitutions performed. |
| """ |
| |
| # Find the highest-priority pattern matches for each string index, going |
| # left-to-right and skipping indices that are already involved in a |
| # pattern match. |
| plan = {} |
| matched_indices_set = {} |
| for pattern_start in range(len(string)): |
| if pattern_start in matched_indices_set: |
| continue |
| for (pattern, replacement) in substitutions: |
| if not string.startswith(pattern, pattern_start): |
| continue |
| length = len(pattern) |
| plan[pattern_start] = (length, replacement) |
| matched_indices_set.update([(pattern_start + i, True) for i in range(length)]) |
| break |
| |
| # Execute the replacement plan, working from right to left. |
| for pattern_start in sorted(plan.keys(), reverse = True): |
| length, replacement = plan[pattern_start] |
| after_pattern = pattern_start + length |
| string = string[:pattern_start] + replacement + string[after_pattern:] |
| |
| return string |
| |
| def can_build_metadata(toolchain, ctx, crate_type): |
| """Can we build metadata for this rust_library? |
| |
| Args: |
| toolchain (toolchain): The rust toolchain |
| ctx (ctx): The rule's context object |
| crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro |
| |
| Returns: |
| bool: whether we can build metadata for this rule. |
| """ |
| |
| # In order to enable pipelined compilation we require that: |
| # 1) The _pipelined_compilation flag is enabled, |
| # 2) the OS running the rule is something other than windows as we require sandboxing (for now), |
| # 3) process_wrapper is enabled (this is disabled when compiling process_wrapper itself), |
| # 4) the crate_type is rlib or lib. |
| return toolchain._pipelined_compilation and \ |
| toolchain.exec_triple.system != "windows" and \ |
| ctx.attr._process_wrapper and \ |
| crate_type in ("rlib", "lib") |
| |
| def crate_root_src(name, crate_name, srcs, crate_type): |
| """Determines the source file for the crate root, should it not be specified in `attr.crate_root`. |
| |
| Args: |
| name (str): The name of the target. |
| crate_name (str): The target's `crate_name` attribute. |
| srcs (list): A list of all sources for the target Crate. |
| crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc). |
| |
| Returns: |
| File: The root File object for a given crate. See the following links for more details: |
| - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library |
| - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries |
| """ |
| default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs" |
| |
| if not crate_name: |
| crate_name = name |
| |
| crate_root = ( |
| (srcs[0] if len(srcs) == 1 else None) or |
| _shortest_src_with_basename(srcs, default_crate_root_filename) or |
| _shortest_src_with_basename(srcs, name + ".rs") or |
| _shortest_src_with_basename(srcs, crate_name + ".rs") |
| ) |
| if not crate_root: |
| file_names = [default_crate_root_filename, name + ".rs"] |
| fail("Couldn't find {} among `srcs`, please use `crate_root` to specify the root file.".format(" or ".join(file_names))) |
| return crate_root |
| |
| def _shortest_src_with_basename(srcs, basename): |
| """Finds the shortest among the paths in srcs that match the desired basename. |
| |
| Args: |
| srcs (list): A list of File objects |
| basename (str): The target basename to match against. |
| |
| Returns: |
| File: The File object with the shortest path that matches `basename` |
| """ |
| shortest = None |
| for f in srcs: |
| if f.basename == basename: |
| if not shortest or len(f.dirname) < len(shortest.dirname): |
| shortest = f |
| return shortest |
| |
| def determine_lib_name(name, crate_type, toolchain, lib_hash = None): |
| """See https://github.com/bazelbuild/rules_rust/issues/405 |
| |
| Args: |
| name (str): The name of the current target |
| crate_type (str): The `crate_type` |
| toolchain (rust_toolchain): The current `rust_toolchain` |
| lib_hash (str, optional): The hashed crate root path |
| |
| Returns: |
| str: A unique library name |
| """ |
| extension = None |
| prefix = "" |
| if crate_type in ("dylib", "cdylib", "proc-macro"): |
| extension = toolchain.dylib_ext |
| elif crate_type == "staticlib": |
| extension = toolchain.staticlib_ext |
| elif crate_type in ("lib", "rlib"): |
| # All platforms produce 'rlib' here |
| extension = ".rlib" |
| prefix = "lib" |
| elif crate_type == "bin": |
| fail("crate_type of 'bin' was detected in a rust_library. Please compile " + |
| "this crate as a rust_binary instead.") |
| |
| if not extension: |
| fail(("Unknown crate_type: {}. If this is a cargo-supported crate type, " + |
| "please file an issue!").format(crate_type)) |
| |
| prefix = "lib" |
| if toolchain.target_triple and toolchain.target_os == "windows" and crate_type not in ("lib", "rlib"): |
| prefix = "" |
| if toolchain.target_arch in ("wasm32", "wasm64") and crate_type == "cdylib": |
| prefix = "" |
| |
| return "{prefix}{name}{lib_hash}{extension}".format( |
| prefix = prefix, |
| name = name, |
| lib_hash = "-" + lib_hash if lib_hash else "", |
| extension = extension, |
| ) |
| |
| def transform_sources(ctx, srcs, crate_root): |
| """Creates symlinks of the source files if needed. |
| |
| Rustc assumes that the source files are located next to the crate root. |
| In case of a mix between generated and non-generated source files, this |
| we violate this assumption, as part of the sources will be located under |
| bazel-out/... . In order to allow for targets that contain both generated |
| and non-generated source files, we generate symlinks for all non-generated |
| files. |
| |
| Args: |
| ctx (struct): The current rule's context. |
| srcs (List[File]): The sources listed in the `srcs` attribute |
| crate_root (File): The file specified in the `crate_root` attribute, |
| if it exists, otherwise None |
| |
| Returns: |
| Tuple(List[File], File): The transformed srcs and crate_root |
| """ |
| has_generated_sources = len([src for src in srcs if not src.is_source]) > 0 |
| |
| if not has_generated_sources: |
| return srcs, crate_root |
| |
| package_root = paths.join(ctx.label.workspace_root, ctx.label.package) |
| generated_sources = [_symlink_for_non_generated_source(ctx, src, package_root) for src in srcs if src != crate_root] |
| generated_root = crate_root |
| if crate_root: |
| generated_root = _symlink_for_non_generated_source(ctx, crate_root, package_root) |
| generated_sources.append(generated_root) |
| |
| return generated_sources, generated_root |
| |
| def get_edition(attr, toolchain, label): |
| """Returns the Rust edition from either the current rule's attributes or the current `rust_toolchain` |
| |
| Args: |
| attr (struct): The current rule's attributes |
| toolchain (rust_toolchain): The `rust_toolchain` for the current target |
| label (Label): The label of the target being built |
| |
| Returns: |
| str: The target Rust edition |
| """ |
| if getattr(attr, "edition"): |
| return attr.edition |
| elif not toolchain.default_edition: |
| fail("Attribute `edition` is required for {}.".format(label)) |
| else: |
| return toolchain.default_edition |
| |
| def _symlink_for_non_generated_source(ctx, src_file, package_root): |
| """Creates and returns a symlink for non-generated source files. |
| |
| This rule uses the full path to the source files and the rule directory to compute |
| the relative paths. This is needed, instead of using `short_path`, because of non-generated |
| source files in external repositories possibly returning relative paths depending on the |
| current version of Bazel. |
| |
| Args: |
| ctx (struct): The current rule's context. |
| src_file (File): The source file. |
| package_root (File): The full path to the directory containing the current rule. |
| |
| Returns: |
| File: The created symlink if a non-generated file, or the file itself. |
| """ |
| |
| if src_file.is_source or src_file.root.path != ctx.bin_dir.path: |
| src_short_path = paths.relativize(src_file.path, src_file.root.path) |
| src_symlink = ctx.actions.declare_file(paths.relativize(src_short_path, package_root)) |
| ctx.actions.symlink( |
| output = src_symlink, |
| target_file = src_file, |
| progress_message = "Creating symlink to source file: {}".format(src_file.path), |
| ) |
| return src_symlink |
| else: |
| return src_file |
| |
| def generate_output_diagnostics(ctx, sibling, require_process_wrapper = True): |
| """Generates a .rustc-output file if it's required. |
| |
| Args: |
| ctx: (ctx): The current rule's context object |
| sibling: (File): The file to generate the diagnostics for. |
| require_process_wrapper: (bool): Whether to require the process wrapper |
| in order to generate the .rustc-output file. |
| Returns: |
| Optional[File] The .rustc-object file, if generated. |
| """ |
| |
| # Since this feature requires error_format=json, we usually need |
| # process_wrapper, since it can write the json here, then convert it to the |
| # regular error format so the user can see the error properly. |
| if require_process_wrapper and not ctx.attr._process_wrapper: |
| return None |
| provider = ctx.attr._rustc_output_diagnostics[RustcOutputDiagnosticsInfo] |
| if not provider.rustc_output_diagnostics: |
| return None |
| |
| return ctx.actions.declare_file( |
| sibling.basename + ".rustc-output", |
| sibling = sibling, |
| ) |
| |
| def is_std_dylib(file): |
| """Whether the file is a dylib crate for std |
| |
| """ |
| basename = file.basename |
| return ( |
| # for linux and darwin |
| basename.startswith("libstd-") and (basename.endswith(".so") or basename.endswith(".dylib")) or |
| # for windows |
| basename.startswith("std-") and basename.endswith(".dll") |
| ) |