From 35e0f3873b9b5f12f3b605f55875e2ad06be7d18 Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:04:34 +0800 Subject: [PATCH] feat(toolchain): `gcc@system` is rejected, and says what to do instead `@system` is available for MSVC only, and that is deliberate rather than incidental. xlings depends on the host as little as it can: a toolchain comes from a payload, which is what makes "the manifest says 14.44.35207" true on every machine instead of on the one that happened to have it. Offering `gcc@system` invites that uncertainty back, and the alternative is one command away. MSVC is the exception because Windows is. Visual Studio is frequently already installed and cannot always be redistributed, so refusing to use it would mean refusing to build. A platform fact, not a general capability -- so it is not generalised. Until now the spelling was merely unimplemented, which means it failed later, somewhere else, with a message about something else. It is now refused at the parse, with the version form and the reason for the one exception. Test asserts the refusal AND that the message names the alternative -- a refusal that does not is just a wall. Verified against a guard that never fires. --- src/toolchain/registry.cppm | 29 +++++++++++++++++++++++++++++ tests/unit/test_toolchain_msvc.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/toolchain/registry.cppm b/src/toolchain/registry.cppm index 0dd21fb9..eae2c842 100644 --- a/src/toolchain/registry.cppm +++ b/src/toolchain/registry.cppm @@ -229,6 +229,35 @@ parse_toolchain_spec(std::string compilerArg, "supported alias like mingw / musl-gcc)", compilerArg)); } + // `@system` means "whatever this machine has", and it is deliberately + // available for MSVC ONLY. + // + // xlings depends on the host as little as it can: a toolchain comes from + // a payload, which is what makes "the manifest says 14.44.35207" true on + // every machine instead of on the one that happened to have it. Offering + // `gcc@system` would invite the uncertainty back in, and the alternative + // is one command away. + // + // MSVC is the exception because Windows is: Visual Studio is frequently + // already installed and cannot always be redistributed, so refusing to + // use it would mean refusing to build. That is a platform fact, not a + // general capability, so it is not generalised. + // + // Rejected here rather than left unimplemented: an unimplemented spelling + // fails somewhere further in with a message about something else. + if (norm->version == "system" && norm->family != "msvc") { + return std::unexpected(std::format( + "'{}@system' is not a thing — mcpp does not build with the " + "machine's own {}.\n" + " A toolchain comes from a payload, so a manifest means the same " + "thing on every machine.\n" + " Name a version instead: `{}@` " + "(`mcpp toolchain list` shows what is available).\n" + " Only `msvc@system` exists, because Visual Studio cannot always " + "be redistributed.", + norm->family, norm->family, norm->family)); + } + ToolchainSpec spec; if (norm->family == "llvm") spec.family = Family::Llvm; else if (norm->family == "msvc") spec.family = Family::Msvc; diff --git a/tests/unit/test_toolchain_msvc.cpp b/tests/unit/test_toolchain_msvc.cpp index 034a9c0e..4bd1b48c 100644 --- a/tests/unit/test_toolchain_msvc.cpp +++ b/tests/unit/test_toolchain_msvc.cpp @@ -84,6 +84,30 @@ TEST(MsvcSpec, SystemOriginIsTheUnversionedSpec) { EXPECT_FALSE(is_system_toolchain(*gcc)); } +TEST(MsvcSpec, OnlyMsvcHasASystemOrigin) { + // xlings depends on the host as little as it can: a toolchain comes from + // a payload, which is what makes "the manifest says 14.44.35207" true on + // every machine rather than on the one that happened to have it. + // `gcc@system` would invite that uncertainty back. + // + // MSVC is the exception because Windows is — Visual Studio is often + // already installed and cannot always be redistributed. A platform fact, + // not a general capability, so it is not generalised. + // + // Rejected rather than merely unimplemented: an unimplemented spelling + // fails later, somewhere else, with a message about something else. + for (auto s : {"gcc@system", "llvm@system", "clang@system"}) { + auto spec = parse_toolchain_spec(s); + ASSERT_FALSE(spec.has_value()) << s << " was accepted"; + // The message has to say what to do instead, or it is just a refusal. + EXPECT_NE(spec.error().find("@"), std::string::npos) + << s << ": " << spec.error(); + EXPECT_NE(spec.error().find("msvc@system"), std::string::npos) + << s << ": " << spec.error(); + } + EXPECT_TRUE(parse_toolchain_spec("msvc@system").has_value()); +} + TEST(MsvcSpec, ToolsetVersionIsAManagedPayloadNotASystemSpec) { // The defect this closes: EVERY msvc spec used to be a system spec, so a // manifest could name a toolset and silently get whatever the machine