From 3afbc4fd9f3a4b9c323b44a1247345610bdd5adc Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 25 Aug 2026 00:54:40 +0800 Subject: [PATCH 1/2] =?UTF-8?q?2026.8.24.5=20=E2=80=94=20say=20what=20the?= =?UTF-8?q?=20env=20segment=20names,=20where=20it=20does=20not=20name=20a?= =?UTF-8?q?=20C=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `x86_64-windows-gnu` 之上一行写着 `c-abi musl`,读者两次在同一处停下来问 「这个 gnu / musl 的矛盾还在吗」。它不是矛盾:Windows 上 `gnu` 命名的是 **对象 ABI**,它对应的行是 `c++-abi`,不是 `c-abi`。 ⭐ 沉默作为**诊断**是对的,作为**报告**是不够的。报告里没有一行叫 `gnu`, 于是读者把它映到最像 C 库名字的那一行。加警告则是错的 —— 它会在每一次 合法的 MinGW 构建上出现,而且说的内容本身是假的。 Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu names the object ABI, not a C library) ── 布尔是对事实的有损编码 ────────────────────────────── `envNamesCAbi` 只回答「该段是不是 C 库」,足以压住错误的警告, 却给不出正确的名字。换成 `EnvAxis { Unknown, CLibrary, ObjectAbi, ObjectFormat }`: linux gnu / musl C 库 ← 可以与图矛盾,报警告 windows gnu / msvc 对象 ABI ← 不矛盾,报告加注 none elf 对象格式 ← 同上 一个枚举,三个平台,零 `#if`。 ⚠️ **该提示不出现在 C 库来自载荷时。** 那种情况下 C 库正是三元组选中的 —— 三元组就是选中它的方式 —— `gnu → ucrt` 是可见的因果,加注就成了每次 普通 Windows 构建上的噪声。作用域与既有那条警告的作用域一致 (`if (!ts.cAbi.fromGraph()) return std::nullopt;`),这不是巧合。 实测三种情形互不干扰: --target x86_64-windows-gnu 加注,无警告 --target x86_64-linux 什么都不加(没作请求) --target x86_64-linux-gnu 警告 + 可粘贴的改法 单元测试 targetside 41 → 45,含一条断言「载荷来源时不加注」的反例; 93 通过 / 0 失败。docs/15 中英双份同步。 --- CHANGELOG.md | 29 +++++++++++ docs/15-openkal-cross.md | 20 ++++++++ docs/zh/15-openkal-cross.md | 19 +++++++ mcpp.toml | 2 +- src/build/prepare.cppm | 14 ++++-- src/targetside/model.cppm | 92 +++++++++++++++++++++++++++------- src/version.cppm | 2 +- tests/unit/test_targetside.cpp | 78 ++++++++++++++++++++++++++-- 8 files changed, 229 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b73b20ed..f45cce78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,35 @@ > 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。 > 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。 +## [2026.8.24.5] — 2026-08-25 + +### 改进 + +- **三元组的 env 段在每个平台上命名不同的轴,而报告此前只是对此保持沉默。** + + ``` + Target x86_64-windows-gnu → x86_64-w64-windows-gnu + c-abi musl (openkal-musl@0.3.3, graph) + ``` + + ⭐ 沉默作为**诊断**是对的 —— 在 Windows 上报「名字请求了 `gnu` C ABI」 + 会在每一次合法的 MinGW 构建上出现,而且说的是错的。作为**报告**则不够: + 读者在其中找不到一行叫 `gnu`,于是把它映到最像 C 库名字的那一行。 + 它真正对应的是 `c++-abi`。 + + 于是在该段不命名 C 库的平台上,报告直接说出它命名的是什么: + + ``` + Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu names the object ABI, not a C library) + ``` + + 内部把 `envNamesCAbi` 这个布尔换成 `EnvAxis`,因为布尔是对事实的有损编码: + 该段在 Linux 上是 C 库、Windows 上是对象 ABI、无操作系统时是对象格式。 + 一个只回答「是否为第一种」的布尔,能压住错误的警告,却给不出正确的名字。 + + ⚠️ 该提示**不出现**在 C 库来自载荷时:那种情况下 C 库正是三元组选中的, + `gnu → ucrt` 是可见的因果,加注就成了每次普通 Windows 构建上的噪声。 + ## [2026.8.24.4] — 2026-08-24 ### 修复 diff --git a/docs/15-openkal-cross.md b/docs/15-openkal-cross.md index f15b4865..2c517c0a 100644 --- a/docs/15-openkal-cross.md +++ b/docs/15-openkal-cross.md @@ -129,6 +129,26 @@ GNU ABI, `msvc` for PE with Microsoft's — and both are compatible with more th one C library. The mismatch report is therefore scoped to platforms where the segment names a C library. +Silence is right as a diagnostic and insufficient as a report. A reader sees + +``` +Target x86_64-windows-gnu → x86_64-w64-windows-gnu + c-abi musl (openkal-musl@0.3.3, graph) +``` + +finds no row called `gnu`, and maps it to the nearest thing that resembles a C +library name. The row it belongs to is `c++-abi`. Where the segment does not +name a C library, the report therefore names what it does: + +``` +Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu names the object ABI, not a C library) +``` + +The segment carries a different axis on each platform — the C library on Linux, +the object ABI on Windows, the object format where there is no operating system +— and one value records which, rather than a boolean recording only whether the +first case holds. + ## Bare Metal A target with no operating system is the same model with the platform layer diff --git a/docs/zh/15-openkal-cross.md b/docs/zh/15-openkal-cross.md index eadb9e5e..34b6da07 100644 --- a/docs/zh/15-openkal-cross.md +++ b/docs/zh/15-openkal-cross.md @@ -110,6 +110,25 @@ mcpp build --target x86_64-linux `msvc` 是 PE 加微软的 —— 而两者都与不止一种 C 库相容。 因此该项报出被限定在该段命名 C 库的那些平台上。 +⭐ 沉默作为**诊断**是对的,作为**报告**是不够的。读者看到 + +``` +Target x86_64-windows-gnu → x86_64-w64-windows-gnu + c-abi musl (openkal-musl@0.3.3, graph) +``` + +在其中找不到一行叫 `gnu`,于是把它映到最像 C 库名字的那一行。 +它真正对应的是 `c++-abi`。因此在该段不命名 C 库的平台上, +报告直接说出它命名的是什么: + +``` +Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu names the object ABI, not a C library) +``` + +该段在每个平台上承载不同的轴 —— Linux 上是 C 库,Windows 上是对象 ABI, +没有操作系统时是对象格式 —— 因此记录的是**它是哪一个**, +而不是一个只记录「是否为第一种」的布尔。 + ## 裸机 一个没有操作系统的目标,是同一个模型,只是平台层由固件而非内核供给。 diff --git a/mcpp.toml b/mcpp.toml index c667c5c0..bc91cdbd 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "mcpp" -version = "2026.8.24.4" +version = "2026.8.24.5" description = "Modern C++ build & package management tool" license = "Apache-2.0" authors = ["mcpp-community"] diff --git a/src/build/prepare.cppm b/src/build/prepare.cppm index a3d18225..cc48dcbc 100644 --- a/src/build/prepare.cppm +++ b/src/build/prepare.cppm @@ -5818,10 +5818,16 @@ prepare_build(bool print_fingerprint, auto bare = *tt; bare.env.clear(); in.requestFreeTarget = bare.str(); } - // Only on Linux does the segment name a C library. On Windows - // it names the object ABI and on bare metal the object format, - // and neither is the axis the graph's C library sits on. - in.envNamesCAbi = tt->os == "linux"; + // The segment names a different axis on each platform, and + // saying WHICH lets the report gloss it instead of merely + // withholding a warning. Only the C-library case can contradict + // what the graph resolved; the other two are simply a different + // question, and the report says so. + in.envAxis = + tt->os == "linux" ? tsd::EnvAxis::CLibrary + : tt->os == "windows" ? tsd::EnvAxis::ObjectAbi + : tt->is_freestanding() ? tsd::EnvAxis::ObjectFormat + : tsd::EnvAxis::Unknown; // `sysroot = ""` and "no sysroot key" are different answers and // must not be collapsed: the first says this project wants no diff --git a/src/targetside/model.cppm b/src/targetside/model.cppm index 4820d1c5..64e26ba0 100644 --- a/src/targetside/model.cppm +++ b/src/targetside/model.cppm @@ -64,6 +64,32 @@ constexpr std::string_view origin_name(Origin o) { } return "none"; } +// What the env segment of a target triple names — an axis that changes with the +// OS, which is why the triple alone cannot answer "is `gnu` a C library". +// +// linux `gnu` / `musl` the C LIBRARY +// windows `gnu` / `msvc` the OBJECT ABI (both allow several C libraries) +// none `elf` the OBJECT FORMAT +// +// The distinction is load-bearing twice over. It decides whether a mismatch +// between the segment and the resolved C library is a contradiction worth +// reporting, and — when it is NOT — it lets the report say what the segment +// does mean, so that a reader looking at `x86_64-windows-gnu` above a line +// reading `c-abi musl` is not left to work out which row `gnu` belongs to. +enum class EnvAxis { Unknown, CLibrary, ObjectAbi, ObjectFormat }; + +// The noun for an axis, as it appears in the report. Empty for `Unknown`, +// because a report that cannot name the axis says nothing rather than guessing. +inline std::string_view env_axis_noun(EnvAxis a) { + switch (a) { + case EnvAxis::CLibrary: return "a C library"; + case EnvAxis::ObjectAbi: return "the object ABI"; + case EnvAxis::ObjectFormat: return "the object format"; + case EnvAxis::Unknown: break; + } + return {}; +} + // ── One layer of the target side ───────────────────────────────────────────── // @@ -132,6 +158,7 @@ struct TargetSide { // bearing and belongs in the report. std::string llvmTriple; + Layer compiler; Layer compilerRuntime; Layer kernelAbi; @@ -146,9 +173,9 @@ struct TargetSide { // the request turns out to describe nothing. Built by the caller, which is // the only place that still holds mcpp's own triple. std::string requestFreeTarget; - // Whether the env segment names a C library on this platform. See the - // member of the same name on `Inputs`. - bool envNamesCAbi = false; + // What the env segment names on this platform. See the member of the same + // name on `Inputs`. + EnvAxis envAxis = EnvAxis::Unknown; // The single question the five former derivation sites actually asked. // @@ -326,17 +353,20 @@ struct Inputs { std::string requestedCAbi; // The same target spelled without that segment, for the suggestion. std::string requestFreeTarget; - // ⚠️ WHETHER THE ENV SEGMENT NAMES A C LIBRARY ON THIS PLATFORM, WHICH IS - // NOT TRUE EVERYWHERE AND WAS ASSUMED TO BE. + // ⚠️ WHAT THE ENV SEGMENT NAMES ON THIS PLATFORM. It is a different axis + // per OS, and a boolean here was a lossy encoding of that. + // + // On Linux the segment names the C library — `gnu` is glibc, `musl` is musl + // — which is the case the request check was written for. On Windows it + // names the OBJECT ABI: `gnu` is PE with the GNU ABI and `msvc` is PE with + // Microsoft's, and both are compatible with more than one C library. On a + // target with no operating system it names the object FORMAT. // - // The segment carries a different axis depending on the OS. On Linux it - // names the C library — `gnu` is glibc, `musl` is musl — which is the case - // the request check was written for. On Windows it names the OBJECT ABI: - // `gnu` is PE with the GNU ABI and `msvc` is PE with Microsoft's, and both - // are compatible with more than one C library. Reporting a Windows build as - // "asking for the `gnu` C ABI" describes an axis the name never addressed, - // and the correction it suggested named a target that does not exist. - bool envNamesCAbi = false; + // Reporting a Windows build as "asking for the `gnu` C ABI" describes an + // axis the name never addressed, and the correction it suggested named a + // target that does not exist. Knowing which axis it IS lets the report say + // so instead of merely staying silent. + EnvAxis envAxis = EnvAxis::Unknown; std::optional compilerRuntime; std::optional kernelAbi; @@ -393,7 +423,7 @@ inline TargetSide resolve(const Inputs& in) { ts.llvmTriple = in.llvmTriple; ts.requestedCAbi = in.requestedCAbi; ts.requestFreeTarget = in.requestFreeTarget; - ts.envNamesCAbi = in.envNamesCAbi; + ts.envAxis = in.envAxis; // compiler — always a payload, never a package. if (!in.compilerFamily.empty()) @@ -592,7 +622,7 @@ check_requirements(const TargetSide& ts, std::span reqs) { // first. Telling someone their target name is wrong is only useful once there // is a right one to give them. inline std::optional check_request(const TargetSide& ts) { - if (!ts.envNamesCAbi) return std::nullopt; + if (ts.envAxis != EnvAxis::CLibrary) return std::nullopt; if (ts.requestedCAbi.empty()) return std::nullopt; if (ts.cAbi.absent()) return std::nullopt; if (ts.cAbi.interfaceName == ts.requestedCAbi) return std::nullopt; @@ -714,8 +744,36 @@ inline std::string format_report(const TargetSide& ts, std::string_view targetNa // status line's own padding, and the layer lines below are indented to sit // under it. std::string head = (ts.llvmTriple.empty() || ts.llvmTriple == targetName) - ? std::format("{}\n", targetName) - : std::format("{} → {}\n", targetName, ts.llvmTriple); + ? std::format("{}", targetName) + : std::format("{} → {}", targetName, ts.llvmTriple); + + // ⚠️ WHEN THE SEGMENT IS NOT A C LIBRARY, SAY WHAT IT IS — HERE, WHERE THE + // READER IS LOOKING AT IT. + // + // `x86_64-windows-gnu` above a line reading `c-abi musl` is not a + // contradiction: on Windows `gnu` names the object ABI, and the row it + // actually corresponds to is `c++-abi`. But the report contains no row + // called `gnu`, so a reader maps it to the nearest thing that looks like a + // C library name and concludes the build disagrees with itself. Measured + // twice, by the same reader, on two different days. + // + // A warning would be wrong — it would fire on every legitimate MinGW build + // and would say something false. A noun on the head line is not a + // diagnostic; it is the missing half of a name the report was already + // showing. + // + // Scoped to the case that actually reads as a contradiction: the segment is + // present, it does not name a C library here, and the C library came from + // somewhere the segment did not choose. A payload C library IS selected by + // the triple, so `gnu → ucrt` follows visibly and needs no gloss. + if (ts.envAxis != EnvAxis::Unknown && ts.envAxis != EnvAxis::CLibrary + && !ts.requestedCAbi.empty() + && !ts.cAbi.absent() && ts.cAbi.fromGraph() + && ts.cAbi.interfaceName != ts.requestedCAbi) { + head += std::format(" ({} names {}, not a C library)", + ts.requestedCAbi, env_axis_noun(ts.envAxis)); + } + head += '\n'; auto body = format_layers(ts, verbose); if (!body.empty() && body.back() == '\n') body.pop_back(); diff --git a/src/version.cppm b/src/version.cppm index 4be1e591..b2881efb 100644 --- a/src/version.cppm +++ b/src/version.cppm @@ -31,6 +31,6 @@ import std; export namespace mcpp { -inline constexpr std::string_view MCPP_VERSION = "2026.8.24.4"; +inline constexpr std::string_view MCPP_VERSION = "2026.8.24.5"; } // namespace mcpp diff --git a/tests/unit/test_targetside.cpp b/tests/unit/test_targetside.cpp index 0ab741dd..d440a882 100644 --- a/tests/unit/test_targetside.cpp +++ b/tests/unit/test_targetside.cpp @@ -407,7 +407,7 @@ TEST(TargetSideRequest, AFilledEnvSegmentStatesNothing) { auto in = payload_linux(); in.compilerFamily = "llvm"; in.cAbi = provider("openkal-musl", "0.3.3", "musl"); - in.envNamesCAbi = true; + in.envAxis = ts::EnvAxis::CLibrary; // `--target x86_64-linux` — the project declined to name a C library, so // the parser's fill must not become a claim it can be held to. in.requestedCAbi.clear(); @@ -424,7 +424,7 @@ TEST(TargetSideRequest, AWrittenEnvSegmentThatDisagreesIsReported) { in.cAbi = provider("openkal-musl", "0.3.3", "musl"); in.requestedCAbi = "gnu"; in.requestFreeTarget = "x86_64-linux"; - in.envNamesCAbi = true; + in.envAxis = ts::EnvAxis::CLibrary; auto why = ts::check_request(ts::resolve(in)); ASSERT_TRUE(why.has_value()); EXPECT_NE(why->find("`gnu`"), std::string::npos); @@ -438,7 +438,7 @@ TEST(TargetSideRequest, APrebuiltCLibraryIsWhatTheRequestSelected) { auto in = payload_linux(); in.compilerFamily = "llvm"; in.requestedCAbi = "musl"; - in.envNamesCAbi = true; + in.envAxis = ts::EnvAxis::CLibrary; // No graph supplier: the payload's C library IS the request's answer, so // there is nothing to contradict even when the names differ. EXPECT_EQ(ts::check_request(ts::resolve(in)), std::nullopt); @@ -456,10 +456,80 @@ TEST(TargetSideRequest, TheSegmentIsOnlyACLibraryWhereItNamesOne) { in.cAbi = provider("openkal-musl", "0.3.3", "musl"); in.requestedCAbi = "gnu"; in.requestFreeTarget = "x86_64-windows"; - in.envNamesCAbi = false; + in.envAxis = ts::EnvAxis::ObjectAbi; EXPECT_EQ(ts::check_request(ts::resolve(in)), std::nullopt); } +// ⭐ AND WHEN IT DOES NOT NAME ONE, THE REPORT SAYS WHAT IT DOES NAME. +// +// Staying silent is correct as a DIAGNOSTIC and insufficient as a REPORT. The +// reader sees `x86_64-windows-gnu` above a line reading `c-abi musl`, finds no +// row called `gnu`, and maps it to the nearest thing that looks like a C +// library name. Measured twice, by the same reader, on two different days. +TEST(TargetSideReport, AnEnvSegmentThatIsNotACLibraryIsGlossed) { + auto in = payload_linux(); + in.targetOs = "windows"; + in.compilerFamily = "llvm"; + in.cAbi = provider("openkal-musl", "0.3.3", "musl"); + in.requestedCAbi = "gnu"; + in.requestFreeTarget = "x86_64-windows"; + in.envAxis = ts::EnvAxis::ObjectAbi; + + auto r = ts::format_report(ts::resolve(in), "x86_64-windows-gnu"); + EXPECT_NE(r.find("gnu names the object ABI, not a C library"), + std::string::npos) << r; +} + +// A bare-metal target's segment names the object FORMAT, and the same gloss +// applies with a different noun. One enum, three platforms, no `#if`. +TEST(TargetSideReport, OnBareMetalTheSegmentNamesTheObjectFormat) { + auto in = payload_linux(); + in.targetOs = "none"; + in.compilerFamily = "llvm"; + in.cAbi = provider("openkal-musl", "0.3.3", "musl"); + in.requestedCAbi = "elf"; + in.requestFreeTarget = "riscv64-none"; + in.envAxis = ts::EnvAxis::ObjectFormat; + + auto r = ts::format_report(ts::resolve(in), "riscv64-none-elf"); + EXPECT_NE(r.find("elf names the object format, not a C library"), + std::string::npos) << r; +} + +// ⚠️ AND IT DOES NOT FIRE WHEN THE C LIBRARY CAME FROM A PAYLOAD. +// +// A payload C library IS what the triple selected — the triple is how it was +// selected — so `gnu → ucrt` follows visibly and a gloss would be noise on +// every ordinary Windows build. +TEST(TargetSideReport, NoGlossWhenTheTripleItselfChoseTheCLibrary) { + auto in = payload_linux(); + in.targetOs = "windows"; + in.compilerFamily = "llvm"; + in.requestedCAbi = "gnu"; + in.requestFreeTarget = "x86_64-windows"; + in.envAxis = ts::EnvAxis::ObjectAbi; + // cAbi left as the payload's — not from the graph. + + auto r = ts::format_report(ts::resolve(in), "x86_64-windows-gnu"); + EXPECT_EQ(r.find("not a C library"), std::string::npos) << r; +} + +// On Linux the segment DOES name a C library, so there is nothing to gloss — +// the disagreement is reported as a warning instead, which is a different +// mechanism and must not double up. +TEST(TargetSideReport, NoGlossWhereTheSegmentNamesACLibrary) { + auto in = payload_linux(); + in.compilerFamily = "llvm"; + in.cAbi = provider("openkal-musl", "0.3.3", "musl"); + in.requestedCAbi = "gnu"; + in.requestFreeTarget = "x86_64-linux"; + in.envAxis = ts::EnvAxis::CLibrary; + + auto r = ts::format_report(ts::resolve(in), "x86_64-linux-gnu"); + EXPECT_EQ(r.find("not a C library"), std::string::npos) << r; + EXPECT_NE(ts::check_request(ts::resolve(in)), std::nullopt); +} + // ── Rule two: declared requirements ────────────────────────────────────────── TEST(TargetSideRequirements, ARequirementIsCheckedAgainstWhatResolved) { From f02e72bf22e4d1c07a48f3da98b5012f6f4e943e Mon Sep 17 00:00:00 2001 From: speak-agent <248744407+speak-agent@users.noreply.github.com> Date: Tue, 25 Aug 2026 01:15:22 +0800 Subject: [PATCH 2/2] docs+report: the segment selects an ABI, and that ABI is not any row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 复查时被问住两处,量了一遍,其中一处是我写错的。 **`musl 是 gnu ABI 吗?`** 不是,而且 `gnu` 也没在说 C 库。 对该次构建的产物实测: 导入的库 ntdll、KERNEL32、SHELL32 —— 无 msvcrt,无 ucrtbase _Z… 符号 4507 ?… 符号 0 第一行说明 `c-abi musl` 是老实的:MinGW 的 C 运行时一点没链进来。 **`libc++ 是 gnu?`** ⚠️ **不是,而我在上一版里写反了。** 我说 「`gnu` 对应的是 `c++-abi libc++` 那一行」,这是错的:`gnu` 选中的是 **Itanium C++ ABI**(那 4507 个 `_Z` 就是它),而 libc++ 是标准库的一个 实现,libstdc++ 是另一个,**两者都坐在同一套 ABI 上**。 ⭐ 真相是它**不对应任何一行**,而这正是要点。五层记录的是每一层 **由谁供给**;`gnu` 命名的是这些**对象遵循哪套约定** —— 若干层必须一致的 横切事项,不是一层。于是提示改为命名那套 ABI 本身: (gnu selects the Itanium C++ ABI, not a C library) `Itanium` 与 `MSVC` 不出现在任何一行里,因此不会像「the C++ ABI」那样 被误当成 `c++-abi` 那一层。这是把一个错误答案换成正确答案, 而不是把含糊换成含糊。 ⚠️ **名词取自值而非仅取自轴。** `gnu` 与 `msvc` 同轴而选中相反的 ABI, 按轴取名会给 MSVC 构建打印「Itanium」—— 那不只是含糊,是假的。 新增测试断言这一点,并断言提示里的名字**不等于任何一层的实现名**, 否则同一种混淆会换个地方重现。 三处带着那句错话的文本(model.cppm 注释、docs/15 中英)一并改掉。 targetside 单元测试 45 → 47,93 通过 / 0 失败。 --- CHANGELOG.md | 21 ++++++++++++++-- docs/15-openkal-cross.md | 26 ++++++++++++++++--- docs/zh/15-openkal-cross.md | 23 ++++++++++++++--- src/targetside/model.cppm | 42 +++++++++++++++++++++++-------- tests/unit/test_targetside.cpp | 46 ++++++++++++++++++++++++++++++++-- 5 files changed, 138 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f45cce78..bda290c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,29 @@ 读者在其中找不到一行叫 `gnu`,于是把它映到最像 C 库名字的那一行。 它真正对应的是 `c++-abi`。 - 于是在该段不命名 C 库的平台上,报告直接说出它命名的是什么: + 于是在该段不命名 C 库的平台上,报告直接说出它选中的是什么: ``` - Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu names the object ABI, not a C library) + Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu selects the Itanium C++ ABI, not a C library) ``` + ⭐ **它不对应报告里的任何一行,而这正是要点。** 对这次构建的产物实测: + + | 观测 | 值 | + |---|---| + | 导入的库 | `ntdll`、`KERNEL32`、`SHELL32` —— 无 `msvcrt`,无 `ucrtbase` | + | Itanium 修饰符号(`_Z…`) | 4507 | + | MSVC 修饰符号(`?…`) | 0 | + + 第一行说明 `c-abi musl` 是老实的:MinGW 的 C 运行时一点没链进来。 + 五层记录的是每一层**由谁供给**;`gnu` 命名的是这些**对象遵循哪套约定**。 + 把它读成 `c++-abi libc++` 是第二个错误答案 —— libstdc++ 同样坐在 + Itanium ABI 上。因此报告命名那套 **ABI 本身**,它的名字不出现在任何一行里, + 于是不会被误当成某一层。 + + ⚠️ 名词取自**值**而非仅取自轴:`gnu` 与 `msvc` 同轴而选中相反的 ABI, + 按轴取名会给 MSVC 构建打印「Itanium」—— 那不只是含糊,是假的。 + 内部把 `envNamesCAbi` 这个布尔换成 `EnvAxis`,因为布尔是对事实的有损编码: 该段在 Linux 上是 C 库、Windows 上是对象 ABI、无操作系统时是对象格式。 一个只回答「是否为第一种」的布尔,能压住错误的警告,却给不出正确的名字。 diff --git a/docs/15-openkal-cross.md b/docs/15-openkal-cross.md index 2c517c0a..6bd4631b 100644 --- a/docs/15-openkal-cross.md +++ b/docs/15-openkal-cross.md @@ -137,11 +137,31 @@ Target x86_64-windows-gnu → x86_64-w64-windows-gnu ``` finds no row called `gnu`, and maps it to the nearest thing that resembles a C -library name. The row it belongs to is `c++-abi`. Where the segment does not -name a C library, the report therefore names what it does: +library name. + +Measured on the artefact of exactly that build: + +| Observation | Value | +|---|---| +| imported libraries | `ntdll`, `KERNEL32`, `SHELL32` — no `msvcrt`, no `ucrtbase` | +| Itanium-mangled symbols (`_Z…`) | 4507 | +| MSVC-mangled symbols (`?…`) | 0 | + +The first row is why `c-abi musl` is honest: none of MinGW's C runtime is +linked. The other two are what `gnu` selected — the Itanium C++ ABI rather than +Microsoft's. + +That correspondence is to no row of the report, and the absence is the point. +The five layers record who **supplies** each layer; `gnu` names a convention the +**objects follow**, which several layers must agree on. Reading it as `c++-abi +libc++` is a second wrong answer: libc++ is one implementation of the standard +library and libstdc++ is another, and both sit on the Itanium ABI. + +The report therefore names the ABI itself, whose name appears in no row and so +cannot be mistaken for one: ``` -Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu names the object ABI, not a C library) +Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu selects the Itanium C++ ABI, not a C library) ``` The segment carries a different axis on each platform — the C library on Linux, diff --git a/docs/zh/15-openkal-cross.md b/docs/zh/15-openkal-cross.md index 34b6da07..60bdfae6 100644 --- a/docs/zh/15-openkal-cross.md +++ b/docs/zh/15-openkal-cross.md @@ -118,11 +118,28 @@ Target x86_64-windows-gnu → x86_64-w64-windows-gnu ``` 在其中找不到一行叫 `gnu`,于是把它映到最像 C 库名字的那一行。 -它真正对应的是 `c++-abi`。因此在该段不命名 C 库的平台上, -报告直接说出它命名的是什么: + +对这次构建的产物实测: + +| 观测 | 值 | +|---|---| +| 导入的库 | `ntdll`、`KERNEL32`、`SHELL32` —— 无 `msvcrt`,无 `ucrtbase` | +| Itanium 修饰符号(`_Z…`) | 4507 | +| MSVC 修饰符号(`?…`) | 0 | + +第一行说明 `c-abi musl` 是老实的:MinGW 的 C 运行时一点没链进来。 +后两行是 `gnu` 实际选中的东西 —— Itanium C++ ABI,而不是微软那套。 + +⭐ 而它**不对应报告里的任何一行**,这正是要点。五层记录的是每一层 +**由谁供给**;`gnu` 命名的是这些**对象遵循哪套约定**,是若干层必须一致的 +横切事项。把它读成 `c++-abi libc++` 是第二个错误答案:libc++ 是标准库的 +一个实现,libstdc++ 是另一个,两者都坐在 Itanium ABI 上。 + +因此报告直接说出那套 ABI 本身 —— 它的名字不出现在任何一行里, +于是不会被误当成某一层: ``` -Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu names the object ABI, not a C library) +Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu selects the Itanium C++ ABI, not a C library) ``` 该段在每个平台上承载不同的轴 —— Linux 上是 C 库,Windows 上是对象 ABI, diff --git a/src/targetside/model.cppm b/src/targetside/model.cppm index 64e26ba0..8d030977 100644 --- a/src/targetside/model.cppm +++ b/src/targetside/model.cppm @@ -78,12 +78,21 @@ constexpr std::string_view origin_name(Origin o) { // reading `c-abi musl` is not left to work out which row `gnu` belongs to. enum class EnvAxis { Unknown, CLibrary, ObjectAbi, ObjectFormat }; -// The noun for an axis, as it appears in the report. Empty for `Unknown`, +// The noun for a segment, as it appears in the report. Empty for `Unknown`, // because a report that cannot name the axis says nothing rather than guessing. -inline std::string_view env_axis_noun(EnvAxis a) { +// +// ⚠️ THE VALUE, NOT ONLY THE AXIS. `gnu` and `msvc` sit on the same axis and +// select opposite ABIs, so a noun fixed per axis would print "the Itanium C++ +// ABI" for an MSVC build. Naming the ABI rather than the axis is deliberate: +// `Itanium` and `MSVC` appear in no row of the report, so neither can be +// mistaken for a layer the way "the C++ ABI" would be. +inline std::string_view env_axis_noun(EnvAxis a, std::string_view segment = {}) { switch (a) { case EnvAxis::CLibrary: return "a C library"; - case EnvAxis::ObjectAbi: return "the object ABI"; + case EnvAxis::ObjectAbi: + if (segment == "gnu") return "the Itanium C++ ABI"; + if (segment == "msvc") return "the MSVC C++ ABI"; + return "the object ABI"; case EnvAxis::ObjectFormat: return "the object format"; case EnvAxis::Unknown: break; } @@ -751,11 +760,23 @@ inline std::string format_report(const TargetSide& ts, std::string_view targetNa // READER IS LOOKING AT IT. // // `x86_64-windows-gnu` above a line reading `c-abi musl` is not a - // contradiction: on Windows `gnu` names the object ABI, and the row it - // actually corresponds to is `c++-abi`. But the report contains no row - // called `gnu`, so a reader maps it to the nearest thing that looks like a - // C library name and concludes the build disagrees with itself. Measured - // twice, by the same reader, on two different days. + // contradiction. Measured on the artefact of exactly that build: + // + // imports ntdll, KERNEL32, SHELL32 — no msvcrt, no ucrtbase + // `_Z…` symbols 4507 + // `?…` symbols 0 + // + // The first line is why `c-abi musl` is honest: none of MinGW's C runtime + // is linked. The other two are what `gnu` actually selected — the Itanium + // C++ ABI rather than Microsoft's. + // + // ⭐ AND IT CORRESPONDS TO NO ROW OF THIS REPORT, WHICH IS THE POINT. The + // five layers record who SUPPLIES each layer; `gnu` names a convention the + // OBJECTS FOLLOW, and several layers must agree on it. Pointing the reader + // at `c++-abi libc++` would be a second wrong answer: libc++ is one + // implementation of the standard library, libstdc++ is another, and both + // sit on the Itanium ABI. The gloss therefore names the ABI itself, whose + // name appears in no row and so cannot be mistaken for one. // // A warning would be wrong — it would fire on every legitimate MinGW build // and would say something false. A noun on the head line is not a @@ -770,8 +791,9 @@ inline std::string format_report(const TargetSide& ts, std::string_view targetNa && !ts.requestedCAbi.empty() && !ts.cAbi.absent() && ts.cAbi.fromGraph() && ts.cAbi.interfaceName != ts.requestedCAbi) { - head += std::format(" ({} names {}, not a C library)", - ts.requestedCAbi, env_axis_noun(ts.envAxis)); + head += std::format(" ({} selects {}, not a C library)", + ts.requestedCAbi, + env_axis_noun(ts.envAxis, ts.requestedCAbi)); } head += '\n'; diff --git a/tests/unit/test_targetside.cpp b/tests/unit/test_targetside.cpp index d440a882..35ecbbfc 100644 --- a/tests/unit/test_targetside.cpp +++ b/tests/unit/test_targetside.cpp @@ -476,7 +476,7 @@ TEST(TargetSideReport, AnEnvSegmentThatIsNotACLibraryIsGlossed) { in.envAxis = ts::EnvAxis::ObjectAbi; auto r = ts::format_report(ts::resolve(in), "x86_64-windows-gnu"); - EXPECT_NE(r.find("gnu names the object ABI, not a C library"), + EXPECT_NE(r.find("gnu selects the Itanium C++ ABI, not a C library"), std::string::npos) << r; } @@ -492,10 +492,52 @@ TEST(TargetSideReport, OnBareMetalTheSegmentNamesTheObjectFormat) { in.envAxis = ts::EnvAxis::ObjectFormat; auto r = ts::format_report(ts::resolve(in), "riscv64-none-elf"); - EXPECT_NE(r.find("elf names the object format, not a C library"), + EXPECT_NE(r.find("elf selects the object format, not a C library"), std::string::npos) << r; } +// ⚠️ THE NOUN COMES FROM THE VALUE, NOT ONLY FROM THE AXIS. +// +// `gnu` and `msvc` sit on the same axis and select OPPOSITE ABIs. A noun fixed +// per axis would print "the Itanium C++ ABI" for an MSVC build — a statement +// that is not merely vague but false. +TEST(TargetSideReport, TheOppositeValueOnTheSameAxisGetsTheOppositeName) { + auto in = payload_linux(); + in.targetOs = "windows"; + in.compilerFamily = "llvm"; + in.cAbi = provider("openkal-musl", "0.3.3", "musl"); + in.requestedCAbi = "msvc"; + in.requestFreeTarget = "x86_64-windows"; + in.envAxis = ts::EnvAxis::ObjectAbi; + + auto r = ts::format_report(ts::resolve(in), "x86_64-windows-msvc"); + EXPECT_NE(r.find("msvc selects the MSVC C++ ABI"), std::string::npos) << r; + EXPECT_EQ(r.find("Itanium"), std::string::npos) << r; +} + +// ⭐ AND THE NAME IT PRINTS MUST NOT BE ANY ROW'S VALUE. +// +// Naming the ABI rather than the axis is what stops a reader mapping `gnu` to +// `c++-abi libc++` — a second wrong answer, since libstdc++ sits on the same +// ABI. If the gloss ever printed a string that also appears as a layer's +// implementation name, the confusion would return in a new place. +TEST(TargetSideReport, TheGlossNamesNothingThatAppearsAsALayerValue) { + auto in = payload_linux(); + in.targetOs = "windows"; + in.compilerFamily = "llvm"; + in.cAbi = provider("openkal-musl", "0.3.3", "musl"); + in.cxxAbi = provider("openkal-llvm-runtime", "0.1.2", "libc++"); + in.requestedCAbi = "gnu"; + in.requestFreeTarget = "x86_64-windows"; + in.envAxis = ts::EnvAxis::ObjectAbi; + + auto r = ts::format_report(ts::resolve(in), "x86_64-windows-gnu"); + auto head = r.substr(0, r.find('\n')); + EXPECT_NE(head.find("Itanium"), std::string::npos) << head; + EXPECT_EQ(head.find("libc++"), std::string::npos) << head; + EXPECT_EQ(head.find("musl"), std::string::npos) << head; +} + // ⚠️ AND IT DOES NOT FIRE WHEN THE C LIBRARY CAME FROM A PAYLOAD. // // A payload C library IS what the triple selected — the triple is how it was