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() {}