Skip to content

Commit 25e7aaf

Browse files
committed
fix: an MSVC module map writes each /reference with its value on one line, which is how cl.exe reads a command file
1 parent fa3bfce commit 25e7aaf

2 files changed

Lines changed: 58 additions & 7 deletions

File tree

‎src/build/ninja_backend.cppm‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -299,18 +299,24 @@ bool msvc_module_spelling(const BuildPlan& plan) {
299299
return !prefix.empty() && prefix.back() == ' ';
300300
}
301301

302-
// The text of a module map's argument file (B1): one argument per line. clang
303-
// reads it by GNU rules on every host (#247), where a word holding a space or
304-
// a quote is written in double quotes with `\` and `"` escaped; the paths are
302+
// The text of a module map's argument file (B1). clang reads it by GNU rules on
303+
// every host (#247), one argument per line, where a word holding a space or a
304+
// quote is written in double quotes with `\` and `"` escaped; the paths are
305305
// written with forward slashes, so a backslash does not occur. MSVC reads it
306306
// by its own rules (`shell_quote_arg` on a Windows host) and needs the byte
307-
// order mark to read a path that is not ASCII (#693).
307+
// order mark to read a path that is not ASCII (#693). Its arguments come in
308+
// pairs, `/reference` and `<name>=<path>`, and each pair is one line: cl.exe
309+
// does not take an option's value from the next line of a command file
310+
// (`D8004: '/reference' requires an argument`, measured on windows-2025).
308311
std::string module_map_arguments(const std::vector<std::string>& arguments, bool msvc) {
309312
std::string out = msvc ? std::string(kUtf8ByteOrderMark) : std::string{};
313+
if (msvc) {
314+
for (std::size_t i = 0; i + 1 < arguments.size(); i += 2)
315+
out += arguments[i] + " " + mcpp::build::shell_quote_arg(arguments[i + 1]) + "\n";
316+
return out;
317+
}
310318
for (auto const& a : arguments) {
311-
if (msvc) {
312-
out += mcpp::build::shell_quote_arg(a);
313-
} else if (a.find_first_of(" \t\"'\\") == std::string::npos) {
319+
if (a.find_first_of(" \t\"'\\") == std::string::npos) {
314320
out += a;
315321
} else {
316322
out += '"';

‎tests/unit/test_module_address.cpp‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import std;
2121
import mcpp.build.plan;
22+
import mcpp.build.ninja;
2223
import mcpp.manifest;
2324
import mcpp.modgraph.graph;
2425
import mcpp.modgraph.scanner;
@@ -310,3 +311,47 @@ TEST(ModuleAddress, EveryNamedArgumentFileIsOneThePlanStates) {
310311
// another package or of a member.
311312
EXPECT_EQ(named, 3u);
312313
}
314+
315+
// The argument file of a module map, as each compiler reads it. MSVC takes an
316+
// option's value only from the same line of a command file (`D8004: '/reference'
317+
// requires an argument` on windows-2025 when they were on two lines), and reads
318+
// a non-ASCII path only with a byte order mark; clang reads GNU rules, where a
319+
// word with a space is quoted.
320+
TEST(ModuleAddress, ArgumentFilesAreWrittenAsEachCompilerReadsThem) {
321+
Tmp t;
322+
auto read = [](const std::filesystem::path& p) {
323+
std::ifstream in(p, std::ios::binary);
324+
return std::string(std::istreambuf_iterator<char>(in), {});
325+
};
326+
327+
BuildPlan msvc;
328+
msvc.outputDir = t.path / "msvc";
329+
msvc.toolchain.compiler = mcpp::toolchain::CompilerId::MSVC;
330+
ModuleScope ms;
331+
ms.mapFile = "modmap/core-1.map";
332+
ms.content = "m ifc.cache/core/m.ifc\n";
333+
ms.argsFile = "modmap/core-1.modmap";
334+
ms.arguments = {"/reference", "m=C:/build/ifc.cache/core/m.ifc",
335+
"/reference", "n=C:/build/ifc.cache/core/n.ifc"};
336+
msvc.moduleScopes.emplace("core-1", ms);
337+
write_module_maps(msvc);
338+
EXPECT_EQ(read(msvc.outputDir / "modmap/core-1.modmap"),
339+
"\xEF\xBB\xBF/reference m=C:/build/ifc.cache/core/m.ifc\n"
340+
"/reference n=C:/build/ifc.cache/core/n.ifc\n");
341+
EXPECT_EQ(read(msvc.outputDir / "modmap/core-1.map"), ms.content);
342+
343+
BuildPlan clang;
344+
clang.outputDir = t.path / "clang";
345+
clang.toolchain.compiler = mcpp::toolchain::CompilerId::Clang;
346+
ModuleScope cs;
347+
cs.mapFile = "modmap/core-2.map";
348+
cs.content = "m pcm.cache/core/m.pcm\n";
349+
cs.argsFile = "modmap/core-2.modmap";
350+
cs.arguments = {"-fmodule-file=m=/build dir/pcm.cache/core/m.pcm",
351+
"-fmodule-file=n=/build/pcm.cache/core/n.pcm"};
352+
clang.moduleScopes.emplace("core-2", cs);
353+
write_module_maps(clang);
354+
EXPECT_EQ(read(clang.outputDir / "modmap/core-2.modmap"),
355+
"\"-fmodule-file=m=/build dir/pcm.cache/core/m.pcm\"\n"
356+
"-fmodule-file=n=/build/pcm.cache/core/n.pcm\n");
357+
}

0 commit comments

Comments
 (0)