Use a c++ wrapper to call rustc instead of shell (#354)

diff --git a/cargo/cargo_build_script_runner/lib.rs b/cargo/cargo_build_script_runner/lib.rs
index c203dae..c810c6d 100644
--- a/cargo/cargo_build_script_runner/lib.rs
+++ b/cargo/cargo_build_script_runner/lib.rs
@@ -129,7 +129,7 @@
                 }
             })
             .collect::<Vec<_>>()
-            .join(" ")
+            .join("\n")
     }
 
     /// Convert a vector of [BuildScriptOutput] into a list of dependencies environment variables.
@@ -151,7 +151,7 @@
                 }
             })
             .collect::<Vec<_>>()
-            .join(" ")
+            .join("\n")
     }
 
     /// Convert a vector of [BuildScriptOutput] into a flagfile.
@@ -169,8 +169,8 @@
             }
         }
         CompileAndLinkFlags {
-            compile_flags: compile_flags.join(" "),
-            link_flags: link_flags.join(" ").replace(exec_root, "${EXEC_ROOT}"),
+            compile_flags: compile_flags.join("\n"),
+            link_flags: link_flags.join("\n").replace(exec_root, "<EXEC_ROOT>"),
         }
     }
 }
@@ -212,19 +212,19 @@
 
         assert_eq!(
             BuildScriptOutput::to_dep_env(&result, "my-crate-sys"),
-            "DEP_MY_CRATE_VERSION=123 DEP_MY_CRATE_VERSION_NUMBER=1010107f".to_owned()
+            "DEP_MY_CRATE_VERSION=123\nDEP_MY_CRATE_VERSION_NUMBER=1010107f".to_owned()
         );
         assert_eq!(
             BuildScriptOutput::to_env(&result),
-            "FOO=BAR BAR=FOO".to_owned()
+            "FOO=BAR\nBAR=FOO".to_owned()
         );
         assert_eq!(
             BuildScriptOutput::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 --cfg=feature=awesome".to_owned(),
-                link_flags: "-lsdfsdf -L${EXEC_ROOT}/bleh".to_owned(),
+                compile_flags: "-Lblah\n--cfg=feature=awesome".to_owned(),
+                link_flags: "-lsdfsdf\n-L<EXEC_ROOT>/bleh".to_owned(),
             }
         );
     }
diff --git a/proto/proto.bzl b/proto/proto.bzl
index 067df0e..d5aea2b 100644
--- a/proto/proto.bzl
+++ b/proto/proto.bzl
@@ -220,6 +220,12 @@
             default = PROTO_COMPILE_DEPS,
         ),
         "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
+        "_rust_tool_wrapper": attr.label(
+            default = "@io_bazel_rules_rust//rust/private/rust_tool_wrapper:rust_tool_wrapper",
+            executable = True,
+            allow_single_file = True,
+            cfg = "exec",
+        ),
         "_optional_output_wrapper": attr.label(
             executable = True,
             cfg = "host",
@@ -280,6 +286,12 @@
             default = GRPC_COMPILE_DEPS,
         ),
         "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
+        "_rust_tool_wrapper": attr.label(
+            default = "@io_bazel_rules_rust//rust/private/rust_tool_wrapper:rust_tool_wrapper",
+            executable = True,
+            allow_single_file = True,
+            cfg = "exec",
+        ),
         "_optional_output_wrapper": attr.label(
             executable = True,
             cfg = "host",
diff --git a/rust/platform/triple_mappings.bzl b/rust/platform/triple_mappings.bzl
index 82508a9..d6f7b3d 100644
--- a/rust/platform/triple_mappings.bzl
+++ b/rust/platform/triple_mappings.bzl
@@ -43,7 +43,10 @@
     "darwin": "",
     "windows": ".exe",
     "emscripten": ".js",
-    "unknown": "",
+    # This is currently a hack allowing us to have the proper
+    # generated extension for the wasm target, similarly to the
+    # windows target
+    "unknown": ".wasm",
 }
 
 _SYSTEM_TO_STATICLIB_EXT = {
diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl
index 83ad84f..b5162d6 100644
--- a/rust/private/clippy.bzl
+++ b/rust/private/clippy.bzl
@@ -18,7 +18,6 @@
     "collect_deps",
     "collect_inputs",
     "construct_arguments",
-    "construct_compile_command",
 )
 load(
     "@io_bazel_rules_rust//rust:private/rust.bzl",
@@ -60,41 +59,34 @@
         toolchain,
     )
 
-    compile_inputs, prep_commands, dynamic_env, dynamic_build_flags = collect_inputs(
+    compile_inputs, out_dir, build_env_file, build_flags_files = collect_inputs(
         ctx,
         ctx.rule.file,
         ctx.rule.files,
         toolchain,
         crate_info,
         dep_info,
-        build_info
+        build_info,
     )
 
-    args, env, dynamic_env = construct_arguments(
-        ctx,
-        ctx.rule.file,
-        toolchain,
-        crate_info,
-        dep_info,
-        output_hash = repr(hash(root.path)),
-        rust_flags = [],
-        dynamic_env = dynamic_env)
-
     # A marker file indicating clippy has executed successfully.
     # This file is necessary because "ctx.actions.run" mandates an output.
     clippy_marker = ctx.actions.declare_file(ctx.label.name + "_clippy.ok")
 
-    command = construct_compile_command(
+    args, env = construct_arguments(
         ctx,
-        toolchain.clippy_driver.path,
+        ctx.file,
         toolchain,
+        toolchain.clippy_driver.path,
         crate_info,
-        build_info,
         dep_info,
-        prep_commands,
-        dynamic_env,
-        dynamic_build_flags,
-    ) + (" && touch %s" % clippy_marker.path)
+        output_hash = repr(hash(root.path)),
+        rust_flags = [],
+        out_dir = out_dir,
+        build_env_file = build_env_file,
+        build_flags_files = build_flags_files,
+        maker_path = clippy_marker.path,
+    )
 
     # Deny the default-on clippy warning levels.
     #
@@ -102,12 +94,12 @@
     # result of the aspect to be "success", and Clippy won't be re-triggered
     # unless the source file is modified.
     args.add("-Dclippy::style")
-    args.add("-Dclippy::correctness");
-    args.add("-Dclippy::complexity");
-    args.add("-Dclippy::perf");
+    args.add("-Dclippy::correctness")
+    args.add("-Dclippy::complexity")
+    args.add("-Dclippy::perf")
 
-    ctx.actions.run_shell(
-        command = command,
+    ctx.actions.run(
+        executable = ctx.executable._rust_tool_wrapper,
         inputs = compile_inputs,
         outputs = [clippy_marker],
         env = env,
@@ -131,10 +123,16 @@
         "_cc_toolchain": attr.label(
             default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
         ),
+        "_rust_tool_wrapper": attr.label(
+            default = "@io_bazel_rules_rust//rust/private/rust_tool_wrapper:rust_tool_wrapper",
+            executable = True,
+            allow_single_file = True,
+            cfg = "exec",
+        ),
     },
     toolchains = [
         "@io_bazel_rules_rust//rust:toolchain",
-        "@bazel_tools//tools/cpp:toolchain_type"
+        "@bazel_tools//tools/cpp:toolchain_type",
     ],
     implementation = _clippy_aspect_impl,
     doc = """
@@ -166,7 +164,6 @@
 $ bazel build --aspects=@io_bazel_rules_rust//rust:rust.bzl%rust_clippy_aspect \
               --output_groups=clippy_checks //hello_lib:all
 """,
-
 )
 
 def _rust_clippy_rule_impl(ctx):
@@ -176,7 +173,7 @@
 rust_clippy = rule(
     implementation = _rust_clippy_rule_impl,
     attrs = {
-        'deps': attr.label_list(aspects = [rust_clippy_aspect]),
+        "deps": attr.label_list(aspects = [rust_clippy_aspect]),
     },
     doc = """
 Executes the clippy checker on a specific target.
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index b92cde5..1d26e51 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -155,10 +155,7 @@
     toolchain = find_toolchain(ctx)
     crate_name = ctx.label.name.replace("-", "_")
 
-    if (toolchain.target_arch == "wasm32"):
-        output = ctx.actions.declare_file(ctx.label.name + ".wasm")
-    else:
-        output = ctx.actions.declare_file(ctx.label.name)
+    output = ctx.actions.declare_file(ctx.label.name + toolchain.binary_ext)
 
     crate_type = getattr(ctx.attr, "crate_type")
 
@@ -364,6 +361,12 @@
         ],
     ),
     "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
+    "_rust_tool_wrapper": attr.label(
+        default = "@io_bazel_rules_rust//rust/private/rust_tool_wrapper:rust_tool_wrapper",
+        executable = True,
+        allow_single_file = True,
+        cfg = "exec",
+    ),
 }
 
 _rust_library_attrs = {
diff --git a/rust/private/rust_tool_wrapper/.clang-format b/rust/private/rust_tool_wrapper/.clang-format
new file mode 100644
index 0000000..8a0f2ae
--- /dev/null
+++ b/rust/private/rust_tool_wrapper/.clang-format
@@ -0,0 +1,2 @@
+---

+BasedOnStyle:  Google

diff --git a/rust/private/rust_tool_wrapper/BUILD b/rust/private/rust_tool_wrapper/BUILD
new file mode 100644
index 0000000..67fce4d
--- /dev/null
+++ b/rust/private/rust_tool_wrapper/BUILD
@@ -0,0 +1,63 @@
+config_setting(

+    name = "windows",

+    constraint_values = [

+        "@platforms//os:windows",

+    ],

+)

+

+config_setting(

+    name = "linux",

+    constraint_values = [

+        "@platforms//os:linux",

+    ],

+)

+

+config_setting(

+    name = "macos",

+    constraint_values = [

+        "@platforms//os:macos",

+    ],

+)

+

+config_setting(

+    name = "freebsd",

+    constraint_values = [

+        "@platforms//os:freebsd",

+    ],

+)

+

+config_setting(

+    name = "openbsd",

+    constraint_values = [

+        "@platforms//os:openbsd",

+    ],

+)

+

+POSIX_SRCS = [

+    "system_posix.cc",

+]

+

+cc_binary(

+    name = "rust_tool_wrapper",

+    srcs = [

+        "rust_tool_wrapper.cc",

+        "system.h",

+        "system.cc",

+    ] + select({

+        ":windows": [

+            "system_windows.cc",

+        ],

+        ":linux": POSIX_SRCS,

+        ":macos": POSIX_SRCS,

+        ":freebsd": POSIX_SRCS,

+        ":openbsd": POSIX_SRCS,

+    }),

+    defines = [] + select({

+        ":windows": [

+            "UNICODE",

+            "_UNICODE",

+        ],

+        "//conditions:default": [],

+    }),

+    visibility = ["//visibility:public"],

+)

diff --git a/rust/private/rust_tool_wrapper/rust_tool_wrapper.cc b/rust/private/rust_tool_wrapper/rust_tool_wrapper.cc
new file mode 100644
index 0000000..db4dc59
--- /dev/null
+++ b/rust/private/rust_tool_wrapper/rust_tool_wrapper.cc
@@ -0,0 +1,95 @@
+#include <fstream>

+#include <iostream>

+#include <streambuf>

+#include <string>

+

+#include "rust/private/rust_tool_wrapper/system.h"

+

+// Simple rust tools wrapper allowing us to prepare the context to call rustc or

+// clippy. Since it's only used as a private implementation detail of a rule and

+// not user invoked we don't bother with error checking.

+#if defined(RTW_WIN_UNICODE)

+int wmain(int argc, const wchar_t* argv[], const wchar_t* envp[]) {

+#else

+int main(int argc, const char* argv[], const char* envp[]) {

+#endif  // defined(RTW_WIN_UNICODE)

+

+  using namespace rust_tool_wrapper;

+

+  // Parse args.

+  System::StrType tool_path;

+  System::StrType out_dir;

+  System::StrType rename_from;

+  System::StrType rename_to;

+  System::StrType maker_path;

+  System::Arguments arguments;

+  System::EnvironmentBlock environment_block;

+

+  for (int i = 1; i < argc; ++i) {

+    System::StrType arg = argv[i];

+    if (arg == RTW_SYS_STR_LITERAL("--tool-path")) {

+      tool_path = argv[++i];

+    } else if (arg == RTW_SYS_STR_LITERAL("--out-dir")) {

+      out_dir = System::JoinPaths(System::GetWorkingDirectory(), argv[++i]);

+      environment_block.push_back(System::ComposeEnvironmentVariable(

+          RTW_SYS_STR_LITERAL("OUT_DIR"), out_dir));

+    } else if (arg == RTW_SYS_STR_LITERAL("--build-env-file")) {

+      std::ifstream env_file(argv[++i]);

+      std::string line;

+      while (std::getline(env_file, line)) {

+        environment_block.push_back(System::ToStrType(line));

+      }

+    } else if (arg == RTW_SYS_STR_LITERAL("--build-flags-file")) {

+      std::ifstream env_file(argv[++i]);

+      std::string line;

+      while (std::getline(env_file, line)) {

+        // replace <EXEC_ROOT> by the exec root

+        const System::StrType token = RTW_SYS_STR_LITERAL("<EXEC_ROOT>");

+        System::StrType sys_line = System::ToStrType(line);

+        std::size_t pos = sys_line.find(token);

+        if (pos != std::string::npos) {

+          sys_line.replace(pos, token.size(), System::GetWorkingDirectory());

+        }

+        arguments.push_back(sys_line);

+      }

+    } else if (arg == RTW_SYS_STR_LITERAL("--package-dir")) {

+      environment_block.push_back(System::ComposeEnvironmentVariable(

+          RTW_SYS_STR_LITERAL("CARGO_MANIFEST_DIR"),

+          System::JoinPaths(System::GetWorkingDirectory(), argv[++i])));

+    } else if (arg == RTW_SYS_STR_LITERAL("--maker-path")) {

+      maker_path = argv[++i];

+    } else if (arg == RTW_SYS_STR_LITERAL("--rename")) {

+      rename_from = argv[++i];

+      rename_to = argv[++i];

+    } else if (arg == RTW_SYS_STR_LITERAL("--")) {

+      for (++i; i < argc; ++i) {

+        arguments.push_back(argv[i]);

+      }

+    }

+  }

+

+  for (int i = 0; envp[i] != nullptr; ++i) {

+    environment_block.push_back(envp[i]);

+  }

+

+  // This is to be able to benefit from caching when using RBE

+  arguments.push_back(RTW_SYS_STR_LITERAL("--remap-path-prefix=") +

+                      System::GetWorkingDirectory() +

+                      RTW_SYS_STR_LITERAL("=."));

+

+  int exit_code = System::Exec(tool_path, arguments, environment_block);

+  if (exit_code == 0) {

+    if (!maker_path.empty()) {

+      std::ofstream file(maker_path);

+    }

+

+    // we perform a rename if necessary

+    if (!rename_from.empty() && !rename_to.empty()) {

+      std::ifstream source(rename_from, std::ios::binary);

+      std::ofstream dest(rename_to, std::ios::binary);

+      dest << source.rdbuf();

+    }

+  }

+

+  return exit_code;

+}

diff --git a/rust/private/rust_tool_wrapper/system.cc b/rust/private/rust_tool_wrapper/system.cc
new file mode 100644
index 0000000..f520f51
--- /dev/null
+++ b/rust/private/rust_tool_wrapper/system.cc
@@ -0,0 +1,26 @@
+#include "rust/private/rust_tool_wrapper/system.h"

+

+#if defined(RTW_WIN_UNICODE)

+#include <codecvt>

+#include <locale>

+#endif  // defined(RTW_WIN_UNICODE)

+

+namespace rust_tool_wrapper {

+

+System::StrType System::ToStrType(const std::string& string) {

+#if defined(RTW_WIN_UNICODE)

+  return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(string);

+#else

+  return string;

+#endif  // defined(RTW_WIN_UNICODE)

+}

+

+System::StrType System::ComposeEnvironmentVariable(const StrType& key,

+                                                   const StrType& value) {

+  StrType env = key;

+  env.push_back(RTW_SYS_STR_LITERAL('='));

+  env += value;

+  return env;

+}

+

+}  // namespace rust_tool_wrapper

diff --git a/rust/private/rust_tool_wrapper/system.h b/rust/private/rust_tool_wrapper/system.h
new file mode 100644
index 0000000..fc87e68
--- /dev/null
+++ b/rust/private/rust_tool_wrapper/system.h
@@ -0,0 +1,54 @@
+#ifndef RUST_PRIVATE_RUSTC_WRAPPER_SYSTEM_H_

+#define RUST_PRIVATE_RUSTC_WRAPPER_SYSTEM_H_

+

+#include <string>

+#include <vector>

+

+#if defined(_WIN32) && defined(UNICODE)

+#define RTW_WIN_UNICODE

+#endif  // defined(_WIN32) && defined(UNICODE)

+

+#if defined(RTW_WIN_UNICODE)

+#define RTW_SYS_STR_LITERAL(str) L##str

+#else

+#define RTW_SYS_STR_LITERAL(str) str

+#endif

+

+namespace rust_tool_wrapper {

+

+class System {

+ public:

+#if defined(RTW_WIN_UNICODE)

+  using StrType = std::wstring;

+#else

+  using StrType = std::string;

+#endif  // defined(RTW_WIN_UNICODE)

+

+  using Arguments = std::vector<StrType>;

+  using EnvironmentBlock = std::vector<StrType>;

+

+ public:

+  // Converts to the system string format

+  static StrType ToStrType(const std::string& string);

+

+  // Joins an environment variable in a single string

+  static StrType ComposeEnvironmentVariable(const StrType& key,

+                                            const StrType& value);

+

+  // Gets the working directory of the current process

+  static StrType GetWorkingDirectory();

+

+  // Joins paths using the system convention 

+  static StrType JoinPaths(const StrType& path1, const StrType& path2);

+

+  // Simple function to execute a process that inherits all the current

+  // process handles.

+  // Even if the function doesn't modify global state it is not reentrant

+  // It is meant to be called once during the lifetime of the parent process

+  static int Exec(const StrType& executable, const Arguments& arguments,

+                  const EnvironmentBlock& environment_block);

+};

+

+}  // namespace rust_tool_wrapper

+

+#endif  // RUST_PRIVATE_RUSTC_WRAPPER_SYSTEM_H_

diff --git a/rust/private/rust_tool_wrapper/system_posix.cc b/rust/private/rust_tool_wrapper/system_posix.cc
new file mode 100644
index 0000000..eb12918
--- /dev/null
+++ b/rust/private/rust_tool_wrapper/system_posix.cc
@@ -0,0 +1,64 @@
+#include "rust/private/rust_tool_wrapper/system.h"

+

+// posix headers

+#include <sys/stat.h>

+#include <sys/types.h>

+#include <sys/wait.h>

+#include <unistd.h>

+

+#include <iostream>

+#include <vector>

+

+namespace rust_tool_wrapper {

+

+System::StrType System::GetWorkingDirectory() {

+  const size_t kMaxBufferLength = 4096;

+  char cwd[kMaxBufferLength];

+  if (getcwd(cwd, sizeof(cwd)) == NULL) {

+    return System::StrType{};

+  }

+  return System::StrType{cwd};

+}

+

+System::StrType System::JoinPaths(const StrType &path1, const StrType &path2) {

+  return path1 + "/" + path2;

+}

+

+int System::Exec(const System::StrType &executable,

+                 const System::Arguments &arguments,

+                 const System::EnvironmentBlock &environment_block) {

+  pid_t child_pid = fork();

+  if (child_pid < 0) {

+    std::cerr << "error: Failed to fork the current process." << std::endl;

+    return -1;

+  } else if (child_pid == 0) {

+    std::vector<char *> argv;

+    std::string argv0 = JoinPaths(GetWorkingDirectory(), executable);

+    argv.push_back(&argv0[0]);

+    for (const StrType &argument : arguments) {

+      argv.push_back(const_cast<char *>(argument.c_str()));

+    }

+    argv.push_back(nullptr);

+

+    std::vector<char *> envp;

+    for (const StrType &ev : environment_block) {

+      envp.push_back(const_cast<char *>(ev.c_str()));

+    }

+    envp.push_back(nullptr);

+

+    umask(022);

+

+    execve(executable.c_str(), argv.data(), envp.data());

+    std::cerr << "error: Failed to exec the new process." << std::endl;

+    return -1;

+  }

+

+  int err, exit_status = -1;

+  do {

+    err = waitpid(child_pid, &exit_status, 0);

+  } while (err == -1 && errno == EINTR);

+

+  return exit_status;

+}

+

+}  // namespace rust_tool_wrapper

diff --git a/rust/private/rust_tool_wrapper/system_windows.cc b/rust/private/rust_tool_wrapper/system_windows.cc
new file mode 100644
index 0000000..b9cdd98
--- /dev/null
+++ b/rust/private/rust_tool_wrapper/system_windows.cc
@@ -0,0 +1,128 @@
+#include "rust/private/rust_tool_wrapper/system.h"

+

+#ifndef WIN32_LEAN_AND_MEAN

+#define WIN32_LEAN_AND_MEAN

+#endif

+

+#include <windows.h>

+

+#include <iostream>

+

+namespace rust_tool_wrapper {

+

+namespace {

+// Algorithm used:

+// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way

+

+void ArgumentQuote(const System::StrType& argument,

+                   System::StrType& command_line) {

+  if (argument.empty() == false && argument.find_first_of(RTW_SYS_STR_LITERAL(

+                                       " \t\n\v\"")) == argument.npos) {

+    command_line.append(argument);

+  } else {

+    command_line.push_back(RTW_SYS_STR_LITERAL('"'));

+

+    for (auto it = argument.begin();; ++it) {

+      unsigned number_backslashes = 0;

+

+      while (it != argument.end() && *it == RTW_SYS_STR_LITERAL('\\')) {

+        ++it;

+        ++number_backslashes;

+      }

+

+      if (it == argument.end()) {

+        command_line.append(number_backslashes * 2, RTW_SYS_STR_LITERAL('\\'));

+        break;

+      } else if (*it == L'"') {

+        command_line.append(number_backslashes * 2 + 1,

+                            RTW_SYS_STR_LITERAL('\\'));

+        command_line.push_back(*it);

+      } else {

+        command_line.append(number_backslashes, RTW_SYS_STR_LITERAL('\\'));

+        command_line.push_back(*it);

+      }

+    }

+    command_line.push_back(RTW_SYS_STR_LITERAL('"'));

+  }

+}

+

+void MakeCommandLine(const System::Arguments& arguments,

+                     System::StrType& command_line) {

+  for (const System::StrType& argument : arguments) {

+    command_line.push_back(RTW_SYS_STR_LITERAL(' '));

+    ArgumentQuote(argument, command_line);

+  }

+}

+

+void MakeEnvironmentBlock(const System::EnvironmentBlock& environment_block,

+                          System::StrType& environment_block_win) {

+  for (const System::StrType& ev : environment_block) {

+    environment_block_win += ev;

+    environment_block_win.push_back(RTW_SYS_STR_LITERAL('\0'));

+  }

+  environment_block_win.push_back(RTW_SYS_STR_LITERAL('\0'));

+}

+

+}  // namespace

+

+System::StrType System::GetWorkingDirectory() {

+  const DWORD kMaxBufferLength = 4096;

+  TCHAR buffer[kMaxBufferLength];

+  if (::GetCurrentDirectory(kMaxBufferLength, buffer) == 0) {

+    return System::StrType{};

+  }

+  return System::StrType{buffer};

+}

+

+System::StrType System::JoinPaths(const StrType& path1, const StrType& path2) {

+  return path1 + RTW_SYS_STR_LITERAL("\\") + path2;

+}

+

+int System::Exec(const System::StrType& executable,

+                 const System::Arguments& arguments,

+                 const System::EnvironmentBlock& environment_block) {

+  PROCESS_INFORMATION process_info;

+  ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));

+

+  STARTUPINFO startup_info;

+  ZeroMemory(&startup_info, sizeof(STARTUPINFO));

+  startup_info.cb = sizeof(STARTUPINFO);

+

+  System::StrType command_line;

+  ArgumentQuote(executable, command_line);

+  MakeCommandLine(arguments, command_line);

+

+  System::StrType environment_block_win;

+  MakeEnvironmentBlock(environment_block, environment_block_win);

+

+  BOOL success = ::CreateProcess(

+      /*lpApplicationName*/ nullptr,

+      /*lpCommandLine*/ command_line.empty() ? nullptr : &command_line[0],

+      /*lpProcessAttributes*/ nullptr,

+      /*lpThreadAttributes*/ nullptr, /*bInheritHandles*/ TRUE,

+      /*dwCreationFlags*/ 0

+#if defined(UNICODE)

+          | CREATE_UNICODE_ENVIRONMENT

+#endif  // defined(UNICODE)

+      ,

+      /*lpEnvironment*/ environment_block_win.empty()

+          ? nullptr

+          : &environment_block_win[0],

+      /*lpCurrentDirectory*/ nullptr,

+      /*lpStartupInfo*/ &startup_info,

+      /*lpProcessInformation*/ &process_info);

+

+  if (success == FALSE) {

+    std::cerr << "error: Failed to launch a new process." << std::endl;

+    return -1;

+  }

+

+  DWORD exit_status;

+  WaitForSingleObject(process_info.hProcess, INFINITE);

+  if (GetExitCodeProcess(process_info.hProcess, &exit_status) == FALSE)

+    exit_status = -1;

+  CloseHandle(process_info.hProcess);

+  return exit_status;

+}

+

+}  // namespace rust_tool_wrapper

diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 303ecfd..9595a15 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -122,23 +122,22 @@
     for dep in deps:
         if CrateInfo in dep:
             if dep[CrateInfo].type == "proc-macro":
-              fail(
-                  "{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format(
-                      label,
-                      dep.label,
-                  )
-              )
+                fail(
+                    "{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format(
+                        label,
+                        dep.label,
+                    ),
+                )
     for dep in proc_macro_deps:
         type = dep[CrateInfo].type
         if type != "proc-macro":
-          fail(
-              "{} listed {} in its proc_macro_deps, but it is not proc-macro, it is a {}. It should probably instead be listed in deps.".format(
-                  label,
-                  dep.label,
-                  type,
-              )
-          )
-
+            fail(
+                "{} listed {} in its proc_macro_deps, but it is not proc-macro, it is a {}. It should probably instead be listed in deps.".format(
+                    label,
+                    dep.label,
+                    type,
+                ),
+            )
 
     # TODO: Fix depset union (https://docs.bazel.build/versions/master/skylark/depsets.html)
     direct_crates = []
@@ -148,7 +147,7 @@
     transitive_build_infos = 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 + proc_macro_deps:
         if CrateInfo in dep:
             # This dependency is a rust_library
@@ -249,10 +248,10 @@
         build_info,
         dep_info,
         compile_inputs):
-    extra_inputs, prep_commands, dynamic_env, dynamic_build_flags = _create_out_dir_action(ctx, file, build_info, dep_info)
+    extra_inputs, out_dir, build_env_file, build_flags_files = _create_extra_input_args(ctx, file, build_info, dep_info)
     if extra_inputs:
         compile_inputs = depset(extra_inputs, transitive = [compile_inputs])
-    return compile_inputs, prep_commands, dynamic_env, dynamic_build_flags
+    return compile_inputs, out_dir, build_env_file, build_flags_files
 
 def collect_inputs(
         ctx,
@@ -290,18 +289,71 @@
         ctx,
         file,
         toolchain,
+        tool_path,
         crate_info,
         dep_info,
         output_hash,
         rust_flags,
-        dynamic_env):
+        out_dir,
+        build_env_file,
+        build_flags_files,
+        maker_path):
     output_dir = getattr(crate_info.output, "dirname") if hasattr(crate_info.output, "dirname") else None
 
     linker_script = getattr(file, "linker_script") if hasattr(file, "linker_script") else None
 
     env = _get_rustc_env(ctx, toolchain)
 
+    # Wrapper args first
     args = ctx.actions.args()
+    args.add("--tool-path", tool_path)
+    if out_dir != None:
+        args.add("--out-dir", out_dir)
+
+    if build_env_file != None:
+        args.add("--build-env-file", build_env_file)
+
+    for f in build_flags_files:
+        args.add("--build-flags-file", f)
+
+    # 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. Therefore, in order to fix this without an upstream code change, we have to set
+    # `$CARGO_MANIFEST_DIR`.
+    #
+    # As such we attempt to infer `$CARGO_MANIFEST_DIR`.
+    # Inference cannot be derived from `attr.crate_root`, as this points at a source file which may or
+    # may not follow the `src/lib.rs` convention. As such we use `ctx.build_file_path` mapped into the
+    # `exec_root`. Since we cannot (seemingly) get the `exec_root` from skylark, we cheat a little
+    # and use `$(pwd)` which resolves the `exec_root` at action execution time.
+    package_dir = ctx.build_file_path[:ctx.build_file_path.rfind("/")]
+    args.add("--package-dir", package_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.
+    maybe_rename = ""
+    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:
+            args.add_all(["--rename", src, dst])
+
+    if maker_path != None:
+        args.add("--maker-path", maker_path)
+
+    # Rustc arguments
+    args.add("--")
+
     args.add(crate_info.root)
     args.add("--crate-name=" + crate_info.name)
     args.add("--crate-type=" + crate_info.type)
@@ -362,61 +414,7 @@
     # sysroot being undefined.
     env["SYSROOT"] = ""
 
-    # 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. Therefore, in order to fix this without an upstream code change, we have to set
-    # `$CARGO_MANIFEST_DIR`.
-    #
-    # As such we attempt to infer `$CARGO_MANIFEST_DIR`.
-    # Inference cannot be derived from `attr.crate_root`, as this points at a source file which may or
-    # may not follow the `src/lib.rs` convention. As such we use `ctx.build_file_path` mapped into the
-    # `exec_root`. Since we cannot (seemingly) get the `exec_root` from skylark, we cheat a little
-    # and use `$(pwd)` which resolves the `exec_root` at action execution time.
-    package_dir = ctx.build_file_path[:ctx.build_file_path.rfind("/")]
-    dynamic_env["CARGO_MANIFEST_DIR"] = "${{EXEC_ROOT}}/{}".format(package_dir)
-
-    return args, env, dynamic_env
-
-def construct_compile_command(
-        ctx,
-        command,
-        toolchain,
-        crate_info,
-        build_info,
-        dep_info,
-        prep_commands,
-        dynamic_env,
-        dynamic_build_flags):
-    # 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.
-    maybe_rename = ""
-    if crate_info.type == "bin" and crate_info.output != None:
-        generated_file = crate_info.name
-        if toolchain.target_arch == "wasm32":
-            generated_file = generated_file + ".wasm"
-        src = "/".join([crate_info.output.dirname, generated_file])
-        dst = crate_info.output.path
-        if src != dst:
-            maybe_rename = " && /bin/mv {src} {dst}".format(src=src, dst=dst)
-
-    # Set ${EXEC_ROOT} so that actions which chdir still work.
-    # See https://github.com/google/cargo-raze/issues/71#issuecomment-433225853 for the rationale as
-    # to why.
-    return 'export EXEC_ROOT=$(pwd) && {} && {} "$@" --remap-path-prefix="$(pwd)"=__bazel_redacted_pwd {}{}'.format(
-        " && ".join(["export {}={}".format(key, value) for key, value in dynamic_env.items()] + prep_commands),
-        command,
-        " ".join(dynamic_build_flags),
-        maybe_rename,
-    )
+    return args, env
 
 def rustc_compile_action(
         ctx,
@@ -441,7 +439,7 @@
         toolchain,
     )
 
-    compile_inputs, prep_commands, dynamic_env, dynamic_build_flags = collect_inputs(
+    compile_inputs, out_dir, build_env_file, build_flags_files = collect_inputs(
         ctx,
         ctx.file,
         ctx.files,
@@ -451,27 +449,19 @@
         build_info,
     )
 
-    args, env, dynamic_env = construct_arguments(
+    args, env = construct_arguments(
         ctx,
         ctx.file,
         toolchain,
+        toolchain.rustc.path,
         crate_info,
         dep_info,
         output_hash,
         rust_flags,
-        dynamic_env,
-    )
-
-    command = construct_compile_command(
-        ctx,
-        toolchain.rustc.path,
-        toolchain,
-        crate_info,
-        build_info,
-        dep_info,
-        prep_commands,
-        dynamic_env,
-        dynamic_build_flags,
+        out_dir,
+        build_env_file,
+        build_flags_files,
+        maker_path = None,
     )
 
     if hasattr(ctx.attr, "version") and ctx.attr.version != "0.0.0":
@@ -479,8 +469,8 @@
     else:
         formatted_version = ""
 
-    ctx.actions.run_shell(
-        command = command,
+    ctx.actions.run(
+        executable = ctx.executable._rust_tool_wrapper,
         inputs = compile_inputs,
         outputs = [crate_info.output],
         env = env,
@@ -493,6 +483,7 @@
             len(crate_info.srcs),
         ),
     )
+
     runfiles = ctx.runfiles(
         files = dep_info.transitive_dylibs.to_list() + getattr(ctx.files, "data", []),
         collect_data = True,
@@ -509,7 +500,7 @@
             # nb. This field is required for cc_library to depend on our output.
             files = depset([crate_info.output]),
             runfiles = runfiles,
-            executable = crate_info.output if crate_info.type == "bin" or out_binary else None,
+            executable = crate_info.output if crate_info.type == "bin" or "--test" in rust_flags or out_binary else None,
         ),
     ]
 
@@ -517,27 +508,28 @@
     if crate.edition != "2015":
         args.add("--edition={}".format(crate.edition))
 
-def _create_out_dir_action(ctx, file, build_info, dep_info):
-    prep_commands = []
+def _create_extra_input_args(ctx, file, build_info, dep_info):
     input_files = []
-    # Env vars and build flags which need to be set in the action's command line, rather than on the action's env,
-    # because they rely on other env vars or commands.
-    dynamic_env = {}
-    dynamic_build_flags = []
+
+    # 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:
-        prep_commands.append("export $(cat %s)" % build_info.rustc_env.path)
+        out_dir = build_info.out_dir.path
+        build_env_file = build_info.rustc_env.path
         # out_dir will be added as input by the transitive_build_infos loop below.
-        dynamic_env["OUT_DIR"] = "${{EXEC_ROOT}}/{}".format(build_info.out_dir.path)
-        dynamic_build_flags.append("$(cat '%s')" % build_info.flags.path)
+        build_flags_files.append(build_info.flags.path)
 
     # This should probably only actually be exposed to actions which link.
     for dep_build_info in dep_info.transitive_build_infos.to_list():
         input_files.append(dep_build_info.out_dir)
-        dynamic_build_flags.append("$(cat '{}' | sed -e \"s#\${{EXEC_ROOT}}#${{EXEC_ROOT}}#g\")".format(dep_build_info.link_flags.path))
+        build_flags_files.append(dep_build_info.link_flags.path)
         input_files.append(dep_build_info.link_flags)
 
-    return input_files, prep_commands, dynamic_env, dynamic_build_flags
+    return input_files, out_dir, build_env_file, build_flags_files
 
 def _compute_rpaths(toolchain, output_dir, dep_info):
     """
diff --git a/rust/repositories.bzl b/rust/repositories.bzl
index aef35fa..1860a48 100644
--- a/rust/repositories.bzl
+++ b/rust/repositories.bzl
@@ -202,6 +202,7 @@
     rustfmt = "@{workspace_name}//:rustfmt_bin",
     clippy_driver = "@{workspace_name}//:clippy_driver_bin",
     rustc_lib = "@{workspace_name}//:rustc_lib",
+    binary_ext = "{binary_ext}",
     staticlib_ext = "{staticlib_ext}",
     dylib_ext = "{dylib_ext}",
     os = "{system}",
@@ -213,6 +214,7 @@
 """.format(
         toolchain_name = name,
         workspace_name = workspace_name,
+        binary_ext = system_to_binary_ext(system),
         staticlib_ext = system_to_staticlib_ext(system),
         dylib_ext = system_to_dylib_ext(system),
         system = system,
diff --git a/rust/toolchain.bzl b/rust/toolchain.bzl
index bd5d278..cb9738b 100644
--- a/rust/toolchain.bzl
+++ b/rust/toolchain.bzl
@@ -19,6 +19,7 @@
         clippy_driver = ctx.file.clippy_driver,
         rustc_lib = ctx.attr.rustc_lib,
         rust_lib = ctx.attr.rust_lib,
+        binary_ext = ctx.attr.binary_ext,
         staticlib_ext = ctx.attr.staticlib_ext,
         dylib_ext = ctx.attr.dylib_ext,
         target_triple = ctx.attr.target_triple,
@@ -56,6 +57,7 @@
         "rust_lib": attr.label(
             doc = "The rust standard library.",
         ),
+        "binary_ext": attr.string(mandatory = True),
         "staticlib_ext": attr.string(mandatory = True),
         "dylib_ext": attr.string(mandatory = True),
         "os": attr.string(mandatory = True),
@@ -98,6 +100,7 @@
   rustc_lib = "@rust_cpuX//:rustc_lib",
   rust_lib = "@rust_cpuX//:rust_lib",
   rust_doc = "@rust_cpuX//:rustdoc",
+  binary_ext = "",
   staticlib_ext = ".a",
   dylib_ext = ".so",
   os = "linux",