Always use param files to handle large rustc commands (#4038)

Using a params file helps avoid OS limitations for command line lengths.
This should provide a more stable experience on all platforms.

closes https://github.com/bazelbuild/rules_rust/issues/4006
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index ad84f10..f1a44fe 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -1125,7 +1125,7 @@
     # Rustc arguments
     rustc_flags = ctx.actions.args()
     rustc_flags.set_param_file_format("multiline")
-    rustc_flags.use_param_file("@%s", use_always = False)
+    rustc_flags.use_param_file("@%s", use_always = bool(ctx.executable._process_wrapper))
     rustc_flags.add(crate_info.root)
     rustc_flags.add(crate_info.name, format = "--crate-name=%s")
     rustc_flags.add(crate_info.type, format = "--crate-type=%s")
diff --git a/test/unit/large_command_line/BUILD.bazel b/test/unit/large_command_line/BUILD.bazel
new file mode 100644
index 0000000..5727a1a
--- /dev/null
+++ b/test/unit/large_command_line/BUILD.bazel
@@ -0,0 +1,3 @@
+load(":large_command_line_test_suite.bzl", "large_command_line_test_suite")
+
+large_command_line_test_suite(name = "large_command_line_test_suite")
diff --git a/test/unit/large_command_line/dep.rs b/test/unit/large_command_line/dep.rs
new file mode 100644
index 0000000..a6ba46c
--- /dev/null
+++ b/test/unit/large_command_line/dep.rs
@@ -0,0 +1 @@
+pub fn noop() {}
diff --git a/test/unit/large_command_line/large_command_line_test_suite.bzl b/test/unit/large_command_line/large_command_line_test_suite.bzl
new file mode 100644
index 0000000..1b1c472
--- /dev/null
+++ b/test/unit/large_command_line/large_command_line_test_suite.bzl
@@ -0,0 +1,90 @@
+"""Test that large commands produce outputs and that argv is well-formed."""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+load("//rust:defs.bzl", "rust_library")
+
+_DEP_COUNT = 100
+
+def _rustc_large_argv_test_impl(ctx):
+    env = analysistest.begin(ctx)
+    tut = analysistest.target_under_test(env)
+    rustc_action = [a for a in tut.actions if a.mnemonic == "Rustc"][0]
+
+    # Every dependency must appear as an --extern flag.
+    extern_args = [a for a in rustc_action.argv if a.startswith("--extern=dep_")]
+    asserts.equals(
+        env,
+        _DEP_COUNT,
+        len(extern_args),
+        "expected {} --extern=dep_* flags in the Rustc action argv".format(_DEP_COUNT),
+    )
+
+    # Compute the total byte length of the argv.  On Windows the
+    # output-path configuration prefix is much longer
+    # (x86_64-pc-windows-msvc-fastbuild vs k8-fastbuild), so the real
+    # command line is even larger than what we see here.  Asserting a
+    # minimum size documents that param-file routing (use_param_file
+    # with use_always) is necessary to keep the CreateProcessW /
+    # cmd.exe command line within OS limits.
+    argv_bytes = 0
+    for a in rustc_action.argv:
+        argv_bytes += len(a)
+    asserts.true(
+        env,
+        argv_bytes > 8192,
+        "Expected total argv byte length > 8192 to prove param-file " +
+        "routing is needed, but got {} bytes".format(argv_bytes),
+    )
+
+    return analysistest.end(env)
+
+_rustc_large_argv_test = analysistest.make(_rustc_large_argv_test_impl)
+
+def large_command_line_test_suite(name):
+    """Generate a large number of deps and verify the Rustc action.
+
+    Args:
+        name: Name of the enclosing test-suite target.
+    """
+    deps = []
+
+    for i in range(_DEP_COUNT):
+        lib_name = "dep_{}".format(i)
+        rust_library(
+            name = lib_name,
+            srcs = ["dep.rs"],
+            crate_name = lib_name,
+            edition = "2021",
+            tags = ["no-clippy", "no-unpretty", "no-rustfmt"],
+        )
+        deps.append(":{}".format(lib_name))
+
+    rust_library(
+        name = "many_deps_lib",
+        srcs = ["lib.rs"],
+        edition = "2021",
+        deps = deps,
+        tags = ["no-clippy", "no-unpretty", "no-rustfmt"],
+    )
+
+    # Analysis test: verify all --extern flags are present and that
+    # the total argv size is large enough to require param-file routing.
+    _rustc_large_argv_test(
+        name = "rustc_large_argv_test",
+        target_under_test = ":many_deps_lib",
+    )
+
+    # Build test: verify the compilation actually succeeds end-to-end.
+    build_test(
+        name = "build_test",
+        targets = [":many_deps_lib"],
+    )
+
+    native.test_suite(
+        name = name,
+        tests = [
+            ":rustc_large_argv_test",
+            ":build_test",
+        ],
+    )
diff --git a/test/unit/large_command_line/lib.rs b/test/unit/large_command_line/lib.rs
new file mode 100644
index 0000000..a6ba46c
--- /dev/null
+++ b/test/unit/large_command_line/lib.rs
@@ -0,0 +1 @@
+pub fn noop() {}