Skip to content

Commit ffcf49d

Browse files
committed
fix: dispatch compiler argv before global option scans
1 parent 202aa9f commit ffcf49d

3 files changed

Lines changed: 30 additions & 26 deletions

File tree

‎.agents/docs/2026-11-06-bmi-publication-and-compiler-cap-closeout.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ permit, so waiting joins cannot form a permit dependency cycle.
5656

5757
The internal executor introduces no user mode and no argument serialization.
5858
It receives the compiler's actual argv, inherits the existing streams and
59-
returns its status. Existing response files retain their original owner and
59+
returns its status. It dispatches before global option scans so child flags
60+
cannot configure mcpp. Existing response files retain their original owner and
6061
encoding. Ordinary completion and interrupt cleanup release a permit once;
6162
release cannot remove a permit already acquired by another compiler.
6263
Failure to create or acquire the semaphore is reported rather than followed

‎src/cli.cppm‎

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,32 @@ int cmd_toolchain_any(const mcpplibs::cmdline::ParsedArgs& p) {
274274

275275
int run(int argc, char** argv) {
276276
namespace cl = mcpplibs::cmdline;
277+
278+
// Internal split-schedule executor. Everything after `--` is the
279+
// compiler's argv, including flags with the same names as these options.
280+
// Dispatch before global option scans: child words never configure mcpp.
281+
if (argc > 1 && std::string_view(argv[1]) == "__compiler-cap") {
282+
std::filesystem::path semaphore;
283+
int cap = 0, i = 2;
284+
for (; i < argc && std::string_view(argv[i]) != "--"; ++i) {
285+
const std::string_view option = argv[i];
286+
if ((option != "--sem" && option != "--cap") || i + 1 >= argc) {
287+
std::println(stderr, "error: __compiler-cap: expected --sem <dir> --cap <N> -- <compiler>...");
288+
return 2;
289+
}
290+
const std::string_view value = argv[++i];
291+
if (option == "--sem") semaphore = value;
292+
else {
293+
const auto [end, ec] = std::from_chars(value.data(), value.data() + value.size(), cap);
294+
if (ec != std::errc{} || end != value.data() + value.size()) return 2;
295+
}
296+
}
297+
if (semaphore.empty() || cap <= 0 || i + 1 >= argc) return 2;
298+
std::vector<std::string> command;
299+
for (++i; i < argc; ++i) command.emplace_back(argv[i]);
300+
return mcpp::build::schedule::detach::run_capped(command, semaphore, cap);
301+
}
302+
277303
ReportUnnarrowablePaths reportUnnarrowable_;
278304

279305
// What a build that waits for this one is told it waits for (mcpp#839):
@@ -1493,30 +1519,6 @@ int run(int argc, char** argv) {
14931519
// check and no `prepare`. On the MSVC ABI the first build after upgrading
14941520
// to 2026.9.28.2 re-runs each action once, because its command line gained
14951521
// the directory.
1496-
// Internal split-schedule executor. Everything after `--` is the
1497-
// compiler's argv, including flags with the same names as these options.
1498-
if (std::string_view(argv[1]) == "__compiler-cap") {
1499-
std::filesystem::path semaphore;
1500-
int cap = 0, i = 2;
1501-
for (; i < argc && std::string_view(argv[i]) != "--"; ++i) {
1502-
const std::string_view option = argv[i];
1503-
if ((option != "--sem" && option != "--cap") || i + 1 >= argc) {
1504-
std::println(stderr, "error: __compiler-cap: expected --sem <dir> --cap <N> -- <compiler>...");
1505-
return 2;
1506-
}
1507-
const std::string_view value = argv[++i];
1508-
if (option == "--sem") semaphore = value;
1509-
else {
1510-
const auto [end, ec] = std::from_chars(value.data(), value.data() + value.size(), cap);
1511-
if (ec != std::errc{} || end != value.data() + value.size()) return 2;
1512-
}
1513-
}
1514-
if (semaphore.empty() || cap <= 0 || i + 1 >= argc) return 2;
1515-
std::vector<std::string> command;
1516-
for (++i; i < argc; ++i) command.emplace_back(argv[i]);
1517-
return mcpp::build::schedule::detach::run_capped(command, semaphore, cap);
1518-
}
1519-
15201522
// `mcpp depfile-filter --raw <file> --out <file> -- <compiler argv>...`
15211523
// (internal: written into build.ninja for GCC on a Windows host, WS2 of
15221524
// the 2026-09-28 design). Runs the compile with inherited stdio and no

‎tests/e2e/helpers/check_shared_compiler_cap.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ def check_cancellation(mcpp, root):
147147
assert not list(semaphore.iterdir()), "cancellation retained its compiler permit"
148148
result = subprocess.run([*command, sys.executable, "-c", "raise SystemExit(7)"], cwd=root)
149149
assert result.returncode == 7 and not list(semaphore.iterdir()), "failure lost its status or leaked a permit"
150-
words = ["space value", 'literal "quote"', "back\\slash", "$literal", "", "Ω"]
150+
words = ["space value", 'literal "quote"', "back\\slash", "$literal", "", "Ω",
151+
"--system", "--home", "--deny", "--toolchain", "-j200", "--sem", "--cap"]
151152
result = subprocess.run([*command, sys.executable, "-c", "import json,sys;print(json.dumps(sys.argv[1:]))", *words],
152153
cwd=root, capture_output=True, text=True)
153154
assert result.returncode == 0 and json.loads(result.stdout) == words, "compiler argument words changed"

0 commit comments

Comments
 (0)