Add a cargo_build_script_run rule (#320)

This rule allows to run a build.rs file (cf. https://doc.rust-lang.org/cargo/reference/build-scripts.html), get its output (environment variable, rust flags and generated flags) and pass it to `rust_binary` and `rust_library` in order to build crate with build script.

Example of use:

```rust
load("@io_bazel_rules_rust//rust:rust.bzl", "rust_library")
load("@io_bazel_rules_rust//cargo:cargo_build_script.bzl", "cargo_build_script")

cargo_build_script(
    name = "build_script",
    srcs = ["build_script.rs"],
    data = ["test.txt"],
)

rust_library(
    name = "lib",
    srcs = ["lib.rs"],
    deps = [":build_script_run"],
    deps = [":build_script"],
)
```
diff --git a/cargo/BUILD b/cargo/BUILD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cargo/BUILD
diff --git a/cargo/cargo_build_script.bzl b/cargo/cargo_build_script.bzl
new file mode 100644
index 0000000..79326c9
--- /dev/null
+++ b/cargo/cargo_build_script.bzl
@@ -0,0 +1,122 @@
+load("@io_bazel_rules_rust//rust:private/rustc.bzl", "BuildInfo", "rustc_compile_action")
+load("@io_bazel_rules_rust//rust:private/utils.bzl", "find_toolchain")
+load("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary")
+
+def _cargo_build_script_run(ctx, 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")
+    flags_out = ctx.actions.declare_file(ctx.label.name + ".flags")
+    manifest_dir = "%s.runfiles/%s" % (script.path, ctx.label.workspace_name or ctx.workspace_name)
+    env = {
+        "CARGO_MANIFEST_DIR": manifest_dir,
+        "RUSTC": toolchain.rustc.path,
+        "TARGET": toolchain.target_triple,
+        "OUT_DIR": out_dir.path,
+    }
+
+    for f in ctx.attr.crate_features:
+        env["CARGO_FEATURE_" + f.upper().replace("-", "_")] = "1"
+
+    ctx.actions.run(
+        executable = ctx.executable._cargo_build_script_runner,
+        arguments = [script.path, env_out.path, flags_out.path],
+        outputs = [out_dir, env_out, flags_out],
+        tools = [script, ctx.executable._cargo_build_script_runner],
+        inputs = [script, toolchain.rustc],
+        mnemonic = "CargoBuildScriptRun",
+        env = env,
+    )
+
+    return [
+        BuildInfo(
+            out_dir = out_dir,
+            rustc_env = env_out,
+            flags = flags_out,
+        ),
+    ]
+
+def _build_script_impl(ctx):
+    return _cargo_build_script_run(ctx, ctx.executable.script)
+
+_build_script_run = rule(
+    _build_script_impl,
+    attrs = {
+        "script": attr.label(
+            executable = True,
+            allow_files = True,
+            mandatory = True,
+            cfg = "host",
+            doc = "The binary script to run, generally a rust_binary target. ",
+        ),
+        "crate_features": attr.string_list(doc = "The list of rust features that the build script should consider activated."),
+        "_cargo_build_script_runner": attr.label(
+            executable = True,
+            allow_files = True,
+            default = Label("//cargo/cargo_build_script_runner:cargo_build_script_runner"),
+            cfg = "host",
+        ),
+    },
+    toolchains = [
+        "@io_bazel_rules_rust//rust:toolchain",
+    ],
+)
+
+def cargo_build_script(name, crate_features=[], **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`:
+
+    ```
+    [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("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary", "rust_library")
+    load("@io_bazel_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"],
+        # Data are shipped during execution.
+        data = ["src/lib.rs"],
+    )
+
+    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.
+    """
+    rust_binary(name = name + "_script_",
+        crate_features = crate_features,
+        **kwargs,
+    )
+    _build_script_run(
+        name = name,
+        script = ":%s_script_" % name,
+        crate_features = crate_features,
+    )
diff --git a/cargo/cargo_build_script_runner/BUILD b/cargo/cargo_build_script_runner/BUILD
new file mode 100644
index 0000000..15ad96c
--- /dev/null
+++ b/cargo/cargo_build_script_runner/BUILD
@@ -0,0 +1,18 @@
+load("//rust:rust.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"],
+)
diff --git a/cargo/cargo_build_script_runner/bin.rs b/cargo/cargo_build_script_runner/bin.rs
new file mode 100644
index 0000000..7946e93
--- /dev/null
+++ b/cargo/cargo_build_script_runner/bin.rs
@@ -0,0 +1,60 @@
+// 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;
+use std::env;
+use std::fs::{File, canonicalize, create_dir_all};
+use std::io::Write;
+use std::process::{exit, Command};
+
+fn main() {
+    let mut args = env::args().skip(1);
+    let manifest_dir_env = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR was not set");
+    let out_dir_env = env::var("OUT_DIR").expect("OUT_DIR was not set");
+    // For some reason RBE does not creat the output directory, force create it
+    create_dir_all(out_dir_env.clone()).expect(&format!("Failed to create OUT_DIR: {}", out_dir_env));
+    let rustc_env = env::var("RUSTC").expect("RUSTC was not set");
+    // Because of the Bazel's sandbox, bazel cannot provide full path, convert all relative path to correct path.
+    let manifest_dir = canonicalize(&manifest_dir_env).expect(&format!("Failed to canonicalize '{}'", manifest_dir_env));
+    let out_dir = canonicalize(&out_dir_env).expect(&format!("Failed to canonicalize '{}'", out_dir_env));
+    let rustc = canonicalize(&rustc_env).expect(&format!("Failed to canonicalize '{}'", rustc_env));
+    match (args.next(), args.next(), args.next()) {
+        (Some(progname), Some(envfile), Some(flagfile)) => {
+            let output = BuildScriptOutput::from_command(
+                    Command::new(
+                        canonicalize(&progname).expect(&format!("Failed to canonicalize '{}'", progname)))
+                .args(args)
+                .current_dir(manifest_dir.clone())
+                .env("OUT_DIR", out_dir)
+                .env("CARGO_MANIFEST_DIR", manifest_dir)
+                .env("RUSTC", rustc));
+            let mut f =
+                File::create(&envfile).expect(&format!("Unable to create file {}", envfile));
+            f.write_all(BuildScriptOutput::to_env(&output).as_bytes())
+                .expect(&format!("Unable to write file {}", envfile));
+            let mut f =
+                File::create(&flagfile).expect(&format!("Unable to create file {}", flagfile));
+            f.write_all(BuildScriptOutput::to_flags(&output).as_bytes())
+                .expect(&format!("Unable to write file {}", flagfile));
+        }
+        _ => {
+            eprintln!("Usage: $0 progname envfile flagfile [arg1...argn]");
+            exit(1);
+        }
+    }
+}
diff --git a/cargo/cargo_build_script_runner/lib.rs b/cargo/cargo_build_script_runner/lib.rs
new file mode 100644
index 0000000..387626f
--- /dev/null
+++ b/cargo/cargo_build_script_runner/lib.rs
@@ -0,0 +1,169 @@
+// 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, Stdio};
+
+/// 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-env
+    Env(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 {
+            return None;
+        }
+        let param = split[1].trim().to_owned();
+        match split[0] {
+            "cargo:rustc-link-lib" => Some(BuildScriptOutput::LinkLib(param)),
+            "cargo:rustc-link-search" => Some(BuildScriptOutput::LinkSearch(param)),
+            "cargo:rustc-cfg" => Some(BuildScriptOutput::Cfg(param)),
+            "cargo:rustc-flags" => Some(BuildScriptOutput::Flags(param)),
+            "cargo:rustc-env" => Some(BuildScriptOutput::Env(param)),
+            "cargo:rerun-if-changed" | "cargo:rerun-if-env-changed" =>
+                // Ignored because Bazel will re-run if those change all the time.
+                None,
+            "cargo:warning" => {
+                eprintln!("Build Script Warning: {}", split[1]);
+                None
+            },
+            _ => {
+                // Not yet supported:
+                // cargo:KEY=VALUE — Metadata, used by links scripts.
+                // cargo:rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates.
+                eprintln!("Warning: build script returned unsupported directive `{}`", split[0]);
+                None
+            },
+        }
+    }
+
+    /// Converts a [BufReader] into a vector of [BuildScriptOutput] enums.
+    fn 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 from_command(cmd: &mut Command) -> Vec<BuildScriptOutput> {
+        let mut child = cmd.stdout(Stdio::piped()).spawn().expect("Unable to start binary");
+        let ecode = child.wait().expect("failed to wait on child");
+        let reader = BufReader::new(
+                child
+                .stdout
+                .as_mut()
+                .expect("Failed to open stdout"),
+            );
+        assert!(ecode.success());
+        Self::from_reader(reader)
+    }
+
+    /// Convert a vector of [BuildScriptOutput] into a list of environment variables.
+    pub fn to_env(v: &Vec<BuildScriptOutput>) -> String {
+        v.iter()
+            .filter_map(|x| {
+                if let BuildScriptOutput::Env(env) = x {
+                    Some(env.to_owned())
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>()
+            .join(" ")
+    }
+
+    /// Convert a vector of [BuildScriptOutput] into a flagfile.
+    pub fn to_flags(v: &Vec<BuildScriptOutput>) -> String {
+        v.iter()
+            .filter_map(|x| match x {
+                BuildScriptOutput::Cfg(e) => Some(format!("--cfg={}", e)),
+                BuildScriptOutput::Flags(e) => Some(e.to_owned()),
+                BuildScriptOutput::LinkLib(e) => Some(format!("-l{}", e)),
+                BuildScriptOutput::LinkSearch(e) => Some(format!("-L{}", e)),
+                _ => None,
+            })
+            .collect::<Vec<_>>()
+            .join(" ")
+    }
+}
+
+#[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=bleh
+cargo:rustc-env=BAR=FOO
+cargo:rustc-flags=-Lblah
+cargo:invalid=ignored
+cargo:rerun-if-changed=ignored
+cargo:rustc-cfg=feature=awesome
+",
+        );
+        let reader = BufReader::new(buff);
+        let result = BuildScriptOutput::from_reader(reader);
+        assert_eq!(result.len(), 6);
+        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("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!(
+            BuildScriptOutput::to_env(&result),
+            "FOO=BAR BAR=FOO".to_owned()
+        );
+        assert_eq!(
+            BuildScriptOutput::to_flags(&result),
+            "-lsdfsdf -Lbleh -Lblah --cfg=feature=awesome".to_owned()
+        );
+    }
+
+}
diff --git a/examples/cargo/BUILD b/examples/cargo/BUILD
new file mode 100644
index 0000000..6949638
--- /dev/null
+++ b/examples/cargo/BUILD
@@ -0,0 +1,28 @@
+load(
+    "@io_bazel_rules_rust//rust:rust.bzl",
+    "rust_binary",
+    "rust_library",
+    "rust_test",
+)
+load(
+    "@io_bazel_rules_rust//cargo:cargo_build_script.bzl",
+    "cargo_build_script",
+)
+
+cargo_build_script(
+    name = "build_script",
+    srcs = ["build_script.rs"],
+    data = ["test.txt"],
+    crate_features = ["bleh"],
+)
+
+rust_library(
+    name = "lib",
+    srcs = ["lib.rs"],
+    deps = [":build_script"],
+)
+
+rust_test(
+    name = "test",
+    crate = ":lib",
+)
diff --git a/examples/cargo/build_script.rs b/examples/cargo/build_script.rs
new file mode 100644
index 0000000..6c325fe
--- /dev/null
+++ b/examples/cargo/build_script.rs
@@ -0,0 +1,30 @@
+// Copyright 2020 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.
+use std::io::prelude::*;
+use std::fs::File;
+use std::env;
+
+fn main() {
+    let bleh = env::var("CARGO_FEATURE_BLEH").unwrap();
+    let out_dir = env::var("OUT_DIR").unwrap();
+    let data = std::fs::read("cargo/test.txt").unwrap();
+    assert!(!bleh.is_empty());
+    println!(r#"cargo:rustc-env=FOO=BAR
+cargo:rustc-env=BAR=FOO
+cargo:rustc-flags=--cfg=blah="bleh"
+cargo:rustc-flags=--cfg=data="{}"
+cargo:rustc-cfg=foobar"#, std::str::from_utf8(&data).unwrap());
+    let mut file = File::create(format!("{}/hello.world.txt", out_dir)).unwrap();
+    file.write_all(b"Hello, world!").unwrap();
+}
\ No newline at end of file
diff --git a/examples/cargo/lib.rs b/examples/cargo/lib.rs
new file mode 100644
index 0000000..f1f1389
--- /dev/null
+++ b/examples/cargo/lib.rs
@@ -0,0 +1,36 @@
+// Copyright 2020 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.
+
+mod test {
+    #[test]
+    fn test_env_contents() {
+        assert_eq!(env!("FOO"), "BAR");
+        assert_eq!(env!("BAR"), "FOO");
+    }
+
+    #[test]
+    fn test_cfg_contents() {
+        assert!(cfg!(foobar));
+    }
+
+    #[test]
+    fn test_rustc_contents() {
+        assert!(cfg!(blah = "bleh"));
+    }
+
+    #[test]
+    fn test_access_data() {
+        assert!(cfg!(data = "Yeah!"));
+    }
+}
\ No newline at end of file
diff --git a/examples/cargo/test.txt b/examples/cargo/test.txt
new file mode 100644
index 0000000..2eda9ce
--- /dev/null
+++ b/examples/cargo/test.txt
@@ -0,0 +1 @@
+Yeah!
\ No newline at end of file
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 1b93e94..2819f0f 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -39,11 +39,19 @@
     },
 )
 
+BuildInfo = provider(
+    fields = {
+        "flags": """File: file containing additional flags to pass to rustc""",
+        "out_dir": """File: directory containing the result of a build script""",
+        "rustc_env": """File: file containing additional environment variables to set for rustc.""",
+    },
+)
+
 AliasableDep = provider(
     fields = {
         "name": "str",
         "dep": "CrateInfo",
-    }
+    },
 )
 
 DepInfo = provider(
@@ -110,8 +118,9 @@
     transitive_crates = depset()
     transitive_dylibs = depset(order = "topological")  # dylib link flag ordering matters.
     transitive_staticlibs = depset()
+    build_info = None
 
-    aliases = {k.label: v for k,v in aliases.items()}
+    aliases = {k.label: v for k, v in aliases.items()}
     for dep in deps:
         if CrateInfo in dep:
             # This dependency is a rust_library
@@ -134,6 +143,10 @@
             staticlibs = [l for l in libs.to_list() if l.basename.endswith(toolchain.staticlib_ext)]
             transitive_dylibs = depset(transitive = [transitive_dylibs, depset(dylibs)])
             transitive_staticlibs = depset(transitive = [transitive_staticlibs, depset(staticlibs)])
+        elif BuildInfo in dep:
+            if build_info:
+                fail("Several deps are providing build information, only one is allowed in the dependencies", "deps")
+            build_info = dep[BuildInfo]
         else:
             fail("rust targets can only depend on rust_library, rust_*_library or cc_library targets." + str(dep), "deps")
 
@@ -142,12 +155,15 @@
         transitive = [transitive_staticlibs, transitive_dylibs],
     )
 
-    return DepInfo(
-        direct_crates = depset(direct_crates),
-        transitive_crates = transitive_crates,
-        transitive_dylibs = transitive_dylibs,
-        transitive_staticlibs = transitive_staticlibs,
-        transitive_libs = transitive_libs.to_list(),
+    return (
+        DepInfo(
+            direct_crates = depset(direct_crates),
+            transitive_crates = transitive_crates,
+            transitive_dylibs = transitive_dylibs,
+            transitive_staticlibs = transitive_staticlibs,
+            transitive_libs = transitive_libs.to_list(),
+        ),
+        build_info,
     )
 
 def _get_linker_and_args(ctx, rpaths):
@@ -211,7 +227,7 @@
     """
     output_dir = crate_info.output.dirname
 
-    dep_info = collect_deps(
+    dep_info, build_info = collect_deps(
         crate_info.deps,
         crate_info.aliases,
         toolchain,
@@ -233,6 +249,7 @@
         dep_info.transitive_libs +
         [toolchain.rustc] +
         toolchain.crosstool_files +
+        ([build_info.rustc_env, build_info.flags] if build_info else []) +
         ([] if linker_script == None else [linker_script]),
         transitive = [
             toolchain.rustc_lib.files,
@@ -292,7 +309,7 @@
     add_crate_link_flags(args, dep_info)
 
     # We awkwardly construct this command because we cannot reference $PWD from ctx.actions.run(executable=toolchain.rustc)
-    out_dir = _create_out_dir_action(ctx)
+    out_dir = _create_out_dir_action(ctx, build_info.out_dir if build_info else None)
     if out_dir:
         compile_inputs = depset([out_dir], transitive = [compile_inputs])
         out_dir_env = "OUT_DIR=$(pwd)/{} ".format(out_dir.path)
@@ -336,10 +353,12 @@
         dst = crate_info.output.path
         if src != dst:
             maybe_rename = " && /bin/mv {src} {dst}".format(src=src, dst=dst)
-    command = '{}{}{} "$@" --remap-path-prefix="$(pwd)"=__bazel_redacted_pwd{}'.format(
+    command = '{}{}{}{} "$@" --remap-path-prefix="$(pwd)"=__bazel_redacted_pwd{}{}'.format(
+        ("export $(cat %s);" % build_info.rustc_env.path) if build_info else "",
         manifest_dir_env,
         out_dir_env,
         toolchain.rustc.path,
+        (" $(cat '%s')" % build_info.flags.path) if build_info else "",
         maybe_rename,
     )
 
@@ -366,7 +385,10 @@
         arguments = [args],
         mnemonic = "Rustc",
         progress_message = "Compiling Rust {} {}{} ({} files)".format(
-            crate_info.type, ctx.label.name, formatted_version, len(crate_info.srcs)
+            crate_info.type,
+            ctx.label.name,
+            formatted_version,
+            len(crate_info.srcs),
         ),
     )
     runfiles = ctx.runfiles(
@@ -393,19 +415,25 @@
     if crate.edition != "2015":
         args.add("--edition={}".format(crate.edition))
 
-def _create_out_dir_action(ctx):
+def _create_out_dir_action(ctx, build_info_out_dir = None):
     tar_file = getattr(ctx.file, "out_dir_tar", None)
     if not tar_file:
-        return None
-
-    out_dir = ctx.actions.declare_directory(ctx.label.name + ".out_dir")
-    ctx.actions.run_shell(
-        # TODO: Remove system tar usage
-        command = "rm -fr {dir} && mkdir {dir} && tar -xzf {tar} -C {dir}".format(tar = tar_file.path, dir = out_dir.path),
-        inputs = [tar_file],
-        outputs = [out_dir],
-        use_default_shell_env = True,  # Sets PATH for tar and gzip (tar's dependency)
-    )
+        return build_info_out_dir
+    else:
+        out_dir = ctx.actions.declare_directory(ctx.label.name + ".out_dir")
+        ctx.actions.run_shell(
+            # TODO: Remove system tar usage
+            command = ";".join([
+                "rm -fr {dir} && mkdir {dir} && tar -xzf {tar} -C {dir}".format(tar = tar_file.path, dir = out_dir.path),
+            ] + (
+                ["pushd {dir}; cp -fr {in_dir}; popd".format(dir = out_dir.path, in_dir = build_info_out_dir.path)
+                ] if build_info_out_dir else []
+            )),
+            progress_message = "Creating OUT_DIR = %s" % out_dir.path,
+            inputs = [tar_file] + (build_info_out_dir or []),
+            outputs = [out_dir],
+            use_default_shell_env = True,  # Sets PATH for tar and gzip (tar's dependency)
+        )
     return out_dir
 
 def _compute_rpaths(toolchain, output_dir, dep_info):