Squashed 'third_party/rules_rust/' content from commit bf59038ca

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
new file mode 100644
index 0000000..596c10d
--- /dev/null
+++ b/rust/private/rustc.bzl
@@ -0,0 +1,1436 @@
+# Copyright 2018 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.
+
+"""Functionality for constructing actions that invoke the Rust compiler"""
+
+load(
+    "@bazel_tools//tools/build_defs/cc:action_names.bzl",
+    "CPP_LINK_EXECUTABLE_ACTION_NAME",
+)
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:providers.bzl", _BuildInfo = "BuildInfo")
+load("//rust/private:stamp.bzl", "is_stamping_enabled")
+load(
+    "//rust/private:utils.bzl",
+    "abs",
+    "expand_dict_value_locations",
+    "expand_list_element_locations",
+    "find_cc_toolchain",
+    "get_lib_name",
+    "get_preferred_artifact",
+    "is_exec_configuration",
+    "make_static_lib_symlink",
+    "relativize",
+)
+
+BuildInfo = _BuildInfo
+
+AliasableDepInfo = provider(
+    doc = "A provider mapping an alias name to a Crate's information.",
+    fields = {
+        "dep": "CrateInfo",
+        "name": "str",
+    },
+)
+
+_error_format_values = ["human", "json", "short"]
+
+ErrorFormatInfo = provider(
+    doc = "Set the --error-format flag for all rustc invocations",
+    fields = {"error_format": "(string) [" + ", ".join(_error_format_values) + "]"},
+)
+
+ExtraRustcFlagsInfo = provider(
+    doc = "Pass each value as an additional flag to non-exec rustc invocations",
+    fields = {"extra_rustc_flags": "List[string] Extra flags to pass to rustc in non-exec configuration"},
+)
+
+ExtraExecRustcFlagsInfo = provider(
+    doc = "Pass each value as an additional flag to exec rustc invocations",
+    fields = {"extra_exec_rustc_flags": "List[string] Extra flags to pass to rustc in exec configuration"},
+)
+
+def _get_rustc_env(attr, toolchain, crate_name):
+    """Gathers rustc environment variables
+
+    Args:
+        attr (struct): The current target's attributes
+        toolchain (rust_toolchain): The current target's rust toolchain context
+        crate_name (str): The name of the crate to be compiled
+
+    Returns:
+        dict: Rustc environment variables
+    """
+    version = attr.version if hasattr(attr, "version") else "0.0.0"
+    major, minor, patch = version.split(".", 2)
+    if "-" in patch:
+        patch, pre = patch.split("-", 1)
+    else:
+        pre = ""
+    return {
+        "CARGO_CFG_TARGET_ARCH": toolchain.target_arch,
+        "CARGO_CFG_TARGET_OS": toolchain.os,
+        "CARGO_CRATE_NAME": crate_name,
+        "CARGO_PKG_AUTHORS": "",
+        "CARGO_PKG_DESCRIPTION": "",
+        "CARGO_PKG_HOMEPAGE": "",
+        "CARGO_PKG_NAME": attr.name,
+        "CARGO_PKG_VERSION": version,
+        "CARGO_PKG_VERSION_MAJOR": major,
+        "CARGO_PKG_VERSION_MINOR": minor,
+        "CARGO_PKG_VERSION_PATCH": patch,
+        "CARGO_PKG_VERSION_PRE": pre,
+    }
+
+def get_compilation_mode_opts(ctx, toolchain):
+    """Gathers rustc flags for the current compilation mode (opt/debug)
+
+    Args:
+        ctx (ctx): The current rule's context object
+        toolchain (rust_toolchain): The current rule's `rust_toolchain`
+
+    Returns:
+        struct: See `_rust_toolchain_impl` for more details
+    """
+    comp_mode = ctx.var["COMPILATION_MODE"]
+    if not comp_mode in toolchain.compilation_mode_opts:
+        fail("Unrecognized compilation mode {} for toolchain.".format(comp_mode))
+
+    return toolchain.compilation_mode_opts[comp_mode]
+
+def _are_linkstamps_supported(feature_configuration, has_grep_includes):
+    # Are linkstamps supported by the C++ toolchain?
+    return (cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "linkstamps") and
+            # Is Bazel recent enough to support Starlark linkstamps?
+            hasattr(cc_common, "register_linkstamp_compile_action") and
+            # The current rule doesn't define _grep_includes attribute; this
+            # attribute is required for compiling linkstamps.
+            has_grep_includes)
+
+def _should_use_pic(cc_toolchain, feature_configuration, crate_type):
+    if crate_type in ("cdylib" or "dylib"):
+        return cc_toolchain.needs_pic_for_dynamic_libraries(feature_configuration = feature_configuration)
+    return False
+
+def collect_deps(
+        deps,
+        proc_macro_deps,
+        aliases,
+        are_linkstamps_supported = False):
+    """Walks through dependencies and collects the transitive dependencies.
+
+    Args:
+        deps (list): The deps from ctx.attr.deps.
+        proc_macro_deps (list): The proc_macro deps from ctx.attr.proc_macro_deps.
+        aliases (dict): A dict mapping aliased targets to their actual Crate information.
+        are_linkstamps_supported (bool): Whether the current rule and the toolchain support building linkstamps..
+
+    Returns:
+        tuple: Returns a tuple of:
+            DepInfo,
+            BuildInfo,
+            linkstamps (depset[CcLinkstamp]): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
+
+    """
+    direct_crates = []
+    transitive_crates = []
+    transitive_noncrates = []
+    transitive_build_infos = []
+    transitive_link_search_paths = []
+    build_info = None
+    linkstamps = []
+    transitive_crate_outputs = []
+
+    aliases = {k.label: v for k, v in aliases.items()}
+    for dep in depset(transitive = [deps, proc_macro_deps]).to_list():
+        (crate_info, dep_info) = _get_crate_and_dep_info(dep)
+        cc_info = _get_cc_info(dep)
+        dep_build_info = _get_build_info(dep)
+
+        if cc_info and are_linkstamps_supported:
+            linkstamps.append(cc_info.linking_context.linkstamps())
+
+        if crate_info:
+            # This dependency is a rust_library
+
+            # When crate_info.owner is set, we use it. When the dep type is Target we get the
+            # label from dep.label
+            owner = getattr(crate_info, "owner", dep.label if type(dep) == "Target" else None)
+
+            direct_crates.append(AliasableDepInfo(
+                name = aliases.get(owner, crate_info.name),
+                dep = crate_info,
+            ))
+
+            transitive_crates.append(depset([crate_info], transitive = [dep_info.transitive_crates]))
+            transitive_crate_outputs.append(
+                depset(
+                    [crate_info.output],
+                    transitive = [dep_info.transitive_crate_outputs],
+                ),
+            )
+            transitive_noncrates.append(dep_info.transitive_noncrates)
+            transitive_build_infos.append(dep_info.transitive_build_infos)
+            transitive_link_search_paths.append(dep_info.link_search_path_files)
+
+        elif cc_info:
+            # This dependency is a cc_library
+            transitive_noncrates.append(cc_info.linking_context.linker_inputs)
+        elif dep_build_info:
+            if build_info:
+                fail("Several deps are providing build information, " +
+                     "only one is allowed in the dependencies")
+            build_info = dep_build_info
+            transitive_build_infos.append(depset([build_info]))
+            transitive_link_search_paths.append(depset([build_info.link_search_paths]))
+        else:
+            fail("rust targets can only depend on rust_library, rust_*_library or cc_library " +
+                 "targets.")
+
+    transitive_crates_depset = depset(transitive = transitive_crates)
+
+    return (
+        rust_common.dep_info(
+            direct_crates = depset(direct_crates),
+            transitive_crates = transitive_crates_depset,
+            transitive_noncrates = depset(
+                transitive = transitive_noncrates,
+                order = "topological",  # dylib link flag ordering matters.
+            ),
+            transitive_crate_outputs = depset(transitive = transitive_crate_outputs),
+            transitive_build_infos = depset(transitive = transitive_build_infos),
+            link_search_path_files = depset(transitive = transitive_link_search_paths),
+            dep_env = build_info.dep_env if build_info else None,
+        ),
+        build_info,
+        depset(transitive = linkstamps),
+    )
+
+def _collect_libs_from_linker_inputs(linker_inputs, use_pic):
+    # TODO: We could let the user choose how to link, instead of always preferring to link static libraries.
+    return [
+        get_preferred_artifact(lib, use_pic)
+        for li in linker_inputs
+        for lib in li.libraries
+    ]
+
+def _get_crate_and_dep_info(dep):
+    if type(dep) == "Target" and rust_common.crate_info in dep:
+        return (dep[rust_common.crate_info], dep[rust_common.dep_info])
+    elif type(dep) == "struct" and hasattr(dep, "crate_info"):
+        return (dep.crate_info, dep.dep_info)
+    return (None, None)
+
+def _get_cc_info(dep):
+    if type(dep) == "Target" and CcInfo in dep:
+        return dep[CcInfo]
+    elif type(dep) == "struct" and hasattr(dep, "cc_info"):
+        return dep.cc_info
+    return None
+
+def _get_build_info(dep):
+    if type(dep) == "Target" and BuildInfo in dep:
+        return dep[BuildInfo]
+    elif type(dep) == "struct" and hasattr(dep, "build_info"):
+        return dep.build_info
+    return None
+
+def get_cc_user_link_flags(ctx):
+    """Get the current target's linkopt flags
+
+    Args:
+        ctx (ctx): The current rule's context object
+
+    Returns:
+        depset: The flags passed to Bazel by --linkopt option.
+    """
+    return ctx.fragments.cpp.linkopts
+
+def get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths):
+    """Gathers cc_common linker information
+
+    Args:
+        ctx (ctx): The current target's context object
+        attr (struct): Attributes to use in gathering linker args
+        cc_toolchain (CcToolchain): cc_toolchain for which we are creating build variables.
+        feature_configuration (FeatureConfiguration): Feature configuration to be queried.
+        rpaths (depset): Depset of directories where loader will look for libraries at runtime.
+
+
+    Returns:
+        tuple: A tuple of the following items:
+            - (str): The tool path for given action.
+            - (sequence): A flattened command line flags for given action.
+            - (dict): Environment variables to be set for given action.
+    """
+    user_link_flags = get_cc_user_link_flags(ctx)
+
+    # Add linkopt's from dependencies. This includes linkopts from transitive
+    # dependencies since they get merged up.
+    for dep in getattr(attr, "deps", []):
+        if CcInfo in dep and dep[CcInfo].linking_context:
+            for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list():
+                for flag in linker_input.user_link_flags:
+                    user_link_flags.append(flag)
+    link_variables = cc_common.create_link_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        is_linking_dynamic_library = False,
+        runtime_library_search_directories = rpaths,
+        user_link_flags = user_link_flags,
+    )
+    link_args = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+        variables = link_variables,
+    )
+    link_env = cc_common.get_environment_variables(
+        feature_configuration = feature_configuration,
+        action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+        variables = link_variables,
+    )
+    ld = cc_common.get_tool_for_action(
+        feature_configuration = feature_configuration,
+        action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+    )
+
+    return ld, link_args, link_env
+
+def _process_build_scripts(
+        build_info,
+        dep_info,
+        compile_inputs):
+    """Gathers the outputs from a target's `cargo_build_script` action.
+
+    Args:
+        build_info (BuildInfo): The target Build's dependency info.
+        dep_info (DepInfo): The Depinfo provider form the target Crate's set of inputs.
+        compile_inputs (depset): A set of all files that will participate in the build.
+
+    Returns:
+        tuple: A tuple: A tuple of the following items:
+            - (depset[File]): A list of all build info `OUT_DIR` File objects
+            - (str): The `OUT_DIR` of the current build info
+            - (File): An optional path to a generated environment file from a `cargo_build_script` target
+            - (depset[File]): All direct and transitive build flags from the current build info.
+    """
+    extra_inputs, out_dir, build_env_file, build_flags_files = _create_extra_input_args(build_info, dep_info)
+    compile_inputs = depset(transitive = [extra_inputs, compile_inputs])
+    return compile_inputs, out_dir, build_env_file, build_flags_files
+
+def _symlink_for_ambiguous_lib(actions, toolchain, crate_info, lib):
+    """Constructs a disambiguating symlink for a library dependency.
+
+    Args:
+      actions (Actions): The rule's context actions object.
+      toolchain: The Rust toolchain object.
+      crate_info (CrateInfo): The target crate's info.
+      lib (File): The library to symlink to.
+
+    Returns:
+      (File): The disambiguating symlink for the library.
+    """
+    # FIXME: Once the relative order part of the native-link-modifiers rustc
+    # feature is stable, we should be able to eliminate the need to construct
+    # symlinks by passing the full paths to the libraries.
+    # https://github.com/rust-lang/rust/issues/81490.
+
+    # Take the absolute value of hash() since it could be negative.
+    path_hash = abs(hash(lib.path))
+    lib_name = get_lib_name(lib)
+
+    prefix = "lib"
+    extension = ".a"
+    if toolchain.os.startswith("windows"):
+        prefix = ""
+        extension = ".lib"
+
+    # Ensure the symlink follows the lib<name>.a pattern on Unix-like platforms
+    # or <name>.lib on Windows.
+    # Add a hash of the original library path to disambiguate libraries with the same basename.
+    symlink_name = "{}{}-{}{}".format(prefix, lib_name, path_hash, extension)
+
+    # Add the symlink to a target crate-specific _ambiguous_libs/ subfolder,
+    # to avoid possible collisions with sibling crates that may depend on the
+    # same ambiguous libraries.
+    symlink = actions.declare_file("_ambiguous_libs/" + crate_info.output.basename + "/" + symlink_name)
+    actions.symlink(
+        output = symlink,
+        target_file = lib,
+        progress_message = "Creating symlink to ambiguous lib: {}".format(lib.path),
+    )
+    return symlink
+
+def _disambiguate_libs(actions, toolchain, crate_info, dep_info, use_pic):
+    """Constructs disambiguating symlinks for ambiguous library dependencies.
+
+    The symlinks are all created in a _ambiguous_libs/ subfolder specific to
+    the target crate to avoid possible collisions with sibling crates that may
+    depend on the same ambiguous libraries.
+
+    Args:
+      actions (Actions): The rule's context actions object.
+      toolchain: The Rust toolchain object.
+      crate_info (CrateInfo): The target crate's info.
+      dep_info: (DepInfo): The target crate's dependency info.
+      use_pic: (boolean): Whether the build should use PIC.
+
+    Returns:
+      dict[String, File]: A mapping from ambiguous library paths to their
+        disambiguating symlink.
+    """
+    # FIXME: Once the relative order part of the native-link-modifiers rustc
+    # feature is stable, we should be able to eliminate the need to construct
+    # symlinks by passing the full paths to the libraries.
+    # https://github.com/rust-lang/rust/issues/81490.
+
+    # A dictionary from file paths of ambiguous libraries to the corresponding
+    # symlink.
+    ambiguous_libs = {}
+
+    # A dictionary maintaining a mapping from preferred library name to the
+    # last visited artifact with that name.
+    visited_libs = {}
+    for link_input in dep_info.transitive_noncrates.to_list():
+        for lib in link_input.libraries:
+            # FIXME: Dynamic libs are not disambiguated right now, there are
+            # cases where those have a non-standard name with version (e.g.,
+            # //test/unit/versioned_libs). We hope that the link modifiers
+            # stabilization will come before we need to make this work.
+            if _is_dylib(lib):
+                continue
+            artifact = get_preferred_artifact(lib, use_pic)
+            name = get_lib_name(artifact)
+
+            # On Linux-like platforms, normally library base names start with
+            # `lib`, following the pattern `lib[name].(a|lo)` and we pass
+            # -lstatic=name.
+            # On Windows, the base name looks like `name.lib` and we pass
+            # -lstatic=name.
+            # FIXME: Under the native-link-modifiers unstable rustc feature,
+            # we could use -lstatic:+verbatim instead.
+            needs_symlink_to_standardize_name = (
+                (toolchain.os.startswith("linux") or toolchain.os.startswith("mac") or toolchain.os.startswith("darwin")) and
+                artifact.basename.endswith(".a") and not artifact.basename.startswith("lib")
+            ) or (
+                toolchain.os.startswith("windows") and not artifact.basename.endswith(".lib")
+            )
+
+            # Detect cases where we need to disambiguate library dependencies
+            # by constructing symlinks.
+            if (
+                needs_symlink_to_standardize_name or
+                # We have multiple libraries with the same name.
+                (name in visited_libs and visited_libs[name].path != artifact.path)
+            ):
+                # Disambiguate the previously visited library (if we just detected
+                # that it is ambiguous) and the current library.
+                if name in visited_libs:
+                    old_path = visited_libs[name].path
+                    if old_path not in ambiguous_libs:
+                        ambiguous_libs[old_path] = _symlink_for_ambiguous_lib(actions, toolchain, crate_info, visited_libs[name])
+                ambiguous_libs[artifact.path] = _symlink_for_ambiguous_lib(actions, toolchain, crate_info, artifact)
+
+            visited_libs[name] = artifact
+    return ambiguous_libs
+
+def collect_inputs(
+        ctx,
+        file,
+        files,
+        linkstamps,
+        toolchain,
+        cc_toolchain,
+        feature_configuration,
+        crate_info,
+        dep_info,
+        build_info,
+        stamp = False):
+    """Gather's the inputs and required input information for a rustc action
+
+    Args:
+        ctx (ctx): The rule's context object.
+        file (struct): A struct containing files defined in label type attributes marked as `allow_single_file`.
+        files (list): A list of all inputs (`ctx.files`).
+        linkstamps (depset): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
+        toolchain (rust_toolchain): The current `rust_toolchain`.
+        cc_toolchain (CcToolchainInfo): The current `cc_toolchain`.
+        feature_configuration (FeatureConfiguration): Feature configuration to be queried.
+        crate_info (CrateInfo): The Crate information of the crate to process build scripts for.
+        dep_info (DepInfo): The target Crate's dependency information.
+        build_info (BuildInfo): The target Crate's build settings.
+        stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
+            https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
+
+    Returns:
+        tuple: A tuple: A tuple of the following items:
+            - (list): A list of all build info `OUT_DIR` File objects
+            - (str): The `OUT_DIR` of the current build info
+            - (File): An optional path to a generated environment file from a `cargo_build_script` target
+            - (depset[File]): All direct and transitive build flag files from the current build info
+            - (list[File]): Linkstamp outputs
+            - (dict[String, File]): Ambiguous libs, see `_disambiguate_libs`.
+    """
+    linker_script = getattr(file, "linker_script") if hasattr(file, "linker_script") else None
+
+    linker_depset = cc_toolchain.all_files
+
+    use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
+
+    # Pass linker inputs only for linking-like actions, not for example where
+    # the output is rlib. This avoids quadratic behavior where transitive noncrates are
+    # flattened on each transitive rust_library dependency.
+    additional_transitive_inputs = []
+    ambiguous_libs = {}
+    if crate_info.type in ("staticlib", "proc-macro"):
+        additional_transitive_inputs = _collect_libs_from_linker_inputs(
+            dep_info.transitive_noncrates.to_list(),
+            use_pic,
+        )
+    elif crate_info.type in ("bin", "dylib", "cdylib"):
+        linker_inputs = dep_info.transitive_noncrates.to_list()
+        ambiguous_libs = _disambiguate_libs(ctx.actions, toolchain, crate_info, dep_info, use_pic)
+        additional_transitive_inputs = _collect_libs_from_linker_inputs(linker_inputs, use_pic) + [
+            additional_input
+            for linker_input in linker_inputs
+            for additional_input in linker_input.additional_inputs
+        ] + ambiguous_libs.values()
+
+    # Compute linkstamps. Use the inputs of the binary as inputs to the
+    # linkstamp action to ensure linkstamps are rebuilt whenever binary inputs
+    # change.
+    linkstamp_outs = []
+
+    nolinkstamp_compile_inputs = depset(
+        getattr(files, "data", []) +
+        ([build_info.rustc_env, build_info.flags] if build_info else []) +
+        ([toolchain.target_json] if toolchain.target_json else []) +
+        ([] if linker_script == None else [linker_script]),
+        transitive = [
+            linker_depset,
+            crate_info.srcs,
+            dep_info.transitive_crate_outputs,
+            depset(additional_transitive_inputs),
+            crate_info.compile_data,
+            toolchain.all_files,
+        ],
+    )
+
+    if crate_info.type in ("bin", "cdylib"):
+        # There is no other way to register an action for each member of a depset than
+        # flattening the depset as of 2021-10-12. Luckily, usually there is only one linkstamp
+        # in a build, and we only flatten the list on binary targets that perform transitive linking,
+        # so it's extremely unlikely that this call to `to_list()` will ever be a performance
+        # problem.
+        for linkstamp in linkstamps.to_list():
+            # The linkstamp output path is based on the binary crate
+            # name and the input linkstamp path. This is to disambiguate
+            # the linkstamp outputs produced by multiple binary crates
+            # that depend on the same linkstamp. We use the same pattern
+            # for the output name as the one used by native cc rules.
+            out_name = "_objs/" + crate_info.output.basename + "/" + linkstamp.file().path[:-len(linkstamp.file().extension)] + "o"
+            linkstamp_out = ctx.actions.declare_file(out_name)
+            linkstamp_outs.append(linkstamp_out)
+            cc_common.register_linkstamp_compile_action(
+                actions = ctx.actions,
+                cc_toolchain = cc_toolchain,
+                feature_configuration = feature_configuration,
+                grep_includes = ctx.file._grep_includes,
+                source_file = linkstamp.file(),
+                output_file = linkstamp_out,
+                compilation_inputs = linkstamp.hdrs(),
+                inputs_for_validation = nolinkstamp_compile_inputs,
+                label_replacement = str(ctx.label),
+                output_replacement = crate_info.output.path,
+            )
+
+    # If stamping is enabled include the volatile status info file
+    stamp_info = [ctx.version_file] if stamp else []
+
+    compile_inputs = depset(
+        linkstamp_outs + stamp_info,
+        transitive = [
+            nolinkstamp_compile_inputs,
+        ],
+    )
+
+    build_env_files = getattr(files, "rustc_env_files", [])
+    compile_inputs, out_dir, build_env_file, build_flags_files = _process_build_scripts(build_info, dep_info, compile_inputs)
+    if build_env_file:
+        build_env_files = [f for f in build_env_files] + [build_env_file]
+    compile_inputs = depset(build_env_files, transitive = [compile_inputs])
+
+    return compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs
+
+def construct_arguments(
+        ctx,
+        attr,
+        file,
+        toolchain,
+        tool_path,
+        cc_toolchain,
+        feature_configuration,
+        crate_info,
+        dep_info,
+        linkstamp_outs,
+        ambiguous_libs,
+        output_hash,
+        rust_flags,
+        out_dir,
+        build_env_files,
+        build_flags_files,
+        emit = ["dep-info", "link"],
+        force_all_deps_direct = False,
+        force_link = False,
+        stamp = False,
+        remap_path_prefix = "."):
+    """Builds an Args object containing common rustc flags
+
+    Args:
+        ctx (ctx): The rule's context object
+        attr (struct): The attributes for the target. These may be different from ctx.attr in an aspect context.
+        file (struct): A struct containing files defined in label type attributes marked as `allow_single_file`.
+        toolchain (rust_toolchain): The current target's `rust_toolchain`
+        tool_path (str): Path to rustc
+        cc_toolchain (CcToolchain): The CcToolchain for the current target.
+        feature_configuration (FeatureConfiguration): Class used to construct command lines from CROSSTOOL features.
+        crate_info (CrateInfo): The CrateInfo provider of the target crate
+        dep_info (DepInfo): The DepInfo provider of the target crate
+        linkstamp_outs (list): Linkstamp outputs of native dependencies
+        ambiguous_libs (dict): Ambiguous libs, see `_disambiguate_libs`
+        output_hash (str): The hashed path of the crate root
+        rust_flags (list): Additional flags to pass to rustc
+        out_dir (str): The path to the output directory for the target Crate.
+        build_env_files (list): Files containing rustc environment variables, for instance from `cargo_build_script` actions.
+        build_flags_files (depset): The output files of a `cargo_build_script` actions containing rustc build flags
+        emit (list): Values for the --emit flag to rustc.
+        force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
+            to the commandline as opposed to -L.
+        force_link (bool, optional): Whether to add link flags to the command regardless of `emit`.
+        stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
+            https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
+        remap_path_prefix (str, optional): A value used to remap `${pwd}` to. If set to a falsey value, no prefix will be set.
+
+    Returns:
+        tuple: A tuple of the following items
+            - (struct): A struct of arguments used to run the `Rustc` action
+                - process_wrapper_flags (Args): Arguments for the process wrapper
+                - rustc_path (Args): Arguments for invoking rustc via the process wrapper
+                - rustc_flags (Args): Rust flags for the Rust compiler
+                - all (list): A list of all `Args` objects in the order listed above.
+                    This is to be passed to the `arguments` parameter of actions
+            - (dict): Common rustc environment variables
+    """
+    output_dir = getattr(crate_info.output, "dirname", None)
+    linker_script = getattr(file, "linker_script", None)
+
+    env = _get_rustc_env(attr, toolchain, crate_info.name)
+
+    # Wrapper args first
+    process_wrapper_flags = ctx.actions.args()
+
+    for build_env_file in build_env_files:
+        process_wrapper_flags.add("--env-file", build_env_file)
+
+    process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file")
+
+    # Certain rust build processes expect to find files from the environment
+    # variable `$CARGO_MANIFEST_DIR`. Examples of this include pest, tera,
+    # asakuma.
+    #
+    # The compiler and by extension proc-macros see the current working
+    # directory as the Bazel exec root. This is what `$CARGO_MANIFEST_DIR`
+    # would default to but is often the wrong value (e.g. if the source is in a
+    # sub-package or if we are building something in an external repository).
+    # Hence, we need to set `CARGO_MANIFEST_DIR` explicitly.
+    #
+    # Since we cannot get the `exec_root` from starlark, we cheat a little and
+    # use `${pwd}` which resolves the `exec_root` at action execution time.
+    process_wrapper_flags.add("--subst", "pwd=${pwd}")
+
+    # If stamping is enabled, enable the functionality in the process wrapper
+    if stamp:
+        process_wrapper_flags.add("--volatile-status-file", ctx.version_file)
+
+    # Both ctx.label.workspace_root and ctx.label.package are relative paths
+    # and either can be empty strings. Avoid trailing/double slashes in the path.
+    components = "${{pwd}}/{}/{}".format(ctx.label.workspace_root, ctx.label.package).split("/")
+    env["CARGO_MANIFEST_DIR"] = "/".join([c for c in components if c])
+
+    if out_dir != None:
+        env["OUT_DIR"] = "${pwd}/" + out_dir
+
+    # Handle that the binary name and crate name may be different.
+    #
+    # If a target name contains a - then cargo (and rules_rust) will generate a
+    # crate name with _ instead.  Accordingly, rustc will generate a output
+    # file (executable, or rlib, or whatever) with _ not -.  But when cargo
+    # puts a binary in the target/${config} directory, and sets environment
+    # variables like `CARGO_BIN_EXE_${binary_name}` it will use the - version
+    # not the _ version.  So we rename the rustc-generated file (with _s) to
+    # have -s if needed.
+    emit_with_paths = emit
+    if crate_info.type == "bin" and crate_info.output != None:
+        generated_file = crate_info.name + toolchain.binary_ext
+        src = "/".join([crate_info.output.dirname, generated_file])
+        dst = crate_info.output.path
+        if src != dst:
+            emit_with_paths = [("link=" + dst if val == "link" else val) for val in emit]
+
+    # Arguments for launching rustc from the process wrapper
+    rustc_path = ctx.actions.args()
+    rustc_path.add("--")
+    rustc_path.add(tool_path)
+
+    # Rustc arguments
+    rustc_flags = ctx.actions.args()
+    rustc_flags.set_param_file_format("multiline")
+    rustc_flags.use_param_file("@%s", use_always = False)
+    rustc_flags.add(crate_info.root)
+    rustc_flags.add("--crate-name=" + crate_info.name)
+    rustc_flags.add("--crate-type=" + crate_info.type)
+    if hasattr(attr, "_error_format"):
+        rustc_flags.add("--error-format=" + attr._error_format[ErrorFormatInfo].error_format)
+
+    # Mangle symbols to disambiguate crates with the same name
+    extra_filename = "-" + output_hash if output_hash else ""
+    rustc_flags.add("--codegen=metadata=" + extra_filename)
+    if output_dir:
+        rustc_flags.add("--out-dir=" + output_dir)
+    rustc_flags.add("--codegen=extra-filename=" + extra_filename)
+
+    compilation_mode = get_compilation_mode_opts(ctx, toolchain)
+    rustc_flags.add("--codegen=opt-level=" + compilation_mode.opt_level)
+    rustc_flags.add("--codegen=debuginfo=" + compilation_mode.debug_info)
+
+    # For determinism to help with build distribution and such
+    if remap_path_prefix:
+        rustc_flags.add("--remap-path-prefix=${{pwd}}={}".format(remap_path_prefix))
+
+    if emit:
+        rustc_flags.add("--emit=" + ",".join(emit_with_paths))
+    rustc_flags.add("--color=always")
+    rustc_flags.add("--target=" + toolchain.target_flag_value)
+    if hasattr(attr, "crate_features"):
+        rustc_flags.add_all(getattr(attr, "crate_features"), before_each = "--cfg", format_each = 'feature="%s"')
+    if linker_script:
+        rustc_flags.add(linker_script.path, format = "--codegen=link-arg=-T%s")
+
+    # Gets the paths to the folders containing the standard library (or libcore)
+    rust_std_paths = toolchain.rust_std_paths.to_list()
+
+    # Tell Rustc where to find the standard library
+    rustc_flags.add_all(rust_std_paths, before_each = "-L", format_each = "%s")
+    rustc_flags.add_all(rust_flags)
+
+    # Deduplicate data paths due to https://github.com/bazelbuild/bazel/issues/14681
+    data_paths = depset(direct = getattr(attr, "data", []) + getattr(attr, "compile_data", [])).to_list()
+
+    rustc_flags.add_all(
+        expand_list_element_locations(
+            ctx,
+            getattr(attr, "rustc_flags", []),
+            data_paths,
+        ),
+    )
+    add_edition_flags(rustc_flags, crate_info)
+
+    # Link!
+    if ("link" in emit and crate_info.type not in ["rlib", "lib"]) or force_link:
+        # Rust's built-in linker can handle linking wasm files. We don't want to attempt to use the cc
+        # linker since it won't understand.
+        if toolchain.target_arch != "wasm32":
+            if output_dir:
+                use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
+                rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic)
+            else:
+                rpaths = depset([])
+            ld, link_args, link_env = get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths)
+            env.update(link_env)
+            rustc_flags.add("--codegen=linker=" + ld)
+            rustc_flags.add_joined("--codegen", link_args, join_with = " ", format_joined = "link-args=%s")
+
+        _add_native_link_flags(rustc_flags, dep_info, linkstamp_outs, ambiguous_libs, crate_info.type, toolchain, cc_toolchain, feature_configuration)
+
+    # These always need to be added, even if not linking this crate.
+    add_crate_link_flags(rustc_flags, dep_info, force_all_deps_direct)
+
+    needs_extern_proc_macro_flag = "proc-macro" in [crate_info.type, crate_info.wrapped_crate_type] and \
+                                   crate_info.edition != "2015"
+    if needs_extern_proc_macro_flag:
+        rustc_flags.add("--extern")
+        rustc_flags.add("proc_macro")
+
+    # Make bin crate data deps available to tests.
+    for data in getattr(attr, "data", []):
+        if rust_common.crate_info in data:
+            dep_crate_info = data[rust_common.crate_info]
+            if dep_crate_info.type == "bin":
+                # Trying to make CARGO_BIN_EXE_{} canonical across platform by strip out extension if exists
+                env_basename = dep_crate_info.output.basename[:-(1 + len(dep_crate_info.output.extension))] if len(dep_crate_info.output.extension) > 0 else dep_crate_info.output.basename
+                env["CARGO_BIN_EXE_" + env_basename] = dep_crate_info.output.short_path
+
+    # Update environment with user provided variables.
+    env.update(expand_dict_value_locations(
+        ctx,
+        crate_info.rustc_env,
+        data_paths,
+    ))
+
+    # Ensure the sysroot is set for the target platform
+    env["SYSROOT"] = toolchain.sysroot
+
+    if toolchain._rename_first_party_crates:
+        env["RULES_RUST_THIRD_PARTY_DIR"] = toolchain._third_party_dir
+
+    # extra_rustc_flags apply to the target configuration, not the exec configuration.
+    if hasattr(ctx.attr, "_extra_rustc_flags") and not is_exec_configuration(ctx):
+        rustc_flags.add_all(ctx.attr._extra_rustc_flags[ExtraRustcFlagsInfo].extra_rustc_flags)
+
+    if hasattr(ctx.attr, "_extra_exec_rustc_flags") and is_exec_configuration(ctx):
+        rustc_flags.add_all(ctx.attr._extra_exec_rustc_flags[ExtraExecRustcFlagsInfo].extra_exec_rustc_flags)
+
+    # Create a struct which keeps the arguments separate so each may be tuned or
+    # replaced where necessary
+    args = struct(
+        process_wrapper_flags = process_wrapper_flags,
+        rustc_path = rustc_path,
+        rustc_flags = rustc_flags,
+        all = [process_wrapper_flags, rustc_path, rustc_flags],
+    )
+
+    return args, env
+
+def rustc_compile_action(
+        ctx,
+        attr,
+        toolchain,
+        crate_info,
+        output_hash = None,
+        rust_flags = [],
+        force_all_deps_direct = False):
+    """Create and run a rustc compile action based on the current rule's attributes
+
+    Args:
+        ctx (ctx): The rule's context object
+        attr (struct): Attributes to use for the rust compile action
+        toolchain (rust_toolchain): The current `rust_toolchain`
+        crate_info (CrateInfo): The CrateInfo provider for the current target.
+        output_hash (str, optional): The hashed path of the crate root. Defaults to None.
+        rust_flags (list, optional): Additional flags to pass to rustc. Defaults to [].
+        force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
+            to the commandline as opposed to -L.
+
+    Returns:
+        list: A list of the following providers:
+            - (CrateInfo): info for the crate we just built; same as `crate_info` parameter.
+            - (DepInfo): The transitive dependencies of this crate.
+            - (DefaultInfo): The output file for this crate, and its runfiles.
+    """
+    cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
+
+    dep_info, build_info, linkstamps = collect_deps(
+        deps = crate_info.deps,
+        proc_macro_deps = crate_info.proc_macro_deps,
+        aliases = crate_info.aliases,
+        are_linkstamps_supported = _are_linkstamps_supported(
+            feature_configuration = feature_configuration,
+            has_grep_includes = hasattr(ctx.attr, "_grep_includes"),
+        ),
+    )
+
+    # Determine if the build is currently running with --stamp
+    stamp = is_stamping_enabled(attr)
+
+    compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
+        ctx = ctx,
+        file = ctx.file,
+        files = ctx.files,
+        linkstamps = linkstamps,
+        toolchain = toolchain,
+        cc_toolchain = cc_toolchain,
+        feature_configuration = feature_configuration,
+        crate_info = crate_info,
+        dep_info = dep_info,
+        build_info = build_info,
+        stamp = stamp,
+    )
+
+    args, env_from_args = construct_arguments(
+        ctx = ctx,
+        attr = attr,
+        file = ctx.file,
+        toolchain = toolchain,
+        tool_path = toolchain.rustc.path,
+        cc_toolchain = cc_toolchain,
+        feature_configuration = feature_configuration,
+        crate_info = crate_info,
+        dep_info = dep_info,
+        linkstamp_outs = linkstamp_outs,
+        ambiguous_libs = ambiguous_libs,
+        output_hash = output_hash,
+        rust_flags = rust_flags,
+        out_dir = out_dir,
+        build_env_files = build_env_files,
+        build_flags_files = build_flags_files,
+        force_all_deps_direct = force_all_deps_direct,
+        stamp = stamp,
+    )
+
+    env = dict(ctx.configuration.default_shell_env)
+    env.update(env_from_args)
+
+    if hasattr(attr, "version") and attr.version != "0.0.0":
+        formatted_version = " v{}".format(attr.version)
+    else:
+        formatted_version = ""
+
+    outputs = [crate_info.output]
+
+    # For a cdylib that might be added as a dependency to a cc_* target on Windows, it is important to include the
+    # interface library that rustc generates in the output files.
+    interface_library = None
+    if toolchain.os == "windows" and crate_info.type == "cdylib":
+        # Rustc generates the import library with a `.dll.lib` extension rather than the usual `.lib` one that msvc
+        # expects (see https://github.com/rust-lang/rust/pull/29520 for more context).
+        interface_library = ctx.actions.declare_file(crate_info.output.basename + ".lib")
+        outputs.append(interface_library)
+
+    # The action might generate extra output that we don't want to include in the `DefaultInfo` files.
+    action_outputs = list(outputs)
+
+    # Rustc generates a pdb file (on Windows) or a dsym folder (on macos) so provide it in an output group for crate
+    # types that benefit from having debug information in a separate file.
+    pdb_file = None
+    dsym_folder = None
+    if crate_info.type in ("cdylib", "bin") and not crate_info.is_test:
+        if toolchain.os == "windows":
+            pdb_file = ctx.actions.declare_file(crate_info.output.basename[:-len(crate_info.output.extension)] + "pdb")
+            action_outputs.append(pdb_file)
+        elif toolchain.os == "darwin":
+            dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM")
+            action_outputs.append(dsym_folder)
+
+    # This uses startswith as on windows the basename will be process_wrapper_fake.exe.
+    if not ctx.executable._process_wrapper.basename.startswith("process_wrapper_fake"):
+        # Run as normal
+        ctx.actions.run(
+            executable = ctx.executable._process_wrapper,
+            inputs = compile_inputs,
+            outputs = action_outputs,
+            env = env,
+            arguments = args.all,
+            mnemonic = "Rustc",
+            progress_message = "Compiling Rust {} {}{} ({} files)".format(
+                crate_info.type,
+                ctx.label.name,
+                formatted_version,
+                len(crate_info.srcs.to_list()),
+            ),
+        )
+    else:
+        # Run without process_wrapper
+        if build_env_files or build_flags_files or stamp:
+            fail("build_env_files, build_flags_files, stamp are not supported if use_process_wrapper is False")
+        ctx.actions.run(
+            executable = toolchain.rustc,
+            inputs = compile_inputs,
+            outputs = action_outputs,
+            env = env,
+            arguments = [args.rustc_flags],
+            mnemonic = "Rustc",
+            progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} files)".format(
+                crate_info.type,
+                ctx.label.name,
+                formatted_version,
+                len(crate_info.srcs.to_list()),
+            ),
+        )
+
+    runfiles = ctx.runfiles(
+        files = getattr(ctx.files, "data", []),
+        collect_data = True,
+    )
+
+    # TODO: Remove after some resolution to
+    # https://github.com/bazelbuild/rules_rust/issues/771
+    out_binary = getattr(attr, "out_binary", False)
+
+    providers = [
+        crate_info,
+        dep_info,
+        DefaultInfo(
+            # nb. This field is required for cc_library to depend on our output.
+            files = depset(outputs),
+            runfiles = runfiles,
+            executable = crate_info.output if crate_info.type == "bin" or crate_info.is_test or out_binary else None,
+        ),
+    ]
+    if toolchain.target_arch != "wasm32":
+        providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library)
+    if pdb_file:
+        providers.append(OutputGroupInfo(pdb_file = depset([pdb_file])))
+    if dsym_folder:
+        providers.append(OutputGroupInfo(dsym_folder = depset([dsym_folder])))
+
+    return providers
+
+def _is_dylib(dep):
+    return not bool(dep.static_library or dep.pic_static_library)
+
+def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library):
+    """If the produced crate is suitable yield a CcInfo to allow for interop with cc rules
+
+    Args:
+        ctx (ctx): The rule's context object
+        attr (struct): Attributes to use in gathering CcInfo
+        crate_info (CrateInfo): The CrateInfo provider of the target crate
+        toolchain (rust_toolchain): The current `rust_toolchain`
+        cc_toolchain (CcToolchainInfo): The current `CcToolchainInfo`
+        feature_configuration (FeatureConfiguration): Feature configuration to be queried.
+        interface_library (File): Optional interface library for cdylib crates on Windows.
+
+    Returns:
+        list: A list containing the CcInfo provider
+    """
+
+    # A test will not need to produce CcInfo as nothing can depend on test targets
+    if crate_info.is_test:
+        return []
+
+    # Only generate CcInfo for particular crate types
+    if crate_info.type not in ("staticlib", "cdylib", "rlib", "lib"):
+        return []
+
+    # TODO: Remove after some resolution to
+    # https://github.com/bazelbuild/rules_rust/issues/771
+    if getattr(attr, "out_binary", False):
+        return []
+
+    if crate_info.type == "staticlib":
+        library_to_link = cc_common.create_library_to_link(
+            actions = ctx.actions,
+            feature_configuration = feature_configuration,
+            cc_toolchain = cc_toolchain,
+            static_library = crate_info.output,
+            # TODO(hlopko): handle PIC/NOPIC correctly
+            pic_static_library = crate_info.output,
+        )
+    elif crate_info.type in ("rlib", "lib"):
+        # bazel hard-codes a check for endswith((".a", ".pic.a",
+        # ".lib")) in create_library_to_link, so we work around that
+        # by creating a symlink to the .rlib with a .a extension.
+        dot_a = make_static_lib_symlink(ctx.actions, crate_info.output)
+
+        # TODO(hlopko): handle PIC/NOPIC correctly
+        library_to_link = cc_common.create_library_to_link(
+            actions = ctx.actions,
+            feature_configuration = feature_configuration,
+            cc_toolchain = cc_toolchain,
+            static_library = dot_a,
+            # TODO(hlopko): handle PIC/NOPIC correctly
+            pic_static_library = dot_a,
+        )
+    elif crate_info.type == "cdylib":
+        library_to_link = cc_common.create_library_to_link(
+            actions = ctx.actions,
+            feature_configuration = feature_configuration,
+            cc_toolchain = cc_toolchain,
+            dynamic_library = crate_info.output,
+            interface_library = interface_library,
+        )
+    else:
+        fail("Unexpected case")
+
+    link_input = cc_common.create_linker_input(
+        owner = ctx.label,
+        libraries = depset([library_to_link]),
+    )
+
+    linking_context = cc_common.create_linking_context(
+        # TODO - What to do for no_std?
+        linker_inputs = depset([link_input]),
+    )
+
+    cc_infos = [
+        CcInfo(linking_context = linking_context),
+        toolchain.stdlib_linkflags,
+    ]
+    for dep in getattr(attr, "deps", []):
+        if CcInfo in dep:
+            cc_infos.append(dep[CcInfo])
+
+    if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
+        # TODO: if we already have an rlib in our deps, we could skip this
+        cc_infos.append(toolchain.libstd_and_allocator_ccinfo)
+
+    return [cc_common.merge_cc_infos(cc_infos = cc_infos)]
+
+def add_edition_flags(args, crate):
+    """Adds the Rust edition flag to an arguments object reference
+
+    Args:
+        args (Args): A reference to an Args object
+        crate (CrateInfo): A CrateInfo provider
+    """
+    if crate.edition != "2015":
+        args.add("--edition={}".format(crate.edition))
+
+def _create_extra_input_args(build_info, dep_info):
+    """Gather additional input arguments from transitive dependencies
+
+    Args:
+        build_info (BuildInfo): The BuildInfo provider from the target Crate's set of inputs.
+        dep_info (DepInfo): The Depinfo provider form the target Crate's set of inputs.
+
+    Returns:
+        tuple: A tuple of the following items:
+            - (depset[File]): A list of all build info `OUT_DIR` File objects
+            - (str): The `OUT_DIR` of the current build info
+            - (File): An optional generated environment file from a `cargo_build_script` target
+            - (depset[File]): All direct and transitive build flag files from the current build info.
+    """
+    input_files = []
+
+    # Arguments to the commandline line wrapper that are going to be used
+    # to create the final command line
+    out_dir = None
+    build_env_file = None
+    build_flags_files = []
+
+    if build_info:
+        out_dir = build_info.out_dir.path
+        build_env_file = build_info.rustc_env
+        build_flags_files.append(build_info.flags)
+        build_flags_files.append(build_info.link_flags)
+        input_files.append(build_info.out_dir)
+        input_files.append(build_info.link_flags)
+
+    return (
+        depset(input_files, transitive = [dep_info.link_search_path_files]),
+        out_dir,
+        build_env_file,
+        depset(build_flags_files, transitive = [dep_info.link_search_path_files]),
+    )
+
+def _compute_rpaths(toolchain, output_dir, dep_info, use_pic):
+    """Determine the artifact's rpaths relative to the bazel root for runtime linking of shared libraries.
+
+    Args:
+        toolchain (rust_toolchain): The current `rust_toolchain`
+        output_dir (str): The output directory of the current target
+        dep_info (DepInfo): The current target's dependency info
+        use_pic: If set, prefers pic_static_library over static_library.
+
+    Returns:
+        depset: A set of relative paths from the output directory to each dependency
+    """
+
+    # Windows has no rpath equivalent, so always return an empty depset.
+    if toolchain.os == "windows":
+        return depset([])
+
+    dylibs = [
+        get_preferred_artifact(lib, use_pic)
+        for linker_input in dep_info.transitive_noncrates.to_list()
+        for lib in linker_input.libraries
+        if _is_dylib(lib)
+    ]
+    if not dylibs:
+        return depset([])
+
+    # For darwin, dylibs compiled by Bazel will fail to be resolved at runtime
+    # without a version of Bazel that includes
+    # https://github.com/bazelbuild/bazel/pull/13427. This is known to not be
+    # included in Bazel 4.1 and below.
+    if toolchain.os != "linux" and toolchain.os != "darwin":
+        fail("Runtime linking is not supported on {}, but found {}".format(
+            toolchain.os,
+            dep_info.transitive_noncrates,
+        ))
+
+    # Multiple dylibs can be present in the same directory, so deduplicate them.
+    return depset([
+        relativize(lib_dir, output_dir)
+        for lib_dir in _get_dir_names(dylibs)
+    ])
+
+def _get_dir_names(files):
+    """Returns a list of directory names from the given list of File objects
+
+    Args:
+        files (list): A list of File objects
+
+    Returns:
+        list: A list of directory names for all files
+    """
+    dirs = {}
+    for f in files:
+        dirs[f.dirname] = None
+    return dirs.keys()
+
+def add_crate_link_flags(args, dep_info, force_all_deps_direct = False):
+    """Adds link flags to an Args object reference
+
+    Args:
+        args (Args): An arguments object reference
+        dep_info (DepInfo): The current target's dependency info
+        force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
+            to the commandline as opposed to -L.
+    """
+
+    if force_all_deps_direct:
+        args.add_all(
+            depset(
+                transitive = [
+                    dep_info.direct_crates,
+                    dep_info.transitive_crates,
+                ],
+            ),
+            uniquify = True,
+            map_each = _crate_to_link_flag,
+        )
+    else:
+        # nb. Direct crates are linked via --extern regardless of their crate_type
+        args.add_all(dep_info.direct_crates, map_each = _crate_to_link_flag)
+    args.add_all(
+        dep_info.transitive_crates,
+        map_each = _get_crate_dirname,
+        uniquify = True,
+        format_each = "-Ldependency=%s",
+    )
+
+def _crate_to_link_flag(crate):
+    """A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object
+
+    Args:
+        crate (CrateInfo|AliasableDepInfo): A CrateInfo or an AliasableDepInfo provider
+
+    Returns:
+        list: Link flags for the given provider
+    """
+
+    # This is AliasableDepInfo, we should use the alias as a crate name
+    if hasattr(crate, "dep"):
+        name = crate.name
+        crate_info = crate.dep
+    else:
+        name = crate.name
+        crate_info = crate
+    return ["--extern={}={}".format(name, crate_info.output.path)]
+
+def _get_crate_dirname(crate):
+    """A helper macro used by `add_crate_link_flags` for getting the directory name of the current crate's output path
+
+    Args:
+        crate (CrateInfo): A CrateInfo provider from the current rule
+
+    Returns:
+        str: The directory name of the the output File that will be produced.
+    """
+    return crate.output.dirname
+
+def _portable_link_flags(lib, use_pic, ambiguous_libs):
+    artifact = get_preferred_artifact(lib, use_pic)
+    if ambiguous_libs and artifact.path in ambiguous_libs:
+        artifact = ambiguous_libs[artifact.path]
+    if lib.static_library or lib.pic_static_library:
+        return [
+            "-lstatic=%s" % get_lib_name(artifact),
+        ]
+    elif _is_dylib(lib):
+        return [
+            "-ldylib=%s" % get_lib_name(artifact),
+        ]
+
+    return []
+
+def _make_link_flags_windows(linker_input_and_use_pic_and_ambiguous_libs):
+    linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
+    ret = []
+    for lib in linker_input.libraries:
+        if lib.alwayslink:
+            ret.extend(["-C", "link-arg=/WHOLEARCHIVE:%s" % get_preferred_artifact(lib, use_pic).path])
+        else:
+            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+    return ret
+
+def _make_link_flags_darwin(linker_input_and_use_pic_and_ambiguous_libs):
+    linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
+    ret = []
+    for lib in linker_input.libraries:
+        if lib.alwayslink:
+            ret.extend([
+                "-C",
+                ("link-arg=-Wl,-force_load,%s" % get_preferred_artifact(lib, use_pic).path),
+            ])
+        else:
+            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+    return ret
+
+def _make_link_flags_default(linker_input_and_use_pic_and_ambiguous_libs):
+    linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
+    ret = []
+    for lib in linker_input.libraries:
+        if lib.alwayslink:
+            ret.extend([
+                "-C",
+                "link-arg=-Wl,--whole-archive",
+                "-C",
+                ("link-arg=%s" % get_preferred_artifact(lib, use_pic).path),
+                "-C",
+                "link-arg=-Wl,--no-whole-archive",
+            ])
+        else:
+            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+    return ret
+
+def _libraries_dirnames(linker_input_and_use_pic_and_ambiguous_libs):
+    link_input, use_pic, _ = linker_input_and_use_pic_and_ambiguous_libs
+
+    # De-duplicate names.
+    return depset([get_preferred_artifact(lib, use_pic).dirname for lib in link_input.libraries]).to_list()
+
+def _add_native_link_flags(args, dep_info, linkstamp_outs, ambiguous_libs, crate_type, toolchain, cc_toolchain, feature_configuration):
+    """Adds linker flags for all dependencies of the current target.
+
+    Args:
+        args (Args): The Args struct for a ctx.action
+        dep_info (DepInfo): Dependency Info provider
+        linkstamp_outs (list): Linkstamp outputs of native dependencies
+        ambiguous_libs (dict): Ambiguous libs, see `_disambiguate_libs`
+        crate_type: Crate type of the current target
+        toolchain (rust_toolchain): The current `rust_toolchain`
+        cc_toolchain (CcToolchainInfo): The current `cc_toolchain`
+        feature_configuration (FeatureConfiguration): feature configuration to use with cc_toolchain
+
+    """
+    if crate_type in ["lib", "rlib"]:
+        return
+
+    use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_type)
+
+    if toolchain.os == "windows":
+        make_link_flags = _make_link_flags_windows
+    elif toolchain.os.startswith("mac") or toolchain.os.startswith("darwin"):
+        make_link_flags = _make_link_flags_darwin
+    else:
+        make_link_flags = _make_link_flags_default
+
+    # TODO(hlopko): Remove depset flattening by using lambdas once we are on >=Bazel 5.0
+    args_and_pic_and_ambiguous_libs = [(arg, use_pic, ambiguous_libs) for arg in dep_info.transitive_noncrates.to_list()]
+    args.add_all(args_and_pic_and_ambiguous_libs, map_each = _libraries_dirnames, uniquify = True, format_each = "-Lnative=%s")
+    if ambiguous_libs:
+        # If there are ambiguous libs, the disambiguation symlinks to them are
+        # all created in the same directory. Add it to the library search path.
+        ambiguous_libs_dirname = ambiguous_libs.values()[0].dirname
+        args.add("-Lnative={}".format(ambiguous_libs_dirname))
+
+    args.add_all(args_and_pic_and_ambiguous_libs, map_each = make_link_flags)
+
+    for linkstamp_out in linkstamp_outs:
+        args.add_all(["-C", "link-arg=%s" % linkstamp_out.path])
+
+    if crate_type in ["dylib", "cdylib"]:
+        # For shared libraries we want to link C++ runtime library dynamically
+        # (for example libstdc++.so or libc++.so).
+        args.add_all(
+            cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
+            map_each = _get_dirname,
+            format_each = "-Lnative=%s",
+        )
+        args.add_all(
+            cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
+            map_each = get_lib_name,
+            format_each = "-ldylib=%s",
+        )
+    else:
+        # For all other crate types we want to link C++ runtime library statically
+        # (for example libstdc++.a or libc++.a).
+        args.add_all(
+            cc_toolchain.static_runtime_lib(feature_configuration = feature_configuration),
+            map_each = _get_dirname,
+            format_each = "-Lnative=%s",
+        )
+        args.add_all(
+            cc_toolchain.static_runtime_lib(feature_configuration = feature_configuration),
+            map_each = get_lib_name,
+            format_each = "-lstatic=%s",
+        )
+
+def _get_dirname(file):
+    """A helper function for `_add_native_link_flags`.
+
+    Args:
+        file (File): The target file
+
+    Returns:
+        str: Directory name of `file`
+    """
+    return file.dirname
+
+def _error_format_impl(ctx):
+    """Implementation of the `error_format` rule
+
+    Args:
+        ctx (ctx): The rule's context object
+
+    Returns:
+        list: A list containing the ErrorFormatInfo provider
+    """
+    raw = ctx.build_setting_value
+    if raw not in _error_format_values:
+        fail("{} expected a value in `{}` but got `{}`".format(
+            ctx.label,
+            _error_format_values,
+            raw,
+        ))
+    return [ErrorFormatInfo(error_format = raw)]
+
+error_format = rule(
+    doc = (
+        "Change the [--error-format](https://doc.rust-lang.org/rustc/command-line-arguments.html#option-error-format) " +
+        "flag from the command line with `--@rules_rust//:error_format`. See rustc documentation for valid values."
+    ),
+    implementation = _error_format_impl,
+    build_setting = config.string(flag = True),
+)
+
+def _extra_rustc_flags_impl(ctx):
+    return ExtraRustcFlagsInfo(extra_rustc_flags = ctx.build_setting_value)
+
+extra_rustc_flags = rule(
+    doc = (
+        "Add additional rustc_flags from the command line with `--@rules_rust//:extra_rustc_flags`. " +
+        "This flag should only be used for flags that need to be applied across the entire build. For options that " +
+        "apply to individual crates, use the rustc_flags attribute on the individual crate's rule instead. NOTE: " +
+        "These flags not applied to the exec configuration (proc-macros, cargo_build_script, etc); " +
+        "use `--@rules_rust//:extra_exec_rustc_flags` to apply flags to the exec configuration."
+    ),
+    implementation = _extra_rustc_flags_impl,
+    build_setting = config.string_list(flag = True),
+)
+
+def _extra_exec_rustc_flags_impl(ctx):
+    return ExtraExecRustcFlagsInfo(extra_exec_rustc_flags = ctx.build_setting_value)
+
+extra_exec_rustc_flags = rule(
+    doc = (
+        "Add additional rustc_flags in the exec configuration from the command line with `--@rules_rust//:extra_exec_rustc_flags`. " +
+        "This flag should only be used for flags that need to be applied across the entire build. " +
+        "These flags only apply to the exec configuration (proc-macros, cargo_build_script, etc)."
+    ),
+    implementation = _extra_exec_rustc_flags_impl,
+    build_setting = config.string_list(flag = True),
+)