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/cargo/BUILD.bazel b/cargo/BUILD.bazel
new file mode 100644
index 0000000..8772c31
--- /dev/null
+++ b/cargo/BUILD.bazel
@@ -0,0 +1,15 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+package(default_visibility = ["//visibility:public"])
+
+bzl_library(
+    name = "bzl_lib",
+    srcs = glob(["**/*.bzl"]),
+    deps = ["//cargo/private:bzl_lib"],
+)
+
+alias(
+    name = "rules",
+    actual = ":bzl_lib",
+    deprecation = "Please use the `@rules_rust//cargo:bzl_lib` target instead",
+)
diff --git a/cargo/bootstrap/BUILD.bazel b/cargo/bootstrap/BUILD.bazel
new file mode 100644
index 0000000..a607f60
--- /dev/null
+++ b/cargo/bootstrap/BUILD.bazel
@@ -0,0 +1,20 @@
+load("//rust:defs.bzl", "rust_binary")
+
+exports_files(
+    [
+        "bootstrap_installer.rs",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+# This target is only used to confirm the source code is buildable
+# in a `cargo_bootstrap_repository` rule.
+rust_binary(
+    name = "bootstrap_installer_bin",
+    srcs = [
+        "bootstrap_installer.rs",
+    ],
+    rustc_env = {
+        "RULES_RUST_CARGO_BOOTSTRAP_BINARY": "$(rootpath bootstrap_installer.rs)",
+    },
+)
diff --git a/cargo/bootstrap/bootstrap_installer.rs b/cargo/bootstrap/bootstrap_installer.rs
new file mode 100644
index 0000000..32cb137
--- /dev/null
+++ b/cargo/bootstrap/bootstrap_installer.rs
@@ -0,0 +1,35 @@
+//! A tool for installing bootstrapped Rust binaries into the requested paths.
+
+use std::{
+    env,
+    fs::{copy, create_dir_all},
+    path::PathBuf,
+};
+
+fn install() -> std::io::Result<u64> {
+    let binary = PathBuf::from(env!("RULES_RUST_CARGO_BOOTSTRAP_BINARY"));
+
+    // Consume only the first argument as the destination
+    let dest = PathBuf::from(
+        env::args()
+            .nth(1)
+            .expect("No destination argument provided"),
+    );
+
+    // Create the parent directory structure if it doesn't exist
+    if let Some(parent) = dest.parent() {
+        if !parent.exists() {
+            create_dir_all(parent)?;
+        }
+    }
+
+    // Copy the file to the requested destination
+    copy(binary, dest)
+}
+
+fn main() {
+    if let Err(err) = install() {
+        eprintln!("{:?}", err);
+        std::process::exit(1);
+    };
+}
diff --git a/cargo/cargo_bootstrap.bzl b/cargo/cargo_bootstrap.bzl
new file mode 100644
index 0000000..bbe35c0
--- /dev/null
+++ b/cargo/cargo_bootstrap.bzl
@@ -0,0 +1,339 @@
+"""The `cargo_bootstrap` rule is used for bootstrapping cargo binaries in a repository rule."""
+
+load("//cargo/private:cargo_utils.bzl", "get_host_triple", "get_rust_tools")
+load("//rust:defs.bzl", "rust_common")
+
+_CARGO_BUILD_MODES = [
+    "release",
+    "debug",
+]
+
+_FAIL_MESSAGE = """\
+Process exited with code '{code}'
+# ARGV ########################################################################
+{argv}
+
+# STDOUT ######################################################################
+{stdout}
+
+# STDERR ######################################################################
+{stderr}
+"""
+
+def cargo_bootstrap(
+        repository_ctx,
+        cargo_bin,
+        rustc_bin,
+        binary,
+        cargo_manifest,
+        environment = {},
+        quiet = False,
+        build_mode = "release",
+        target_dir = None,
+        timeout = 600):
+    """A function for bootstrapping a cargo binary within a repository rule
+
+    Args:
+        repository_ctx (repository_ctx): The rule's context object.
+        cargo_bin (path): The path to a Cargo binary.
+        rustc_bin (path): The path to a Rustc binary.
+        binary (str): The binary to build (the `--bin` parameter for Cargo).
+        cargo_manifest (path): The path to a Cargo manifest (Cargo.toml file).
+        environment (dict): Environment variables to use during execution.
+        quiet (bool, optional): Whether or not to print output from the Cargo command.
+        build_mode (str, optional): The build mode to use
+        target_dir (path, optional): The directory in which to produce build outputs
+            (Cargo's --target-dir argument).
+        timeout (int, optional): Maximum duration of the Cargo build command in seconds,
+
+    Returns:
+        path: The path of the built binary within the target directory
+    """
+
+    if not target_dir:
+        target_dir = repository_ctx.path(".")
+
+    args = [
+        cargo_bin,
+        "build",
+        "--bin",
+        binary,
+        "--locked",
+        "--target-dir",
+        target_dir,
+        "--manifest-path",
+        cargo_manifest,
+    ]
+
+    if build_mode not in _CARGO_BUILD_MODES:
+        fail("'{}' is not a supported build mode. Use one of {}".format(build_mode, _CARGO_BUILD_MODES))
+
+    if build_mode == "release":
+        args.append("--release")
+
+    env = dict({
+        "RUSTC": str(rustc_bin),
+    }.items() + environment.items())
+
+    repository_ctx.report_progress("Cargo Bootstrapping {}".format(binary))
+    result = repository_ctx.execute(
+        args,
+        environment = env,
+        quiet = quiet,
+        timeout = timeout,
+    )
+
+    if result.return_code != 0:
+        fail(_FAIL_MESSAGE.format(
+            code = result.return_code,
+            argv = args,
+            stdout = result.stdout,
+            stderr = result.stderr,
+        ))
+
+    extension = ""
+    if "win" in repository_ctx.os.name:
+        extension = ".exe"
+
+    binary_path = "{}/{}{}".format(
+        build_mode,
+        binary,
+        extension,
+    )
+
+    if not repository_ctx.path(binary_path).exists:
+        fail("Failed to produce binary at {}".format(binary_path))
+
+    return binary_path
+
+_BUILD_FILE_CONTENT = """\
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+
+package(default_visibility = ["//visibility:public"])
+
+exports_files([
+    "{binary_name}",
+    "{binary}"
+])
+
+alias(
+    name = "binary",
+    actual = "{binary}",
+)
+
+rust_binary(
+    name = "install",
+    rustc_env = {{
+        "RULES_RUST_CARGO_BOOTSTRAP_BINARY": "$(rootpath {binary})"
+    }},
+    data = [
+        "{binary}",
+    ],
+    srcs = [
+        "@rules_rust//cargo/bootstrap:bootstrap_installer.rs"
+    ],
+)
+"""
+
+def _collect_environ(repository_ctx, host_triple):
+    """Gather environment varialbes to use from the current rule context
+
+    Args:
+        repository_ctx (repository_ctx): The rule's context object.
+        host_triple (str): A string of the current host triple
+
+    Returns:
+        dict: A map of environment variables
+    """
+    env_vars = dict(json.decode(repository_ctx.attr.env.get(host_triple, "{}")))
+
+    # Gather the path for each label and ensure it exists
+    env_labels = dict(json.decode(repository_ctx.attr.env_label.get(host_triple, "{}")))
+    env_labels = {key: repository_ctx.path(Label(value)) for (key, value) in env_labels.items()}
+    for key in env_labels:
+        if not env_labels[key].exists:
+            fail("File for key '{}' does not exist: {}", key, env_labels[key])
+    env_labels = {key: str(value) for (key, value) in env_labels.items()}
+
+    return dict(env_vars.items() + env_labels.items())
+
+def _detect_changes(repository_ctx):
+    """Inspect files that are considered inputs to the build for changes
+
+    Args:
+        repository_ctx (repository_ctx): The rule's context object.
+    """
+    # Simply generating a `path` object consideres the file as 'tracked' or
+    # 'consumed' which means changes to it will trigger rebuilds
+
+    for src in repository_ctx.attr.srcs:
+        repository_ctx.path(src)
+
+    repository_ctx.path(repository_ctx.attr.cargo_lockfile)
+    repository_ctx.path(repository_ctx.attr.cargo_toml)
+
+def _cargo_bootstrap_repository_impl(repository_ctx):
+    # Pretend to Bazel that this rule's input files have been used, so that it will re-run the rule if they change.
+    _detect_changes(repository_ctx)
+
+    if repository_ctx.attr.version in ("beta", "nightly"):
+        version_str = "{}-{}".format(repository_ctx.attr.version, repository_ctx.attr.iso_date)
+    else:
+        version_str = repository_ctx.attr.version
+
+    host_triple = get_host_triple(repository_ctx)
+
+    if repository_ctx.attr.rust_toolchain_repository_template:
+        # buildifier: disable=print
+        print("Warning: `rust_toolchain_repository_template` is deprecated. Please use `rust_toolchain_cargo_template` and `rust_toolchain_rustc_template`")
+        cargo_template = "@{}{}".format(repository_ctx.attr.rust_toolchain_repository_template, "//:bin/{tool}")
+        rustc_template = "@{}{}".format(repository_ctx.attr.rust_toolchain_repository_template, "//:bin/{tool}")
+    else:
+        cargo_template = repository_ctx.attr.rust_toolchain_cargo_template
+        rustc_template = repository_ctx.attr.rust_toolchain_rustc_template
+
+    tools = get_rust_tools(
+        cargo_template = cargo_template,
+        rustc_template = rustc_template,
+        host_triple = host_triple,
+        version = version_str,
+    )
+
+    binary_name = repository_ctx.attr.binary or repository_ctx.name
+
+    # In addition to platform specific environment variables, a common set (indicated by `*`) will always
+    # be gathered.
+    environment = dict(_collect_environ(repository_ctx, "*").items() + _collect_environ(repository_ctx, host_triple.triple).items())
+
+    built_binary = cargo_bootstrap(
+        repository_ctx = repository_ctx,
+        cargo_bin = repository_ctx.path(tools.cargo),
+        rustc_bin = repository_ctx.path(tools.rustc),
+        binary = binary_name,
+        cargo_manifest = repository_ctx.path(repository_ctx.attr.cargo_toml),
+        build_mode = repository_ctx.attr.build_mode,
+        environment = environment,
+        timeout = repository_ctx.attr.timeout,
+    )
+
+    # Create a symlink so that the binary can be accesed via it's target name
+    repository_ctx.symlink(built_binary, binary_name)
+
+    repository_ctx.file("BUILD.bazel", _BUILD_FILE_CONTENT.format(
+        binary_name = binary_name,
+        binary = built_binary,
+    ))
+
+cargo_bootstrap_repository = repository_rule(
+    doc = "A rule for bootstrapping a Rust binary using [Cargo](https://doc.rust-lang.org/cargo/)",
+    implementation = _cargo_bootstrap_repository_impl,
+    attrs = {
+        "binary": attr.string(
+            doc = "The binary to build (the `--bin` parameter for Cargo). If left empty, the repository name will be used.",
+        ),
+        "build_mode": attr.string(
+            doc = "The build mode the binary should be built with",
+            values = [
+                "debug",
+                "release",
+            ],
+            default = "release",
+        ),
+        "cargo_lockfile": attr.label(
+            doc = "The lockfile of the crate_universe resolver",
+            allow_single_file = ["Cargo.lock"],
+            mandatory = True,
+        ),
+        "cargo_toml": attr.label(
+            doc = "The path of the crate_universe resolver manifest (`Cargo.toml` file)",
+            allow_single_file = ["Cargo.toml"],
+            mandatory = True,
+        ),
+        "env": attr.string_dict(
+            doc = (
+                "A mapping of platform triple to a set of environment variables. See " +
+                "[cargo_env](#cargo_env) for usage details. Additionally, the platform triple `*` applies to all platforms."
+            ),
+        ),
+        "env_label": attr.string_dict(
+            doc = (
+                "A mapping of platform triple to a set of environment variables. This " +
+                "attribute differs from `env` in that all variables passed here must be " +
+                "fully qualified labels of files. See [cargo_env](#cargo_env) for usage details. " +
+                "Additionally, the platform triple `*` applies to all platforms."
+            ),
+        ),
+        "iso_date": attr.string(
+            doc = "The iso_date of cargo binary the resolver should use. Note: This can only be set if `version` is `beta` or `nightly`",
+        ),
+        "rust_toolchain_cargo_template": attr.string(
+            doc = (
+                "The template to use for finding the host `cargo` binary. `{version}` (eg. '1.53.0'), " +
+                "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
+                "`{system}` (eg. 'darwin'), and `{tool}` (eg. 'rustc.exe') will be replaced in the string if present."
+            ),
+            default = "@rust_{system}_{arch}//:bin/{tool}",
+        ),
+        "rust_toolchain_repository_template": attr.string(
+            doc = "**Deprecated**: Please use `rust_toolchain_cargo_template` and `rust_toolchain_rustc_template`",
+        ),
+        "rust_toolchain_rustc_template": attr.string(
+            doc = (
+                "The template to use for finding the host `rustc` binary. `{version}` (eg. '1.53.0'), " +
+                "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
+                "`{system}` (eg. 'darwin'), and `{tool}` (eg. 'rustc.exe') will be replaced in the string if present."
+            ),
+            default = "@rust_{system}_{arch}//:bin/{tool}",
+        ),
+        "srcs": attr.label_list(
+            doc = "Souce files of the crate to build. Passing source files here can be used to trigger rebuilds when changes are made",
+            allow_files = True,
+        ),
+        "timeout": attr.int(
+            doc = "Maximum duration of the Cargo build command in seconds",
+            default = 600,
+        ),
+        "version": attr.string(
+            doc = "The version of cargo the resolver should use",
+            default = rust_common.default_version,
+        ),
+        "_cc_toolchain": attr.label(
+            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+        ),
+    },
+)
+
+def cargo_env(env):
+    """A helper for generating platform specific environment variables
+
+    ```python
+    load("@rules_rust//rust:defs.bzl", "rust_common")
+    load("@rules_rust//cargo:defs.bzl", "cargo_bootstrap_repository", "cargo_env")
+
+    cargo_bootstrap_repository(
+        name = "bootstrapped_bin",
+        cargo_lockfile = "//:Cargo.lock",
+        cargo_toml = "//:Cargo.toml",
+        srcs = ["//:resolver_srcs"],
+        version = rust_common.default_version,
+        binary = "my-crate-binary",
+        env = {
+            "x86_64-unknown-linux-gnu": cargo_env({
+                "FOO": "BAR",
+            }),
+        },
+        env_label = {
+            "aarch64-unknown-linux-musl": cargo_env({
+                "DOC": "//:README.md",
+            }),
+        }
+    )
+    ```
+
+    Args:
+        env (dict): A map of environment variables
+
+    Returns:
+        str: A json encoded string of the environment variables
+    """
+    return json.encode(dict(env))
diff --git a/cargo/cargo_build_script.bzl b/cargo/cargo_build_script.bzl
new file mode 100644
index 0000000..69450a2
--- /dev/null
+++ b/cargo/cargo_build_script.bzl
@@ -0,0 +1,396 @@
+# buildifier: disable=module-docstring
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "C_COMPILE_ACTION_NAME")
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
+load("//rust:defs.bzl", "rust_binary", "rust_common")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:rustc.bzl", "BuildInfo", "get_compilation_mode_opts", "get_linker_and_args")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:utils.bzl", "dedent", "expand_dict_value_locations", "find_cc_toolchain", "find_toolchain", "name_to_crate_name")
+
+def get_cc_compile_env(cc_toolchain, feature_configuration):
+    """Gather cc environment variables from the given `cc_toolchain`
+
+    Args:
+        cc_toolchain (cc_toolchain): The current rule's `cc_toolchain`.
+        feature_configuration (FeatureConfiguration): Class used to construct command lines from CROSSTOOL features.
+
+    Returns:
+        dict: Returns environment variables to be set for given action.
+    """
+    compile_variables = cc_common.create_compile_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+    )
+    return cc_common.get_environment_variables(
+        feature_configuration = feature_configuration,
+        action_name = C_COMPILE_ACTION_NAME,
+        variables = compile_variables,
+    )
+
+def _build_script_impl(ctx):
+    """The implementation for the `_build_script_run` rule.
+
+    Args:
+        ctx (ctx): The rules context object
+
+    Returns:
+        list: A list containing a BuildInfo provider
+    """
+    script = ctx.executable.script
+    toolchain = find_toolchain(ctx)
+    out_dir = ctx.actions.declare_directory(ctx.label.name + ".out_dir")
+    env_out = ctx.actions.declare_file(ctx.label.name + ".env")
+    dep_env_out = ctx.actions.declare_file(ctx.label.name + ".depenv")
+    flags_out = ctx.actions.declare_file(ctx.label.name + ".flags")
+    link_flags = ctx.actions.declare_file(ctx.label.name + ".linkflags")
+    link_search_paths = ctx.actions.declare_file(ctx.label.name + ".linksearchpaths")  # rustc-link-search, propagated from transitive dependencies
+    manifest_dir = "%s.runfiles/%s/%s" % (script.path, ctx.label.workspace_name or ctx.workspace_name, ctx.label.package)
+    compilation_mode_opt_level = get_compilation_mode_opts(ctx, toolchain).opt_level
+
+    streams = struct(
+        stdout = ctx.actions.declare_file(ctx.label.name + ".stdout.log"),
+        stderr = ctx.actions.declare_file(ctx.label.name + ".stderr.log"),
+    )
+
+    pkg_name = _name_to_pkg_name(ctx.label.name)
+
+    toolchain_tools = [toolchain.all_files]
+
+    cc_toolchain = find_cpp_toolchain(ctx)
+
+    # Start with the default shell env, which contains any --action_env
+    # settings passed in on the command line.
+    env = dict(ctx.configuration.default_shell_env)
+
+    env.update({
+        "CARGO_CRATE_NAME": name_to_crate_name(pkg_name),
+        "CARGO_MANIFEST_DIR": manifest_dir,
+        "CARGO_PKG_NAME": pkg_name,
+        "HOST": toolchain.exec_triple,
+        "OPT_LEVEL": compilation_mode_opt_level,
+        "RUSTC": toolchain.rustc.path,
+        "TARGET": toolchain.target_flag_value,
+        # OUT_DIR is set by the runner itself, rather than on the action.
+    })
+
+    # This isn't exactly right, but Bazel doesn't have exact views of "debug" and "release", so...
+    env.update({
+        "DEBUG": {"dbg": "true", "fastbuild": "true", "opt": "false"}.get(ctx.var["COMPILATION_MODE"], "true"),
+        "PROFILE": {"dbg": "debug", "fastbuild": "debug", "opt": "release"}.get(ctx.var["COMPILATION_MODE"], "unknown"),
+    })
+
+    if ctx.attr.version:
+        version = ctx.attr.version.split("+")[0].split(".")
+        patch = version[2].split("-") if len(version) > 2 else [""]
+        env["CARGO_PKG_VERSION_MAJOR"] = version[0]
+        env["CARGO_PKG_VERSION_MINOR"] = version[1] if len(version) > 1 else ""
+        env["CARGO_PKG_VERSION_PATCH"] = patch[0]
+        env["CARGO_PKG_VERSION_PRE"] = patch[1] if len(patch) > 1 else ""
+        env["CARGO_PKG_VERSION"] = ctx.attr.version
+
+    # Pull in env vars which may be required for the cc_toolchain to work (e.g. on OSX, the SDK version).
+    # We hope that the linker env is sufficient for the whole cc_toolchain.
+    cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
+    linker, link_args, linker_env = get_linker_and_args(ctx, ctx.attr, cc_toolchain, feature_configuration, None)
+    env.update(**linker_env)
+    env["LD"] = linker
+    env["LDFLAGS"] = " ".join(link_args)
+
+    # MSVC requires INCLUDE to be set
+    cc_env = get_cc_compile_env(cc_toolchain, feature_configuration)
+    include = cc_env.get("INCLUDE")
+    if include:
+        env["INCLUDE"] = include
+
+    if cc_toolchain:
+        toolchain_tools.append(cc_toolchain.all_files)
+
+        cc_executable = cc_toolchain.compiler_executable
+        if cc_executable:
+            env["CC"] = cc_executable
+            env["CXX"] = cc_executable
+        ar_executable = cc_toolchain.ar_executable
+        if ar_executable:
+            env["AR"] = ar_executable
+        if cc_toolchain.sysroot:
+            env["SYSROOT"] = cc_toolchain.sysroot
+
+    # Inform build scripts of rustc flags
+    # https://github.com/rust-lang/cargo/issues/9600
+    env["CARGO_ENCODED_RUSTFLAGS"] = "\\x1f".join([
+        # Allow build scripts to locate the generated sysroot
+        "--sysroot=${{pwd}}/{}".format(toolchain.sysroot),
+    ] + ctx.attr.rustc_flags)
+
+    for f in ctx.attr.crate_features:
+        env["CARGO_FEATURE_" + f.upper().replace("-", "_")] = "1"
+
+    env.update(expand_dict_value_locations(
+        ctx,
+        ctx.attr.build_script_env,
+        getattr(ctx.attr, "data", []) +
+        getattr(ctx.attr, "compile_data", []) +
+        getattr(ctx.attr, "tools", []),
+    ))
+
+    tools = depset(
+        direct = [
+            script,
+            ctx.executable._cargo_build_script_runner,
+        ] + ctx.files.data + ctx.files.tools + ([toolchain.target_json] if toolchain.target_json else []),
+        transitive = toolchain_tools,
+    )
+
+    links = ctx.attr.links or ""
+
+    # dep_env_file contains additional environment variables coming from
+    # direct dependency sys-crates' build scripts. These need to be made
+    # available to the current crate build script.
+    # See https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
+    # for details.
+    args = ctx.actions.args()
+    args.add_all([
+        script.path,
+        links,
+        out_dir.path,
+        env_out.path,
+        flags_out.path,
+        link_flags.path,
+        link_search_paths.path,
+        dep_env_out.path,
+        streams.stdout.path,
+        streams.stderr.path,
+    ])
+    build_script_inputs = []
+    for dep in ctx.attr.deps:
+        if rust_common.dep_info in dep and dep[rust_common.dep_info].dep_env:
+            dep_env_file = dep[rust_common.dep_info].dep_env
+            args.add(dep_env_file.path)
+            build_script_inputs.append(dep_env_file)
+            for dep_build_info in dep[rust_common.dep_info].transitive_build_infos.to_list():
+                build_script_inputs.append(dep_build_info.out_dir)
+
+    ctx.actions.run(
+        executable = ctx.executable._cargo_build_script_runner,
+        arguments = [args],
+        outputs = [out_dir, env_out, flags_out, link_flags, link_search_paths, dep_env_out, streams.stdout, streams.stderr],
+        tools = tools,
+        inputs = build_script_inputs,
+        mnemonic = "CargoBuildScriptRun",
+        progress_message = "Running Cargo build script {}".format(pkg_name),
+        env = env,
+    )
+
+    return [
+        BuildInfo(
+            out_dir = out_dir,
+            rustc_env = env_out,
+            dep_env = dep_env_out,
+            flags = flags_out,
+            link_flags = link_flags,
+            link_search_paths = link_search_paths,
+        ),
+        OutputGroupInfo(streams = depset([streams.stdout, streams.stderr])),
+    ]
+
+_build_script_run = rule(
+    doc = (
+        "A rule for running a crate's `build.rs` files to generate build information " +
+        "which is then used to determine how to compile said crate."
+    ),
+    implementation = _build_script_impl,
+    attrs = {
+        "build_script_env": attr.string_dict(
+            doc = "Environment variables for build scripts.",
+        ),
+        "crate_features": attr.string_list(
+            doc = "The list of rust features that the build script should consider activated.",
+        ),
+        "data": attr.label_list(
+            doc = "Data required by the build script.",
+            allow_files = True,
+        ),
+        "deps": attr.label_list(
+            doc = "The Rust dependencies of the crate",
+            providers = [rust_common.dep_info],
+        ),
+        "links": attr.string(
+            doc = "The name of the native library this crate links against.",
+        ),
+        "rustc_flags": attr.string_list(
+            doc = dedent("""\
+                List of compiler flags passed to `rustc`.
+
+                These strings are subject to Make variable expansion for predefined
+                source/output path variables like `$location`, `$execpath`, and 
+                `$rootpath`. This expansion is useful if you wish to pass a generated
+                file of arguments to rustc: `@$(location //package:target)`.
+            """),
+        ),
+        # The source of truth will be the `cargo_build_script` macro until stardoc
+        # implements documentation inheritence. See https://github.com/bazelbuild/stardoc/issues/27
+        "script": attr.label(
+            doc = "The binary script to run, generally a `rust_binary` target.",
+            executable = True,
+            allow_files = True,
+            mandatory = True,
+            cfg = "exec",
+        ),
+        "tools": attr.label_list(
+            doc = "Tools required by the build script.",
+            allow_files = True,
+            cfg = "exec",
+        ),
+        "version": attr.string(
+            doc = "The semantic version (semver) of the crate",
+        ),
+        "_cargo_build_script_runner": attr.label(
+            executable = True,
+            allow_files = True,
+            default = Label("//cargo/cargo_build_script_runner:cargo_build_script_runner"),
+            cfg = "exec",
+        ),
+        "_cc_toolchain": attr.label(
+            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+        ),
+    },
+    fragments = ["cpp"],
+    toolchains = [
+        str(Label("//rust:toolchain")),
+        "@bazel_tools//tools/cpp:toolchain_type",
+    ],
+    incompatible_use_toolchain_transition = True,
+)
+
+def cargo_build_script(
+        name,
+        crate_features = [],
+        version = None,
+        deps = [],
+        build_script_env = {},
+        data = [],
+        tools = [],
+        links = None,
+        rustc_env = {},
+        rustc_flags = [],
+        visibility = None,
+        tags = None,
+        **kwargs):
+    """Compile and execute a rust build script to generate build attributes
+
+    This rules take the same arguments as rust_binary.
+
+    Example:
+
+    Suppose you have a crate with a cargo build script `build.rs`:
+
+    ```output
+    [workspace]/
+        hello_lib/
+            BUILD
+            build.rs
+            src/
+                lib.rs
+    ```
+
+    Then you want to use the build script in the following:
+
+    `hello_lib/BUILD`:
+    ```python
+    package(default_visibility = ["//visibility:public"])
+
+    load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
+    load("@rules_rust//cargo:cargo_build_script.bzl", "cargo_build_script")
+
+    # This will run the build script from the root of the workspace, and
+    # collect the outputs.
+    cargo_build_script(
+        name = "build_script",
+        srcs = ["build.rs"],
+        # Optional environment variables passed during build.rs compilation
+        rustc_env = {
+           "CARGO_PKG_VERSION": "0.1.2",
+        },
+        # Optional environment variables passed during build.rs execution.
+        # Note that as the build script's working directory is not execroot,
+        # execpath/location will return an absolute path, instead of a relative
+        # one.
+        build_script_env = {
+            "SOME_TOOL_OR_FILE": "$(execpath @tool//:binary)"
+        }
+        # Optional data/tool dependencies
+        data = ["@tool//:binary"],
+    )
+
+    rust_library(
+        name = "hello_lib",
+        srcs = [
+            "src/lib.rs",
+        ],
+        deps = [":build_script"],
+    )
+    ```
+
+    The `hello_lib` target will be build with the flags and the environment variables declared by the \
+    build script in addition to the file generated by it.
+
+    Args:
+        name (str): The name for the underlying rule. This should be the name of the package being compiled, optionally with a suffix of _build_script.
+        crate_features (list, optional): A list of features to enable for the build script.
+        version (str, optional): The semantic version (semver) of the crate.
+        deps (list, optional): The dependencies of the crate.
+        build_script_env (dict, optional): Environment variables for build scripts.
+        data (list, optional): Files needed by the build script.
+        tools (list, optional): Tools (executables) needed by the build script.
+        links (str, optional): Name of the native library this crate links against.
+        rustc_env (dict, optional): Environment variables to set in rustc when compiling the build script.
+        rustc_flags (list, optional): List of compiler flags passed to `rustc`.
+        visibility (list of label, optional): Visibility to apply to the generated build script output.
+        tags: (list of str, optional): Tags to apply to the generated build script output.
+        **kwargs: Forwards to the underlying `rust_binary` rule.
+    """
+
+    # This duplicates the code in _build_script_impl because we need to make these available both when we invoke rustc (this code) and when we run the compiled build script (_build_script_impl).
+    # https://github.com/bazelbuild/rules_rust/issues/661 will hopefully remove this duplication.
+    rustc_env = dict(rustc_env)
+    if "CARGO_PKG_NAME" not in rustc_env:
+        rustc_env["CARGO_PKG_NAME"] = _name_to_pkg_name(name)
+    if "CARGO_CRATE_NAME" not in rustc_env:
+        rustc_env["CARGO_CRATE_NAME"] = name_to_crate_name(_name_to_pkg_name(name))
+
+    binary_tags = [tag for tag in tags or []]
+    if "manual" not in binary_tags:
+        binary_tags.append("manual")
+
+    rust_binary(
+        name = name + "_",
+        crate_features = crate_features,
+        version = version,
+        deps = deps,
+        data = data,
+        rustc_env = rustc_env,
+        rustc_flags = rustc_flags,
+        tags = binary_tags,
+        **kwargs
+    )
+    _build_script_run(
+        name = name,
+        script = ":{}_".format(name),
+        crate_features = crate_features,
+        version = version,
+        build_script_env = build_script_env,
+        links = links,
+        deps = deps,
+        data = data,
+        tools = tools,
+        rustc_flags = rustc_flags,
+        visibility = visibility,
+        tags = tags,
+    )
+
+def _name_to_pkg_name(name):
+    if name.endswith("_build_script"):
+        return name[:-len("_build_script")]
+    return name
diff --git a/cargo/cargo_build_script_runner/BUILD.bazel b/cargo/cargo_build_script_runner/BUILD.bazel
new file mode 100644
index 0000000..11edd45
--- /dev/null
+++ b/cargo/cargo_build_script_runner/BUILD.bazel
@@ -0,0 +1,24 @@
+load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
+
+rust_library(
+    name = "cargo_build_script_output_parser",
+    srcs = ["lib.rs"],
+)
+
+rust_test(
+    name = "test",
+    crate = ":cargo_build_script_output_parser",
+)
+
+rust_binary(
+    name = "cargo_build_script_runner",
+    srcs = ["bin.rs"],
+    visibility = ["//visibility:public"],
+    deps = [":cargo_build_script_output_parser"],
+)
+
+rust_test(
+    name = "bin_test",
+    crate = ":cargo_build_script_runner",
+    deps = [":cargo_build_script_runner"],
+)
diff --git a/cargo/cargo_build_script_runner/bin.rs b/cargo/cargo_build_script_runner/bin.rs
new file mode 100644
index 0000000..58f0363
--- /dev/null
+++ b/cargo/cargo_build_script_runner/bin.rs
@@ -0,0 +1,345 @@
+// 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.
+
+// A simple wrapper around a build_script execution to generate file to reuse
+// by rust_library/rust_binary.
+extern crate cargo_build_script_output_parser;
+
+use cargo_build_script_output_parser::{BuildScriptOutput, CompileAndLinkFlags};
+use std::collections::BTreeMap;
+use std::env;
+use std::fs::{create_dir_all, read_to_string, write};
+use std::path::Path;
+use std::process::Command;
+
+fn run_buildrs() -> Result<(), String> {
+    // We use exec_root.join rather than std::fs::canonicalize, to avoid resolving symlinks, as
+    // some execution strategies and remote execution environments may use symlinks in ways which
+    // canonicalizing them may break them, e.g. by having input files be symlinks into a /cas
+    // directory - resolving these may cause tools which inspect $0, or try to resolve files
+    // relative to themselves, to fail.
+    let exec_root = env::current_dir().expect("Failed to get current directory");
+    let manifest_dir_env = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR was not set");
+    let rustc_env = env::var("RUSTC").expect("RUSTC was not set");
+    let manifest_dir = exec_root.join(&manifest_dir_env);
+    let rustc = exec_root.join(&rustc_env);
+    let Options {
+        progname,
+        crate_links,
+        out_dir,
+        env_file,
+        compile_flags_file,
+        link_flags_file,
+        link_search_paths_file,
+        output_dep_env_path,
+        stdout_path,
+        stderr_path,
+        input_dep_env_paths,
+    } = parse_args()?;
+
+    let out_dir_abs = exec_root.join(&out_dir);
+    // For some reason Google's RBE does not create the output directory, force create it.
+    create_dir_all(&out_dir_abs)
+        .unwrap_or_else(|_| panic!("Failed to make output directory: {:?}", out_dir_abs));
+
+    let target_env_vars =
+        get_target_env_vars(&rustc_env).expect("Error getting target env vars from rustc");
+
+    let mut command = Command::new(exec_root.join(&progname));
+    command
+        .current_dir(&manifest_dir)
+        .envs(target_env_vars)
+        .env("OUT_DIR", out_dir_abs)
+        .env("CARGO_MANIFEST_DIR", manifest_dir)
+        .env("RUSTC", rustc)
+        .env("RUST_BACKTRACE", "full");
+
+    for dep_env_path in input_dep_env_paths.iter() {
+        if let Ok(contents) = read_to_string(dep_env_path) {
+            for line in contents.split('\n') {
+                // split on empty contents will still produce a single empty string in iterable.
+                if line.is_empty() {
+                    continue;
+                }
+                let mut key_val = line.splitn(2, '=');
+                match (key_val.next(), key_val.next()) {
+                    (Some(key), Some(value)) => {
+                        command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy()));
+                    }
+                    _ => {
+                        return Err(
+                            "error: Wrong environment file format, should not happen".to_owned()
+                        )
+                    }
+                }
+            }
+        } else {
+            return Err("error: Dependency environment file unreadable".to_owned());
+        }
+    }
+
+    for compiler_env_var in &["CC", "CXX"] {
+        if let Some(compiler_path) = env::var_os(compiler_env_var) {
+            let mut compiler_path = exec_root.join(compiler_path).into_os_string();
+            if let Some(sysroot_path) = env::var_os("SYSROOT") {
+                compiler_path.push(" --sysroot=");
+                compiler_path.push(&exec_root.join(sysroot_path));
+            }
+            command.env(compiler_env_var, compiler_path);
+        }
+    }
+
+    if let Some(ar_path) = env::var_os("AR") {
+        // The default OSX toolchain uses libtool as ar_executable not ar.
+        // This doesn't work when used as $AR, so simply don't set it - tools will probably fall back to
+        // /usr/bin/ar which is probably good enough.
+        if Path::new(&ar_path).file_name() == Some("libtool".as_ref()) {
+            command.env_remove("AR");
+        } else {
+            command.env("AR", exec_root.join(ar_path));
+        }
+    }
+
+    if let Some(ld_path) = env::var_os("LD") {
+        command.env("LD", exec_root.join(ld_path));
+    }
+
+    // replace env vars with a ${pwd} prefix with the exec_root
+    for (key, value) in env::vars() {
+        let exec_root_str = exec_root.to_str().expect("exec_root not in utf8");
+        if value.contains("${pwd}") {
+            env::set_var(key, value.replace("${pwd}", exec_root_str));
+        }
+    }
+
+    // Bazel does not support byte strings so in order to correctly represent `CARGO_ENCODED_RUSTFLAGS`
+    // the escaped `\x1f` sequences need to be unescaped
+    if let Ok(encoded_rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
+        command.env(
+            "CARGO_ENCODED_RUSTFLAGS",
+            encoded_rustflags.replace("\\x1f", "\x1f"),
+        );
+    }
+
+    let (buildrs_outputs, process_output) = BuildScriptOutput::outputs_from_command(&mut command)
+        .map_err(|process_output| {
+        format!(
+            "Build script process failed{}\n--stdout:\n{}\n--stderr:\n{}",
+            if let Some(exit_code) = process_output.status.code() {
+                format!(" with exit code {}", exit_code)
+            } else {
+                String::new()
+            },
+            String::from_utf8(process_output.stdout)
+                .expect("Failed to parse stdout of child process"),
+            String::from_utf8(process_output.stderr)
+                .expect("Failed to parse stdout of child process"),
+        )
+    })?;
+
+    write(
+        &env_file,
+        BuildScriptOutput::outputs_to_env(&buildrs_outputs, &exec_root.to_string_lossy())
+            .as_bytes(),
+    )
+    .unwrap_or_else(|_| panic!("Unable to write file {:?}", env_file));
+    write(
+        &output_dep_env_path,
+        BuildScriptOutput::outputs_to_dep_env(
+            &buildrs_outputs,
+            &crate_links,
+            &exec_root.to_string_lossy(),
+        )
+        .as_bytes(),
+    )
+    .unwrap_or_else(|_| panic!("Unable to write file {:?}", output_dep_env_path));
+    write(&stdout_path, process_output.stdout)
+        .unwrap_or_else(|_| panic!("Unable to write file {:?}", stdout_path));
+    write(&stderr_path, process_output.stderr)
+        .unwrap_or_else(|_| panic!("Unable to write file {:?}", stderr_path));
+
+    let CompileAndLinkFlags {
+        compile_flags,
+        link_flags,
+        link_search_paths,
+    } = BuildScriptOutput::outputs_to_flags(&buildrs_outputs, &exec_root.to_string_lossy());
+
+    write(&compile_flags_file, compile_flags.as_bytes())
+        .unwrap_or_else(|_| panic!("Unable to write file {:?}", compile_flags_file));
+    write(&link_flags_file, link_flags.as_bytes())
+        .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_flags_file));
+    write(&link_search_paths_file, link_search_paths.as_bytes())
+        .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_search_paths_file));
+    Ok(())
+}
+
+/// A representation of expected command line arguments.
+struct Options {
+    progname: String,
+    crate_links: String,
+    out_dir: String,
+    env_file: String,
+    compile_flags_file: String,
+    link_flags_file: String,
+    link_search_paths_file: String,
+    output_dep_env_path: String,
+    stdout_path: String,
+    stderr_path: String,
+    input_dep_env_paths: Vec<String>,
+}
+
+/// Parses positional comamnd line arguments into a well defined struct
+fn parse_args() -> Result<Options, String> {
+    let mut args = env::args().skip(1);
+
+    // TODO: we should consider an alternative to positional arguments.
+    match (args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next()) {
+        (
+            Some(progname),
+            Some(crate_links),
+            Some(out_dir),
+            Some(env_file),
+            Some(compile_flags_file),
+            Some(link_flags_file),
+            Some(link_search_paths_file),
+            Some(output_dep_env_path),
+            Some(stdout_path),
+            Some(stderr_path),
+        ) => {
+            Ok(Options{
+                progname,
+                crate_links,
+                out_dir,
+                env_file,
+                compile_flags_file,
+                link_flags_file,
+                link_search_paths_file,
+                output_dep_env_path,
+                stdout_path,
+                stderr_path,
+                input_dep_env_paths: args.collect(),
+            })
+        }
+        _ => {
+            Err(format!("Usage: $0 progname crate_links out_dir env_file compile_flags_file link_flags_file link_search_paths_file output_dep_env_path stdout_path stderr_path input_dep_env_paths[arg1...argn]\nArguments passed: {:?}", args.collect::<Vec<String>>()))
+        }
+    }
+}
+
+fn get_target_env_vars<P: AsRef<Path>>(rustc: &P) -> Result<BTreeMap<String, String>, String> {
+    // As done by Cargo when constructing a cargo::core::compiler::build_context::target_info::TargetInfo.
+    let output = Command::new(rustc.as_ref())
+        .arg("--print=cfg")
+        .arg(format!(
+            "--target={}",
+            env::var("TARGET").expect("missing TARGET")
+        ))
+        .output()
+        .map_err(|err| format!("Error running rustc to get target information: {}", err))?;
+    if !output.status.success() {
+        return Err(format!(
+            "Error running rustc to get target information: {:?}",
+            output
+        ));
+    }
+    let stdout = std::str::from_utf8(&output.stdout)
+        .map_err(|err| format!("Non-UTF8 stdout from rustc: {:?}", err))?;
+
+    Ok(parse_rustc_cfg_output(stdout))
+}
+
+fn parse_rustc_cfg_output(stdout: &str) -> BTreeMap<String, String> {
+    let mut values = BTreeMap::new();
+
+    for line in stdout.lines() {
+        if line.starts_with("target_") && line.contains('=') {
+            let mut parts = line.splitn(2, '=');
+            // UNWRAP: Verified that line contains = and split into exactly 2 parts.
+            let key = parts.next().unwrap();
+            let value = parts.next().unwrap();
+            if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
+                values
+                    .entry(key)
+                    .or_insert_with(Vec::new)
+                    .push(value[1..(value.len() - 1)].to_owned());
+            }
+        } else if ["windows", "unix"].contains(&line) {
+            // the 'windows' or 'unix' line received from rustc will be turned
+            // into eg. CARGO_CFG_WINDOWS='' below
+            values.insert(line, vec![]);
+        }
+    }
+
+    values
+        .into_iter()
+        .map(|(key, value)| (format!("CARGO_CFG_{}", key.to_uppercase()), value.join(",")))
+        .collect()
+}
+
+fn main() {
+    std::process::exit(match run_buildrs() {
+        Ok(_) => 0,
+        Err(err) => {
+            // Neatly print errors
+            eprintln!("{}", err);
+            1
+        }
+    });
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn rustc_cfg_parsing() {
+        let macos_output = r#"\
+debug_assertions
+target_arch="x86_64"
+target_endian="little"
+target_env=""
+target_family="unix"
+target_feature="fxsr"
+target_feature="sse"
+target_feature="sse2"
+target_feature="sse3"
+target_feature="ssse3"
+target_os="macos"
+target_pointer_width="64"
+target_vendor="apple"
+unix
+"#;
+        let tree = parse_rustc_cfg_output(macos_output);
+        assert_eq!(tree["CARGO_CFG_UNIX"], "");
+        assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "unix");
+
+        let windows_output = r#"\
+debug_assertions
+target_arch="x86_64"
+target_endian="little"
+target_env="msvc"
+target_family="windows"
+target_feature="fxsr"
+target_feature="sse"
+target_feature="sse2"
+target_os="windows"
+target_pointer_width="64"
+target_vendor="pc"
+windows
+"#;
+        let tree = parse_rustc_cfg_output(windows_output);
+        assert_eq!(tree["CARGO_CFG_WINDOWS"], "");
+        assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "windows");
+    }
+}
diff --git a/cargo/cargo_build_script_runner/lib.rs b/cargo/cargo_build_script_runner/lib.rs
new file mode 100644
index 0000000..b1aa793
--- /dev/null
+++ b/cargo/cargo_build_script_runner/lib.rs
@@ -0,0 +1,291 @@
+// 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.
+
+//! Parse the output of a cargo build.rs script and generate a list of flags and
+//! environment variable for the build.
+use std::io::{BufRead, BufReader, Read};
+use std::process::{Command, Output};
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct CompileAndLinkFlags {
+    pub compile_flags: String,
+    pub link_flags: String,
+    pub link_search_paths: String,
+}
+
+/// Enum containing all the considered return value from the script
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum BuildScriptOutput {
+    /// cargo:rustc-link-lib
+    LinkLib(String),
+    /// cargo:rustc-link-search
+    LinkSearch(String),
+    /// cargo:rustc-cfg
+    Cfg(String),
+    /// cargo:rustc-flags
+    Flags(String),
+    /// cargo:rustc-link-arg
+    LinkArg(String),
+    /// cargo:rustc-env
+    Env(String),
+    /// cargo:VAR=VALUE
+    DepEnv(String),
+}
+
+impl BuildScriptOutput {
+    /// Converts a line into a [BuildScriptOutput] enum.
+    ///
+    /// Examples
+    /// ```rust
+    /// assert_eq!(BuildScriptOutput::new("cargo:rustc-link-lib=lib"), Some(BuildScriptOutput::LinkLib("lib".to_owned())));
+    /// ```
+    fn new(line: &str) -> Option<BuildScriptOutput> {
+        let split = line.splitn(2, '=').collect::<Vec<_>>();
+        if split.len() <= 1 {
+            // Not a cargo directive.
+            return None;
+        }
+        let param = split[1].trim().to_owned();
+        let key_split = split[0].splitn(2, ':').collect::<Vec<_>>();
+        if key_split.len() <= 1 || key_split[0] != "cargo" {
+            // Not a cargo directive.
+            return None;
+        }
+
+        match key_split[1] {
+            "rustc-link-lib" => Some(BuildScriptOutput::LinkLib(param)),
+            "rustc-link-search" => Some(BuildScriptOutput::LinkSearch(param)),
+            "rustc-cfg" => Some(BuildScriptOutput::Cfg(param)),
+            "rustc-flags" => Some(BuildScriptOutput::Flags(param)),
+            "rustc-link-arg" => Some(BuildScriptOutput::LinkArg(param)),
+            "rustc-env" => Some(BuildScriptOutput::Env(param)),
+            "rerun-if-changed" | "rerun-if-env-changed" =>
+            // Ignored because Bazel will re-run if those change all the time.
+            {
+                None
+            }
+            "warning" => {
+                eprint!("Build Script Warning: {}", split[1]);
+                None
+            }
+            "rustc-cdylib-link-arg" | "rustc-link-arg-bin" | "rustc-link-arg-bins" => {
+                // cargo:rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates.
+                // cargo:rustc-link-arg-bin=BIN=FLAG – Passes custom flags to a linker for the binary BIN.
+                // cargo:rustc-link-arg-bins=FLAG – Passes custom flags to a linker for binaries.
+                eprint!(
+                    "Warning: build script returned unsupported directive `{}`",
+                    split[0]
+                );
+                None
+            }
+            _ => {
+                // cargo:KEY=VALUE — Metadata, used by links scripts.
+                Some(BuildScriptOutput::DepEnv(format!(
+                    "{}={}",
+                    key_split[1].to_uppercase(),
+                    param
+                )))
+            }
+        }
+    }
+
+    /// Converts a [BufReader] into a vector of [BuildScriptOutput] enums.
+    fn outputs_from_reader<T: Read>(mut reader: BufReader<T>) -> Vec<BuildScriptOutput> {
+        let mut result = Vec::<BuildScriptOutput>::new();
+        let mut line = String::new();
+        while reader.read_line(&mut line).expect("Cannot read line") != 0 {
+            if let Some(bso) = BuildScriptOutput::new(&line) {
+                result.push(bso);
+            }
+            line.clear();
+        }
+        result
+    }
+
+    /// Take a [Command], execute it and converts its input into a vector of [BuildScriptOutput]
+    pub fn outputs_from_command(
+        cmd: &mut Command,
+    ) -> Result<(Vec<BuildScriptOutput>, Output), Output> {
+        let child_output = cmd.output().expect("Unable to start binary");
+        if child_output.status.success() {
+            let reader = BufReader::new(child_output.stdout.as_slice());
+            let output = Self::outputs_from_reader(reader);
+            Ok((output, child_output))
+        } else {
+            Err(child_output)
+        }
+    }
+
+    /// Convert a vector of [BuildScriptOutput] into a list of environment variables.
+    pub fn outputs_to_env(outputs: &[BuildScriptOutput], exec_root: &str) -> String {
+        outputs
+            .iter()
+            .filter_map(|x| {
+                if let BuildScriptOutput::Env(env) = x {
+                    Some(Self::escape_for_serializing(Self::redact_exec_root(
+                        env, exec_root,
+                    )))
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>()
+            .join("\n")
+    }
+
+    /// Convert a vector of [BuildScriptOutput] into a list of dependencies environment variables.
+    pub fn outputs_to_dep_env(
+        outputs: &[BuildScriptOutput],
+        crate_links: &str,
+        exec_root: &str,
+    ) -> String {
+        let prefix = format!("DEP_{}_", crate_links.replace('-', "_").to_uppercase());
+        outputs
+            .iter()
+            .filter_map(|x| {
+                if let BuildScriptOutput::DepEnv(env) = x {
+                    Some(format!(
+                        "{}{}",
+                        prefix,
+                        Self::escape_for_serializing(Self::redact_exec_root(env, exec_root))
+                    ))
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>()
+            .join("\n")
+    }
+
+    /// Convert a vector of [BuildScriptOutput] into a flagfile.
+    pub fn outputs_to_flags(outputs: &[BuildScriptOutput], exec_root: &str) -> CompileAndLinkFlags {
+        let mut compile_flags = Vec::new();
+        let mut link_flags = Vec::new();
+        let mut link_search_paths = Vec::new();
+
+        for flag in outputs {
+            match flag {
+                BuildScriptOutput::Cfg(e) => compile_flags.push(format!("--cfg={}", e)),
+                BuildScriptOutput::Flags(e) => compile_flags.push(e.to_owned()),
+                BuildScriptOutput::LinkArg(e) => compile_flags.push(format!("-Clink-arg={}", e)),
+                BuildScriptOutput::LinkLib(e) => link_flags.push(format!("-l{}", e)),
+                BuildScriptOutput::LinkSearch(e) => link_search_paths.push(format!("-L{}", e)),
+                _ => {}
+            }
+        }
+
+        CompileAndLinkFlags {
+            compile_flags: compile_flags.join("\n"),
+            link_flags: Self::redact_exec_root(&link_flags.join("\n"), exec_root),
+            link_search_paths: Self::redact_exec_root(&link_search_paths.join("\n"), exec_root),
+        }
+    }
+
+    fn redact_exec_root(value: &str, exec_root: &str) -> String {
+        value.replace(exec_root, "${pwd}")
+    }
+
+    // The process-wrapper treats trailing backslashes as escapes for following newlines.
+    // If the env var ends with a backslash (and accordingly doesn't have a following newline),
+    // escape it so that it doesn't get turned into a newline by the process-wrapper.
+    //
+    // Note that this code doesn't handle newlines in strings - that's because Cargo treats build
+    // script output as single-line-oriented, so stops processing at the end of a line regardless.
+    fn escape_for_serializing(mut value: String) -> String {
+        if value.ends_with('\\') {
+            value.push('\\');
+        }
+        value
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::io::Cursor;
+
+    #[test]
+    fn test_from_read_buffer_to_env_and_flags() {
+        let buff = Cursor::new(
+            "
+cargo:rustc-link-lib=sdfsdf
+cargo:rustc-env=FOO=BAR
+cargo:rustc-link-search=/some/absolute/path/bleh
+cargo:rustc-env=BAR=FOO
+cargo:rustc-flags=-Lblah
+cargo:rerun-if-changed=ignored
+cargo:rustc-cfg=feature=awesome
+cargo:version=123
+cargo:version_number=1010107f
+cargo:include_path=/some/absolute/path/include
+cargo:rustc-env=SOME_PATH=/some/absolute/path/beep
+cargo:rustc-link-arg=-weak_framework
+cargo:rustc-link-arg=Metal
+",
+        );
+        let reader = BufReader::new(buff);
+        let result = BuildScriptOutput::outputs_from_reader(reader);
+        assert_eq!(result.len(), 12);
+        assert_eq!(result[0], BuildScriptOutput::LinkLib("sdfsdf".to_owned()));
+        assert_eq!(result[1], BuildScriptOutput::Env("FOO=BAR".to_owned()));
+        assert_eq!(
+            result[2],
+            BuildScriptOutput::LinkSearch("/some/absolute/path/bleh".to_owned())
+        );
+        assert_eq!(result[3], BuildScriptOutput::Env("BAR=FOO".to_owned()));
+        assert_eq!(result[4], BuildScriptOutput::Flags("-Lblah".to_owned()));
+        assert_eq!(
+            result[5],
+            BuildScriptOutput::Cfg("feature=awesome".to_owned())
+        );
+        assert_eq!(
+            result[6],
+            BuildScriptOutput::DepEnv("VERSION=123".to_owned())
+        );
+        assert_eq!(
+            result[7],
+            BuildScriptOutput::DepEnv("VERSION_NUMBER=1010107f".to_owned())
+        );
+        assert_eq!(
+            result[9],
+            BuildScriptOutput::Env("SOME_PATH=/some/absolute/path/beep".to_owned())
+        );
+        assert_eq!(
+            result[10],
+            BuildScriptOutput::LinkArg("-weak_framework".to_owned())
+        );
+        assert_eq!(result[11], BuildScriptOutput::LinkArg("Metal".to_owned()));
+
+        assert_eq!(
+            BuildScriptOutput::outputs_to_dep_env(&result, "ssh2", "/some/absolute/path"),
+            "DEP_SSH2_VERSION=123\nDEP_SSH2_VERSION_NUMBER=1010107f\nDEP_SSH2_INCLUDE_PATH=${pwd}/include".to_owned()
+        );
+        assert_eq!(
+            BuildScriptOutput::outputs_to_env(&result, "/some/absolute/path"),
+            "FOO=BAR\nBAR=FOO\nSOME_PATH=${pwd}/beep".to_owned()
+        );
+        assert_eq!(
+            BuildScriptOutput::outputs_to_flags(&result, "/some/absolute/path"),
+            CompileAndLinkFlags {
+                // -Lblah was output as a rustc-flags, so even though it probably _should_ be a link
+                // flag, we don't treat it like one.
+                compile_flags:
+                    "-Lblah\n--cfg=feature=awesome\n-Clink-arg=-weak_framework\n-Clink-arg=Metal"
+                        .to_owned(),
+                link_flags: "-lsdfsdf".to_owned(),
+                link_search_paths: "-L${pwd}/bleh".to_owned(),
+            }
+        );
+    }
+}
diff --git a/cargo/defs.bzl b/cargo/defs.bzl
new file mode 100644
index 0000000..0ca086b
--- /dev/null
+++ b/cargo/defs.bzl
@@ -0,0 +1,9 @@
+"""Common definitions for the `@rules_rust//cargo` package"""
+
+load(":cargo_bootstrap.bzl", _cargo_bootstrap_repository = "cargo_bootstrap_repository", _cargo_env = "cargo_env")
+load(":cargo_build_script.bzl", _cargo_build_script = "cargo_build_script")
+
+cargo_bootstrap_repository = _cargo_bootstrap_repository
+cargo_env = _cargo_env
+
+cargo_build_script = _cargo_build_script
diff --git a/cargo/private/BUILD.bazel b/cargo/private/BUILD.bazel
new file mode 100644
index 0000000..6a1aab6
--- /dev/null
+++ b/cargo/private/BUILD.bazel
@@ -0,0 +1,7 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+    name = "bzl_lib",
+    srcs = glob(["**/*.bzl"]),
+    visibility = ["//:__subpackages__"],
+)
diff --git a/cargo/private/cargo_utils.bzl b/cargo/private/cargo_utils.bzl
new file mode 100644
index 0000000..d519576
--- /dev/null
+++ b/cargo/private/cargo_utils.bzl
@@ -0,0 +1,193 @@
+"""Utility functions for the cargo rules"""
+
+load("//rust/platform:triple.bzl", "triple")
+load("//rust/platform:triple_mappings.bzl", "system_to_binary_ext")
+
+_CPU_ARCH_ERROR_MSG = """\
+Command failed with exit code '{code}': {args}
+----------stdout:
+{stdout}
+----------stderr:
+{stderr}
+"""
+
+def _query_cpu_architecture(repository_ctx, expected_archs, is_windows = False):
+    """Detect the host CPU architecture
+
+    Args:
+        repository_ctx (repository_ctx): The repository rule's context object
+        expected_archs (list): A list of expected architecture strings
+        is_windows (bool, optional): If true, the cpu lookup will use the windows method (`wmic` vs `uname`)
+
+    Returns:
+        str: The host's CPU architecture
+    """
+    if is_windows:
+        arguments = ["wmic", "os", "get", "osarchitecture"]
+    else:
+        arguments = ["uname", "-m"]
+
+    result = repository_ctx.execute(arguments)
+
+    if result.return_code:
+        fail(_CPU_ARCH_ERROR_MSG.format(
+            code = result.return_code,
+            args = arguments,
+            stdout = result.stdout,
+            stderr = result.stderr,
+        ))
+
+    if is_windows:
+        # Example output:
+        # OSArchitecture
+        # 64-bit
+        lines = result.stdout.split("\n")
+        arch = lines[1].strip()
+
+        # Translate 64-bit to a compatible rust platform
+        # https://doc.rust-lang.org/nightly/rustc/platform-support.html
+        if arch == "64-bit":
+            arch = "x86_64"
+    else:
+        arch = result.stdout.strip("\n")
+
+        # Correct the arm architecture for macos
+        if "mac" in repository_ctx.os.name and arch == "arm64":
+            arch = "aarch64"
+
+    if not arch in expected_archs:
+        fail("{} is not a expected cpu architecture {}\n{}".format(
+            arch,
+            expected_archs,
+            result.stdout,
+        ))
+
+    return arch
+
+def get_host_triple(repository_ctx, abi = None):
+    """Query host information for the appropriate triples for the crate_universe resolver
+
+    Args:
+        repository_ctx (repository_ctx): The rule's repository_ctx
+        abi (str): Since there's no consistent way to check for ABI, this info
+            may be explicitly provided
+
+    Returns:
+        struct: A triple struct, see `@rules_rust//rust/platform:triple.bzl`
+    """
+
+    # Detect the host's cpu architecture
+
+    supported_architectures = {
+        "linux": ["aarch64", "x86_64"],
+        "macos": ["aarch64", "x86_64"],
+        "windows": ["x86_64"],
+    }
+
+    if "linux" in repository_ctx.os.name:
+        cpu = _query_cpu_architecture(repository_ctx, supported_architectures["linux"])
+        return triple("{}-unknown-linux-{}".format(
+            cpu,
+            abi or "gnu",
+        ))
+
+    if "mac" in repository_ctx.os.name:
+        cpu = _query_cpu_architecture(repository_ctx, supported_architectures["macos"])
+        return triple("{}-apple-darwin".format(cpu))
+
+    if "win" in repository_ctx.os.name:
+        cpu = _query_cpu_architecture(repository_ctx, supported_architectures["windows"], True)
+        return triple("{}-pc-windows-{}".format(
+            cpu,
+            abi or "msvc",
+        ))
+
+    fail("Unhandled host os: {}", repository_ctx.os.name)
+
+def _resolve_repository_template(
+        template,
+        abi = None,
+        arch = None,
+        system = None,
+        tool = None,
+        triple = None,
+        vendor = None,
+        version = None):
+    """Render values into a repository template string
+
+    Args:
+        template (str): The template to use for rendering
+        abi (str, optional): The host ABI
+        arch (str, optional): The host CPU architecture
+        system (str, optional): The host system name
+        tool (str, optional): The tool to expect in the particular repository.
+            Eg. `cargo`, `rustc`, `stdlib`.
+        triple (str, optional): The host triple
+        vendor (str, optional): The host vendor name
+        version (str, optional): The Rust version used in the toolchain.
+    Returns:
+        string: The resolved template string based on the given parameters
+    """
+    if abi:
+        template = template.replace("{abi}", abi)
+
+    if arch:
+        template = template.replace("{arch}", arch)
+
+    if system:
+        template = template.replace("{system}", system)
+
+    if tool:
+        template = template.replace("{tool}", tool)
+
+    if triple:
+        template = template.replace("{triple}", triple)
+
+    if vendor:
+        template = template.replace("{vendor}", vendor)
+
+    if version:
+        template = template.replace("{version}", version)
+
+    return template
+
+def get_rust_tools(cargo_template, rustc_template, host_triple, version):
+    """Retrieve `cargo` and `rustc` labels based on the host triple.
+
+    Args:
+        cargo_template (str): A template used to identify the label of the host `cargo` binary.
+        rustc_template (str): A template used to identify the label of the host `rustc` binary.
+        host_triple (struct): The host's triple. See `@rules_rust//rust/platform:triple.bzl`.
+        version (str): The version of Cargo+Rustc to use.
+
+    Returns:
+        struct: A struct containing the labels of expected tools
+    """
+    extension = system_to_binary_ext(host_triple.system)
+
+    cargo_label = Label(_resolve_repository_template(
+        template = cargo_template,
+        version = version,
+        triple = host_triple.triple,
+        arch = host_triple.arch,
+        vendor = host_triple.vendor,
+        system = host_triple.system,
+        abi = host_triple.abi,
+        tool = "cargo" + extension,
+    ))
+
+    rustc_label = Label(_resolve_repository_template(
+        template = rustc_template,
+        version = version,
+        triple = host_triple.triple,
+        arch = host_triple.arch,
+        vendor = host_triple.vendor,
+        system = host_triple.system,
+        abi = host_triple.abi,
+        tool = "rustc" + extension,
+    ))
+
+    return struct(
+        cargo = cargo_label,
+        rustc = rustc_label,
+    )