Skip to content

Commit 41f7539

Browse files
committed
fix: decide the std module coroutine note from its command
The std module precompile's error carries the command, not the compiler's diagnostics, which reach the terminal directly (measured on #781's Windows CI, run 37802975713, e2e 892 case A). The note there rests on the command: the MSVC STL's std.ixx or std.compat.ixx at C++23 or later for a *-windows-msvc target, and a probe of the same compiler that finds __cpp_impl_coroutine undefined. The ninja path keeps reading the symptom from its output, which carries both.
1 parent 1916a5f commit 41f7539

3 files changed

Lines changed: 132 additions & 15 deletions

File tree

‎src/toolchain/msvc_coroutines.cppm‎

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,20 @@
1717
// coroutine diagnostic can run (measured, probe PR #788).
1818
//
1919
// ASKED ONLY AFTER A FAILURE, OF THE COMPILER THAT FAILED. The command is read
20-
// from the failure output itself — the std module's `command:` line, or the
21-
// command ninja prints after `FAILED:` — so the plan path and the fast path
22-
// share this one function and no record has to be written in advance. A
23-
// successful build never reaches it, and when the compiler or the STL changes,
24-
// either the symptom or the probe stops matching and the note disappears.
20+
// from the failure itself, so no record has to be written in advance, and a
21+
// successful build never reaches this module:
22+
//
23+
// * a compile ninja ran: the output carries the command after `FAILED:` and
24+
// the compiler's diagnostics, so the symptom is read from it (`advice`);
25+
// the plan path and the fast path share that one function;
26+
// * the std module precompile: its error carries the `command:` line but not
27+
// the compiler's diagnostics, which reach the terminal directly (measured
28+
// on #781's Windows CI). The answer there rests on the command alone
29+
// (`std_module_advice`): the MSVC STL's `std.ixx` at C++23 or later
30+
// includes <generator>, which cannot compile without the macro.
31+
//
32+
// When the compiler or the STL changes, the probe stops matching and the note
33+
// disappears.
2534

2635
export module mcpp.toolchain.msvc_coroutines;
2736

@@ -63,6 +72,13 @@ std::optional<FailedCommand> failed_command_in(std::string_view output);
6372
// only after the symptom and the command have both matched.
6473
std::string advice(std::string_view output, const ProbeFn& probe);
6574

75+
// The note for a failed std module precompile, from its error message: the
76+
// `command:` line compiles the MSVC STL's `std.ixx` or `std.compat.ixx` at
77+
// C++23 or later for a `*-windows-msvc` target, and the probe finds the macro
78+
// undefined. Empty otherwise.
79+
std::string std_module_advice(std::string_view message, const ProbeFn& probe);
80+
std::string std_module_advice(std::string_view message);
81+
6682
// The probe that runs the compiler: `-dM -E` of an empty unit, with the same
6783
// target and standard and without the driver's configuration file.
6884
std::optional<Probe> run_probe(const FailedCommand& cmd);
@@ -169,25 +185,74 @@ std::optional<FailedCommand> failed_command_in(std::string_view output) {
169185
return found;
170186
}
171187

188+
namespace {
189+
190+
bool targets_msvc_abi(const FailedCommand& cmd) {
191+
auto triple = mcpp::toolchain::triple::parse(cmd.target);
192+
return triple && triple->os == "windows" && triple->env == "msvc";
193+
}
194+
195+
// C++23 or later: `c++23`, `gnu++2b`, `c++26`, `c++2c`, `c++latest`.
196+
bool at_least_cxx23(std::string_view standard) {
197+
auto plus = standard.find("++");
198+
if (plus == std::string_view::npos) return false;
199+
auto level = standard.substr(plus + 2);
200+
if (level == "latest") return true;
201+
if (level.size() != 2) return false;
202+
if (level[0] == '2' && level[1] >= 'b' && level[1] <= 'z') return true;
203+
return level >= "23" && level <= "99";
204+
}
205+
206+
std::string render(Symptom symptom, const FailedCommand& cmd, const Probe& probed);
207+
208+
} // namespace
209+
172210
std::string advice(std::string_view output, const ProbeFn& probe) {
173211
const auto symptom = symptom_in(output);
174212
if (symptom == Symptom::None) return {};
175213
auto cmd = failed_command_in(output);
176-
if (!cmd) return {};
177-
auto triple = mcpp::toolchain::triple::parse(cmd->target);
178-
if (!triple || triple->os != "windows" || triple->env != "msvc") return {};
214+
if (!cmd || !targets_msvc_abi(*cmd)) return {};
215+
auto probed = probe(*cmd);
216+
if (!probed || probed->predefinesImplCoroutine) return {};
217+
return render(symptom, *cmd, *probed);
218+
}
219+
220+
std::string std_module_advice(std::string_view message, const ProbeFn& probe) {
221+
std::optional<FailedCommand> cmd;
222+
bool stlModule = false;
223+
for_each_line(message, [&](std::string_view line) {
224+
if (!line.starts_with("command: ")) return;
225+
if (auto c = failed_command_in(line)) {
226+
cmd = std::move(c);
227+
for (auto const& w : split_words(line.substr(9)))
228+
if (w.ends_with("std.ixx") || w.ends_with("std.compat.ixx")) stlModule = true;
229+
}
230+
});
231+
if (!cmd || !stlModule || !targets_msvc_abi(*cmd) || !at_least_cxx23(cmd->standard))
232+
return {};
179233
auto probed = probe(*cmd);
180234
if (!probed || probed->predefinesImplCoroutine) return {};
235+
return render(Symptom::StdModuleGenerator, *cmd, *probed);
236+
}
181237

182-
const std::string compiler = probed->version.empty()
183-
? std::string("this clang") : std::format("clang {}", probed->version);
184-
const std::string section = triple->str();
238+
std::string std_module_advice(std::string_view message) {
239+
return std_module_advice(message, run_probe);
240+
}
241+
242+
namespace {
243+
244+
std::string render(Symptom symptom, const FailedCommand& cmd, const Probe& probed) {
245+
auto triple = mcpp::toolchain::triple::parse(cmd.target);
246+
247+
const std::string compiler = probed.version.empty()
248+
? std::string("this clang") : std::format("clang {}", probed.version);
249+
const std::string section = triple ? triple->str() : cmd.target;
185250
std::string note = std::format(
186251
"\n"
187252
"note: {} does not support C++20 coroutines on {} (this Microsoft\n"
188253
" ABI): it does not predefine __cpp_impl_coroutine there, so the\n"
189254
" MSVC STL's <coroutine> is empty.",
190-
compiler, cmd->target);
255+
compiler, cmd.target);
191256
if (symptom == Symptom::StdModuleGenerator) {
192257
note +=
193258
" The C++23 std module includes\n"
@@ -226,6 +291,8 @@ std::string advice(std::string_view output, const ProbeFn& probe) {
226291
return note;
227292
}
228293

294+
} // namespace
295+
229296
std::optional<Probe> run_probe(const FailedCommand& cmd) {
230297
std::error_code ec;
231298
const auto empty = std::filesystem::temp_directory_path(ec)

‎src/toolchain/stdmod.cppm‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,12 @@ std::expected<std::string, StdModError> run_capture_command(
131131
return r.output;
132132
}
133133

134-
// A precompile that stopped inside the STL's <generator> because the
134+
// A precompile of the MSVC STL's std module that cannot succeed because the
135135
// compiler does not support coroutines on this MSVC ABI says so after the
136-
// compiler's own output (mcpp.toolchain.msvc_coroutines).
136+
// error (mcpp.toolchain.msvc_coroutines). Decided from the command: the
137+
// compiler's diagnostics reach the terminal, not this message.
137138
StdModError with_coroutine_note(StdModError e) {
138-
e.message += msvc_coroutines::advice(e.message);
139+
e.message += msvc_coroutines::std_module_advice(e.message);
139140
return e;
140141
}
141142

‎tests/unit/test_msvc_coroutines.cpp‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,55 @@ co::ProbeFn answering(bool predefines, int* calls = nullptr) {
4242
};
4343
}
4444

45+
// What the std module precompile's error actually carries (#781's Windows CI,
46+
// run 37802975713): the command, not the compiler's diagnostics, which reach
47+
// the terminal directly.
48+
constexpr std::string_view kStdModuleMessage =
49+
"std module precompile failed (rc=1):\n"
50+
"\n"
51+
"command: C:/Users/runneradmin/.mcpp/registry\\data\\xpkgs\\xim-x-llvm\\23.1.3\\bin\\clang++.exe "
52+
"-std=c++23 -fms-runtime-lib=dll -x c++-module -Wno-include-angled-in-module-purview "
53+
"--target=i686-pc-windows-msvc "
54+
"-Xmicrosoft-visualc-tools-root \"C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Tools\\MSVC\\14.51.36231\" "
55+
"--precompile \"C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Tools\\MSVC\\14.51.36231\\modules\\std.ixx\" "
56+
"-o std.pcm\n";
57+
58+
std::string with_standard(std::string_view message, std::string_view from, std::string_view to) {
59+
std::string s(message);
60+
s.replace(s.find(from), from.size(), to);
61+
return s;
62+
}
63+
64+
TEST(MsvcCoroutines, StdModuleMessageWithoutDiagnosticsGetsTheNote) {
65+
auto note = co::std_module_advice(kStdModuleMessage, answering(false));
66+
ASSERT_FALSE(note.empty());
67+
EXPECT_NE(note.find("<generator>"), std::string::npos);
68+
EXPECT_NE(note.find("standard = \"c++20\""), std::string::npos);
69+
EXPECT_NE(note.find("[target.i686-windows-msvc]"), std::string::npos);
70+
}
71+
72+
TEST(MsvcCoroutines, StdModuleBelowCxx23IsNotThisFailure) {
73+
int calls = 0;
74+
auto cxx20 = with_standard(kStdModuleMessage, "-std=c++23", "-std=c++20");
75+
EXPECT_TRUE(co::std_module_advice(cxx20, answering(false, &calls)).empty());
76+
EXPECT_EQ(calls, 0);
77+
for (auto spelling : {"-std=c++2b", "-std=c++26", "-std=gnu++23"}) {
78+
auto later = with_standard(kStdModuleMessage, "-std=c++23", spelling);
79+
EXPECT_FALSE(co::std_module_advice(later, answering(false)).empty()) << spelling;
80+
}
81+
}
82+
83+
TEST(MsvcCoroutines, StdModuleOfLibcxxIsNotThisFailure) {
84+
int calls = 0;
85+
auto libcxx = with_standard(kStdModuleMessage, "modules\\std.ixx", "share\\libc++\\v1\\std.cppm");
86+
EXPECT_TRUE(co::std_module_advice(libcxx, answering(false, &calls)).empty());
87+
EXPECT_EQ(calls, 0);
88+
}
89+
90+
TEST(MsvcCoroutines, StdModuleOnACompilerThatPredefinesTheMacroGetsNoNote) {
91+
EXPECT_TRUE(co::std_module_advice(kStdModuleMessage, answering(true)).empty());
92+
}
93+
4594
TEST(MsvcCoroutines, StdModuleFailureIsTheGeneratorSymptom) {
4695
EXPECT_EQ(co::symptom_in(kStdModuleFailure), co::Symptom::StdModuleGenerator);
4796
auto cmd = co::failed_command_in(kStdModuleFailure);

0 commit comments

Comments
 (0)