日期: 2026-06-27
仓库: mcpp-community/mcpp(core)
触发来源: mcpp-community/mcpp-index PR #48 把 CI 的 mcpp 从 0.0.46 升到 0.0.67 后,
smoke_imgui_module.sh(显式 pin llvm@20.1.7)的 compat.glfw 传递依赖报错。
结论: 是一个真实的 mcpp core bug(非配方、非 smoke 脚本问题)。abi: 能力检查在推导
「当前工具链 abi」时,用 C++ 标准库身份(libc++/libstdc++) 抢占了 libc/C-运行时 ABI(glibc/musl),
导致 clang+libc++(目标三元组仍是 *-linux-gnu,即 glibc)被判成 abi=libc++,与纯 C 库 glfw 声明的
abi:glibc 不匹配而误报。修复点单一、明确,见 §6。
error: ABI mismatch: dependency 'compat.glfw' requires abi=glibc but the resolved
toolchain 'clang 20.1.7 (x86_64-unknown-linux-gnu)' is abi=libc++.
fix: `mcpp toolchain default <glibc-compatible>` (e.g. gcc@16.1.0 for glibc),
or set [toolchain] in mcpp.toml.
注意报错里工具链标签自己写着 x86_64-unknown-linux-gnu —— gnu 即 glibc。也就是说:
这台工具链在 libc 层就是 glibc,却被判成 abi=libc++ 然后拒绝一个 glibc 的 C 库。这本身就自相矛盾。
# mcpp.toml
[toolchain]
default = "llvm@20.1.7" # 换成 gcc@16.1.0 则通过
[dependencies.compat]
glfw = "3.4"#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
int main(){ return glfwInit()?0:0; }default = "gcc@16.1.0"→Finished(stdlibId=libstdc++ → tcAbi 落到glibc→ 匹配)。default = "llvm@20.1.7"→ 上面的 ABI mismatch(stdlibId=libc++ → tcAbi=libc++→ 不匹配abi:glibc)。
CI 现场(mcpp-index run 28285632233,smoke-full-linux)同一 job 内 gcc@16.1.0 连续解析成功 11 次,
随后 smoke_imgui_module.sh 一行 default = "${MCPP_INDEX_IMGUI_MODULE_TOOLCHAIN:-llvm@20.1.7}"
解析到 llvm@20.1.7 → clang++ 即触发本错误。与 glfw 的 GLX/窗口/显示无关,纯属依赖解析期的 ABI 门控。
src/build/prepare.cppm:2471-2491(能力驱动的 ABI 强制):
const std::string tcAbi =
ctx.tc.targetTriple.find("musl") != std::string::npos ? "musl"
: ctx.tc.stdlibId == "libc++" ? "libc++" // ← 病灶
: ctx.tc.compiler == mcpp::toolchain::CompilerId::MSVC ? "msvc"
: "glibc";
for (auto& cap : ctx.plan.runtimeCapabilities) {
if (cap.rfind("abi:", 0) != 0) continue;
std::string need = cap.substr(4); // glfw: "glibc"
if (need == tcAbi) continue; // "glibc" != "libc++" → 落入报错
...
return std::unexpected("ABI mismatch: ...");
}这里把两条正交的 ABI 轴塞进了一个 tcAbi 字符串:
| 轴 | 取值 | 决定它的东西 | 谁在乎 |
|---|---|---|---|
| A. libc / C 运行时 ABI | glibc / musl |
目标三元组(-gnu vs -musl) |
链接 C 库(glfw、zlib…)、syscall/CRT 兼容性 |
| B. C++ 标准库 | libstdc++ / libc++ / msvc-stl |
编译器/stdlibId |
跨 C++ TU 传递 std::* 类型时的 C++ ABI |
abi:glibc 是 glfw 声明的 A 轴诉求(「我是 glibc 二进制,别在 musl 上用我」)。而代码里
stdlibId == "libc++" ? "libc++" 这一支,用 B 轴的值抢在 glibc 兜底之前返回,等于把
「这台工具链的 C++ 标准库是 libc++」错误地当成了「这台工具链的 libc ABI 是 libc++」。
关键事实:clang+libc++ 在 x86_64-unknown-linux-gnu 上,libc 仍然是 glibc(libc++ 是 C++
标准库,不是 libc 替代品;它本身就链接 glibc)。src/toolchain/clang.cppm:140 一律把 Linux clang 的
stdlibId 设为 libc++,于是任何 clang 工具链都会被本检查判成 abi=libc++,从而拒绝所有声明
abi:glibc 的(C)依赖——尽管把 glibc 的 C 库链进 libc++ 的 C++ 程序完全合法(glfw 导出的是 C 符号,
跟 C++ 标准库选择无关)。
三元组里 musl 那一支是对的(用三元组判 libc);错的是 libc++ 这一支——它用 stdlibId 覆盖了
libc 轴。abi: 能力本应只看 A 轴。
- 该能力驱动 ABI 检查随
0.0.54(#129,e9ed0e9) 引入,晚于 0.0.46。mcpp-index CI 之前 pin0.0.46→ 根本没有这道门 → clang/libc++ 构建 glfw 一直「能过」(且实际可链接运行)。升到0.0.67才第一次撞上。 - 因此这是「新检查本身判据有误」,不是某次行为退化。但表现为「升级即坏」,需在 mcpp 侧修。
- 正交性:libc++ 与 glibc 不互斥,是两条轴。把它们对立起来在模型上就是错的。
- 真实影响面:
src/toolchain/clang.cppm:140使 Linux 下 clang 恒为stdlibId=libc++。任何用户主动选 clang (这正是「import std体验更好」的常见动机,也是 imgui-m 模块包 pinllvm@20.1.7的原因)再依赖任何声明abi:glibc的 C 库,都会被无理由挡下。当前 index 里grep到声明abi:glibc的只有pkgs/c/compat.glfw.lua一个,但这是给所有未来 C 库挖的坑——按本应的语义,zlib/lua/mbedtls 等纯 C 运行时库若也补abi:glibc(逻辑上它们都该有),就会集体在 clang 下挂掉。 - 诊断误导:报错建议
mcpp toolchain default gcc@16.1.0——把用户从 clang 赶到 gcc,而真实情况是 clang 本可正确构建,只是检查判据错了。
把 tcAbi 的推导改成只表达 libc/C-运行时 ABI,删掉 stdlibId == "libc++" 这一支:
// abi: 能力 = libc/C-运行时 ABI(glibc / musl / msvc),由目标三元组决定,与 C++ 标准库无关。
const std::string tcAbi =
ctx.tc.targetTriple.find("musl") != std::string::npos ? "musl"
: ctx.tc.compiler == mcpp::toolchain::CompilerId::MSVC ? "msvc"
: "glibc";这样 clang+libc++ @ *-linux-gnu → glibc,与 glfw 的 abi:glibc 匹配,clang 也能直接构建 C 库;
*-linux-musl 仍正确判 musl;MSVC 仍 msvc。单点改动,覆盖本 bug 全部表现。
(更稳妥可显式按三元组判:含 -gnu/gnueabi* → glibc,含 musl → musl,Windows/MSVC → msvc。)
若将来有C++ 库需要约束 libstdc++/libc++(跨 stdlib 的 C++ ABI 才有意义),用单独命名空间,例如
cxxstdlib:libstdc++ / cxxstdlib:libc++,且只对「在接口里暴露 std::* 的 C++ 库」施加;纯 C 库
(glfw 等)不该带这条。两轴分开校验,互不抢占。
prepare.cppm:2467-2470 注释自陈:工具链在依赖图之前解析,本检查只「诊断/强制」、不「重选」。
真正的体验改进是:当依赖声明 abi:musl 而当前是 glibc(真不兼容)时给出明确指引;当只是缺省工具链与
依赖偏好不一致、且存在兼容工具链时,优先自动重选而非直接报错。属增强,非本 bug 必需。
mcpp-index已把 GL window/imgui-module 两 smoke 移到 nightly,不阻塞 PR;6.1 落地后smoke_imgui_module.sh即便 pinllvm@20.1.7也能过,可回收进阻塞集。- 复核
compat.glfw的abi:glibc是否应理解为「libc 轴」语义(应是);若按 6.1 修复,语义自洽,无需改配方。
- core 单测:构造
stdlibId=libc++且targetTriple=x86_64-unknown-linux-gnu的工具链 + 一个声明abi:glibc的依赖,断言不报 ABI mismatch;...-linux-musl+abi:glibc断言报。 - e2e:本报告 §2 的三行工程,
llvm@20.1.7与gcc@16.1.0都应Finished。
病灶是 src/build/prepare.cppm:2474 那一行 stdlibId == "libc++" ? "libc++"——它用 C++ 标准库身份
冒充了 libc ABI。删掉它(§6.1),abi: 能力回归「只管 glibc/musl」的本义,clang/libc++ 用户即可正常使用
glfw 及所有 glibc C 库。复现/证据见 §2–§4。
§6.1 的一行修复能立刻止血,但它没有回答根本问题:「依赖 ↔ 工具链」的兼容性本就是多维的,而当前用
一个扁平 abi:<x> 字符串 + 临时 tcAbi 推导去表达,迟早还会在别的维度上重蹈覆辙(如 _GLIBCXX_USE_CXX11_ABI、
libstdc++↔libc++ 的真·C++ 库、cross/musl、macОS system libc、Windows msvc-stl…)。下面给一套基于现有类型
(Toolchain 已有 targetTriple/stdlibId/compiler/stdlibVersion;已有 ProviderCapabilities/capabilities_for
与包侧 runtimeCapabilities/runtimeProviders 两套能力机制)的演进方案,不推倒重来。
- 每个维度单一可信来源:libc/arch/os 由目标三元组决定;cxxstdlib/cxxabi 由编译器决定。不许跨维推导(本 bug 即跨维)。
- 未声明即「不在乎」(don't-care):依赖只约束它真正关心的维度,其余维度匹配器一律跳过。扁平串的根本毛病是「无法表达不在乎」。
- 相关性由「暴露面」决定,而非一刀切:某维度是否需要匹配,取决于依赖跨边界暴露了什么(见 9.3)。这是比 §6.1 更深一层的通用化。
给 Toolchain 配一个派生视图(字段都已存在,只是没结构化使用):
struct AbiProfile {
std::string libc; // glibc | musl | msvcrt | system(darwin) ← 来自 targetTriple
std::string cxxStdlib; // libstdc++ | libc++ | msvc-stl ← 来自 stdlibId
std::string arch; // x86_64 | aarch64 … ← 来自 targetTriple
std::string os; // linux | darwin | windows ← 来自 targetTriple
std::string cxxAbi; // itanium | msvc (+ 可选 cxx11abi 标记) ← 来自 compiler
};
AbiProfile abi_profile(const Toolchain&); // 唯一推导处,杜绝散落的 ad-hoc 串prepare.cppm 的 tcAbi 整段删除,改为查 AbiProfile。本 bug 在 L1 自然消失(libc 维独立于 cxxStdlib 维)。
包侧把单串 abi:glibc 升级为带维度的约束(或保留旧串作为 abi:libc=glibc 的语法糖,零配方改动即兼容):
-- 维度化:只约束自己关心的轴,其余 don't-care
requires_abi = { libc = "glibc" } -- glfw:纯 C 库,只认 libc
-- requires_abi = { libc="glibc", cxxStdlib="libstdc++", cxxAbi="itanium" } -- 假想的预编译 C++ 库才需要匹配器 = 逐维比对 requires_abi[d] 与 AbiProfile[d],未声明的维跳过。abi:glibc ≙ {libc=glibc} →
clang(libc=glibc)通过。这把「能不能表达不在乎」从根上解决,而不只是删一支 if。
最通用的洞见:一个依赖该约束哪些维度,取决于它跨边界暴露什么。mcpp 多数 compat 包是源码构建, 源码库用项目工具链编译 → 天然 ABI 自洽。据此可由描述符自动推断,免去作者手填、也根除「给 C 库套 cxxstdlib」这类误配:
| 依赖形态(可由描述符推断:有无源码/预编译产物、公开头是 C 还是 C++) | libc | arch/os | cxxStdlib / cxxAbi |
|---|---|---|---|
| 源码构建的 C 库(glfw、zlib) | 看运行期/host 假设(如 dlopen glibc GL 栈) | — | 不约束 |
| 源码构建的 C++ 库 | — | — | 不约束(与项目同工具链编译,构造即兼容) |
| 预编译 C 二进制 | ✔ | ✔ | 不约束 |
预编译 C++ 二进制且接口暴露 std::* |
✔ | ✔ | ✔ |
即:cxxStdlib 维仅对「预编译且在接口里跨 TU 传 std::* 的 C++ 库」才相关。glfw 是源码 C 库 → 永远不该被 cxxStdlib 卡住。
(glfw 的 abi:glibc 实质是运行期/host诉求:它 dlopen libGL.so.1 等、链系统 glibc;这天然落在 libc 维,与构建期 C++ 标准库无关。)
现状有两套并行机制:工具链侧 ProviderCapabilities(强类型)、包侧 runtimeCapabilities(扁平串),
而 ABI 检查在 prepare.cppm 里另起炉灶搓 tcAbi。统一为:工具链把自己的 ABI 维度也 advertise 成能力
(扩展 capabilities_for 产出 abi:libc=glibc、abi:cxxstdlib=libc++…),检查退化为逐维的能力集合包含——
与其它 host capability(x11.display、opengl.glx.driver)同一条通路,prepare.cppm 的 ABI 特例整段删除。
prepare.cppm:2467-2470 注释自陈:工具链在依赖图之前解析,故只能诊断、不能重选。通用做法:
- 由目标先定可在图之前确定的维(arch/os/libc——它们本就来自 target/triple,不依赖依赖图);
- 解析依赖图,收集各依赖的
requires_abi约束并集; - 有 pin:校验 pin 满足硬约束(如 musl 依赖 vs glibc 工具链——真不兼容),否则给逐维精确报错; 无 pin:从候选工具链里挑一个满足全部约束的(按偏好序),而不是默认一个再报错。 这把「resolve-then-diagnose」变成「solve-for-compatible」,正是注释里挂的 TODO 的正解;且因 libc/arch/os 在图前已知、只有细粒度约束来自图,不是完整 CSP,只是一步 refine,工程可控。
| 层 | 内容 | 收益 | 风险/工作量 |
|---|---|---|---|
| L0 | §6.1 一行:abi: 只看三元组,删 libc++ 支 |
立刻修好本 bug | 极小 |
| L1 | AbiProfile + 维度化能力(旧串作糖)+ 对称能力模型,删 tcAbi 特例 |
模型自洽,杜绝「跨维冒充」全类问题 | 中,改 prepare/provider/runtimeCapabilities 解析 |
| L2 | 由 linkage/暴露面推断相关维度 | 作者免手写、根除误配默认 | 中,需描述符元信息(已有 language/sources 可推断大部分) |
| L3 | 解析期约束求解 + 自动选工具链 | 声明式、无 pin 也能跑、精确报错 | 较大,触及工具链解析顺序 |
建议:L0 随手发(止血);L1 作为本 issue 的「正解」单独成 PR(它独立消除一整类「维度冒充」bug); L2/L3 作增强按需推进。L1 落地后,
mcpp-index可把 imgui-module/glfw 的 GL smoke 从 nightly 收回阻塞集。
本方案是这些既有文档的自然延伸,不冲突:2026-06-02-usage-requirements-architecture.md(依赖暴露=usage requirements,
正对应 9.3 的「暴露面」)、2026-05-15-clang-parity-and-toolchain-abstraction.md(工具链抽象,正对应 9.1 的
AbiProfile)、2026-06-20-package-resolution-architecture.md(解析架构,正对应 9.5 的求解化)。建议把
「ABI/能力维度模型」作为 usage-requirements 的一个子维并入,而非另起体系。