diff --git a/CHANGELOG.md b/CHANGELOG.md index b73b20ed..bda290c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,52 @@ > 本文件追踪 `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 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、无操作系统时是对象格式。 + 一个只回答「是否为第一种」的布尔,能压住错误的警告,却给不出正确的名字。 + + ⚠️ 该提示**不出现**在 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..6bd4631b 100644 --- a/docs/15-openkal-cross.md +++ b/docs/15-openkal-cross.md @@ -129,6 +129,46 @@ 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. + +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 selects the Itanium C++ 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..60bdfae6 100644 --- a/docs/zh/15-openkal-cross.md +++ b/docs/zh/15-openkal-cross.md @@ -110,6 +110,42 @@ 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 库名字的那一行。 + +对这次构建的产物实测: + +| 观测 | 值 | +|---|---| +| 导入的库 | `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 selects the Itanium C++ 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..8d030977 100644 --- a/src/targetside/model.cppm +++ b/src/targetside/model.cppm @@ -64,6 +64,41 @@ 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 a segment, as it appears in the report. Empty for `Unknown`, +// because a report that cannot name the axis says nothing rather than guessing. +// +// ⚠️ 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: + 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; + } + return {}; +} + // ── One layer of the target side ───────────────────────────────────────────── // @@ -132,6 +167,7 @@ struct TargetSide { // bearing and belongs in the report. std::string llvmTriple; + Layer compiler; Layer compilerRuntime; Layer kernelAbi; @@ -146,9 +182,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 +362,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 +432,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 +631,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 +753,49 @@ 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. 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 + // 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(" ({} selects {}, not a C library)", + ts.requestedCAbi, + env_axis_noun(ts.envAxis, ts.requestedCAbi)); + } + 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..35ecbbfc 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,122 @@ 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 selects the Itanium C++ 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 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 +// 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) {