Skip to content

Commit d2ba022

Browse files
committed
build: a build program's compile command goes through a response file when it outgrows the channel
A build program that imports many host modules carries one `-fmodule-file=<name>=<path>` per module, with absolute paths, and on Windows `capture_exec` reaches a shell that tolerates 8191 bytes. mcpp-plugins' all-rules-compile fixture imports fifteen and crossed that line the moment the collection gained one more module, reporting only The command line is too long. build.mcpp failed to compile (exit 1) which names neither the length nor the cause -- the family mcpp.build.cmdlimits exists to make legible. The command now goes through `@file` when it is over the budget that module states, which every driver mcpp supports reads, and the file stays beside the program for a failed compile to show. Its quoting is `response_file_body`, exported and covered by a unit case, because the command it serves cannot be run on a host whose limit it does not cross.
1 parent 23c5843 commit d2ba022

1 file changed

Lines changed: 72 additions & 0 deletions

File tree

‎src/build/build_program.cppm‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import mcpp.platform.process;
2323
import mcpp.toolchain.cppfly; // std_flag (dialect- and c++fly-aware -std= spelling)
2424
import mcpp.toolchain.dialect; // CommandDialect — gnu vs cl.exe spellings
2525
import mcpp.toolchain.fingerprint; // hash_file / hash_string (FNV-1a, 16 hex)
26+
import mcpp.build.cmdlimits;
2627
import mcpp.build.directives; // the directive definition table (own module: see its header)
2728
import mcpp.build.progress; // the program's line (build progress design 2026-09-29)
2829
import mcpp.build.refusal; // the machine-readable identity of a refusal
@@ -425,6 +426,30 @@ struct BuildProgramEnv {
425426
// The suffix selects the fact: `DIR` (the payload directory), `SOURCE` and
426427
// `PROGRAM` (where it came from, mcpp#755), read back by `xpkg_source` and
427428
// `xpkg_program`.
429+
std::string response_file_body(std::span<const std::string> args) {
430+
std::string body;
431+
for (auto const& a : args) {
432+
if (a.find_first_of(" \t\"") == std::string::npos) {
433+
body += a;
434+
body.push_back('\n');
435+
continue;
436+
}
437+
body.push_back('"');
438+
std::size_t slashes = 0;
439+
for (char c : a) {
440+
if (c == '\\') { ++slashes; body.push_back(c); continue; }
441+
if (c == '"') { body.append(slashes, '\\'); body += "\\\""; }
442+
else body.push_back(c);
443+
slashes = 0;
444+
}
445+
// A run of backslashes that ends the argument would escape the closing
446+
// quote, so it is doubled.
447+
body.append(slashes, '\\');
448+
body += "\"\n";
449+
}
450+
return body;
451+
}
452+
428453
inline std::string xpkg_env_var(std::string_view ns, std::string_view name,
429454
std::string_view suffix = "DIR") {
430455
std::string out = "MCPP_XPKG_";
@@ -440,6 +465,17 @@ inline std::string xpkg_env_var(std::string_view ns, std::string_view name,
440465
return out;
441466
}
442467

468+
// THE ARGV THAT PASSES `args` THROUGH A RESPONSE FILE, and the file's content.
469+
//
470+
// Every compiler driver mcpp supports reads `@file` with one argument per line.
471+
// An argument that carries whitespace or a quote is quoted, and the backslashes
472+
// before a quote -- including a run that would otherwise escape the closing one
473+
// -- are escaped, which is what makes a Windows path safe to write here.
474+
//
475+
// Exported because its quoting is the part worth testing, and the command it
476+
// serves cannot be run on a host whose limit it does not cross.
477+
std::string response_file_body(std::span<const std::string> args);
478+
443479
// Does a compiler's output say the program asked for something the bundled
444480
// `mcpp` module does not have?
445481
//
@@ -1854,6 +1890,42 @@ std::expected<void, std::string> run_build_program_impl(
18541890
// module", e2e 807 under GCC). Otherwise the project root is fine.
18551891
const bool needsBmiCwd = usesModule || stdStagedInBdir || !env.hostModules.empty();
18561892
std::string compileCwd = needsBmiCwd ? bdir.string() : root.string();
1893+
// A COMMAND THAT OUTGREW ITS CHANNEL GOES THROUGH A RESPONSE FILE.
1894+
//
1895+
// The engine's rule for an unbounded payload is a response file
1896+
// (mcpp.build.cmdlimits, and the architecture record it names), and this
1897+
// command carries one: a `-fmodule-file=<name>=<path>` for every host
1898+
// module the program imports, with absolute paths. A collection that
1899+
// offers many rules through features is the case -- mcpp-plugins'
1900+
// `all-rules-compile` imports fifteen -- and on Windows
1901+
// `capture_exec` reaches the shell, whose 8191 bytes this crossed the
1902+
// moment that collection gained one more module:
1903+
//
1904+
// The command line is too long.
1905+
// build.mcpp failed to compile (exit 1)
1906+
//
1907+
// Every driver mcpp supports reads `@file`, one argument per line, and
1908+
// the file is written beside the program it compiles, so a failed
1909+
// compile leaves it to read.
1910+
{
1911+
std::string flat;
1912+
for (auto const& a : compileArgv) { flat += a; flat.push_back(' '); }
1913+
if (mcpp::build::cmdlimits::check_inline(
1914+
flat, mcpp::platform::is_windows, /*needsShell=*/true)) {
1915+
const auto rsp = bdir / "build.mcpp.compile.rsp";
1916+
const auto body = response_file_body(
1917+
std::span<const std::string>(compileArgv).subspan(1));
1918+
std::ofstream out(rsp, std::ios::binary | std::ios::trunc);
1919+
out << body;
1920+
out.close();
1921+
if (out) {
1922+
mcpp::log::verbose("buildmcpp-host", std::format(
1923+
"build.mcpp {}: the compile command is {} bytes and goes through {}",
1924+
who, flat.size(), rsp.string()));
1925+
compileArgv = { compileArgv.front(), "@" + rsp.string() };
1926+
}
1927+
}
1928+
}
18571929
auto cres = mcpp::platform::process::capture_exec(compileArgv, compileEnv,
18581930
compileCwd);
18591931
mcpp::log::verbose("buildmcpp-host", std::format("build.mcpp {}: compile end", who));

0 commit comments

Comments
 (0)