Skip to content

Commit dc6eb34

Browse files
fix(toolchain): an installed msvc toolset did not appear in toolchain list (#436)
The enumeration asked `toolchain_frontend(root / "bin", pkg)`, got nothing, and `continue`d. cl.exe is four levels deeper -- under VC/Tools/MSVC/<ver>/bin/Host<h>/<arch>/ -- so every msvc payload installed correctly and was then invisible. Three places have to know that layout. Install and build knew; the listing did not, which is what a third inline copy of a rule tends to produce. They now share `payload_frontend(payloadRoot, pkg, family)`, which delegates to `msvc::installation_at()` for msvc and to the bin/-shaped lookup for everything else -- so the three cannot disagree about where a payload keeps its compiler. Found by running the RELEASED 2026.8.16.1 binary against a payload-shaped fixture, not by a test: the unit test I had written pinned `identify_xim_payload("msvc")`, which was already correct. The identity mapping and the enumeration are different questions, and only one of them was being asked. prepare.cppm's default-toolchain path also resolves through payload->binDir and is deliberately left alone: the Windows first-run pin is llvm@20.1.7, so that site is unreachable for msvc. Regression test asserts both directions -- payload_frontend finds it, and the `root/bin` question still answers nothing, which is what makes it the wrong question rather than a broken implementation. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 26b26ec commit dc6eb34

4 files changed

Lines changed: 64 additions & 1 deletion

File tree

binDir

Whitespace-only changes.

src/toolchain/lifecycle.cppm

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,11 @@ export int toolchain_list(const mcpp::config::GlobalConfig& cfg) {
285285
s.version = vEntry.path().filename().string();
286286
s.target = id->target;
287287
auto pkg = mcpp::toolchain::to_xim_package(s);
288-
auto bin = mcpp::toolchain::toolchain_frontend(vEntry.path() / "bin", pkg);
288+
// From the payload ROOT, not `root/bin`: msvc keeps cl.exe
289+
// four levels deeper, and asking for `root/bin` skipped every
290+
// installed toolset silently.
291+
auto bin = mcpp::toolchain::payload_frontend(vEntry.path(), pkg,
292+
id->family);
289293
if (bin.empty()) continue;
290294
payloads.push_back({ *id, s.version, bin });
291295
}

src/toolchain/registry.cppm

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,23 @@ ToolchainSpec with_resolved_xim_version(const ToolchainSpec& spec,
106106
std::filesystem::path toolchain_frontend(const std::filesystem::path& binDir,
107107
const XimToolchainPackage& pkg);
108108

109+
// The frontend inside an installed payload ROOT — for callers that have the
110+
// root rather than a bin directory.
111+
//
112+
// Most families keep it in `bin/`, and for them this is `toolchain_frontend`
113+
// on `root/bin`. MSVC does not: cl.exe sits four levels deeper, under
114+
// `VC/Tools/MSVC/<version>/bin/Host<h>/<arch>/`.
115+
//
116+
// It exists because that difference had to be known in three places and was
117+
// only handled in two. The third — `toolchain list`'s enumeration — asked
118+
// `root/bin`, got nothing, and `continue`d, so an msvc toolset installed
119+
// perfectly well and then did not appear in the list. Empty = no frontend
120+
// here, which is the caller's cue to skip; a wrong LAYOUT and a missing
121+
// PAYLOAD had been reporting the same way.
122+
std::filesystem::path payload_frontend(const std::filesystem::path& payloadRoot,
123+
const XimToolchainPackage& pkg,
124+
Family family);
125+
109126
// Reverse mapping: an installed `xim-x-<name>` payload directory back to its
110127
// (family, target) identity. nullopt for non-toolchain xpkgs (ninja, glibc,
111128
// python, …) — list/doctor use this to filter what they enumerate.
@@ -357,6 +374,20 @@ std::filesystem::path toolchain_frontend(const std::filesystem::path& binDir,
357374
return {};
358375
}
359376

377+
std::filesystem::path payload_frontend(const std::filesystem::path& payloadRoot,
378+
const XimToolchainPackage& pkg,
379+
Family family) {
380+
if (family == Family::Msvc) {
381+
// Same resolution the install and build paths use, so the three
382+
// cannot disagree about where an msvc payload keeps its compiler.
383+
if (auto inst = mcpp::toolchain::msvc::installation_at(payloadRoot,
384+
pkg.ximVersion))
385+
return inst->clPath;
386+
return {};
387+
}
388+
return toolchain_frontend(payloadRoot / "bin", pkg);
389+
}
390+
360391
std::optional<PayloadIdentity> identify_xim_payload(std::string_view ximDirName) {
361392
if (ximDirName == "gcc")
362393
return PayloadIdentity{ Family::Gcc, {} };

tests/unit/test_toolchain_msvc.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,34 @@ TEST(MsvcManaged, VersionFallsBackToTheDeclaredOneWhenTheBannerCannotBeRead) {
255255
EXPECT_EQ(inst->display_version(), "14.52.36629");
256256
}
257257

258+
TEST(MsvcManaged, PayloadFrontendFindsClWhereMsvcActuallyKeepsIt) {
259+
// The defect this pins: `toolchain list` asked `toolchain_frontend(root /
260+
// "bin", …)`, got nothing, and skipped the row -- so an msvc toolset
261+
// installed correctly and then did not appear anywhere. Three places need
262+
// to know that cl.exe is four levels deeper than `bin/`; two knew.
263+
FakeToolset t{"frontend"};
264+
t.add_toolset("14.44.35207");
265+
266+
auto spec = parse_toolchain_spec("msvc@14.44.35207");
267+
ASSERT_TRUE(spec.has_value());
268+
auto pkg = to_xim_package(*spec);
269+
270+
auto found = payload_frontend(t.root, pkg, Family::Msvc);
271+
ASSERT_FALSE(found.empty()) << "payload_frontend found no cl.exe under " << t.root;
272+
EXPECT_EQ(found.filename(), "cl.exe");
273+
274+
// The `bin/`-shaped question is the one that used to be asked, and it
275+
// still answers nothing here — which is exactly why it was the wrong
276+
// question rather than a broken implementation.
277+
EXPECT_TRUE(toolchain_frontend(t.root / "bin", pkg).empty());
278+
279+
// A root with no toolset at that version stays empty rather than
280+
// returning a path that does not exist.
281+
EXPECT_TRUE(payload_frontend(t.root,
282+
to_xim_package(*parse_toolchain_spec("msvc@14.52.36629")),
283+
Family::Msvc).empty());
284+
}
285+
258286
// ─── Windows SDK discovery ───────────────────────────────────────────────
259287

260288
TEST(MsvcSdk, WindowsSdkDirBeatsTheHardcodedPaths) {

0 commit comments

Comments
 (0)