Releases: mcpp-community/mcpp
Release list
v2026.8.16.2
修复
-
装好的 msvc toolset 在
mcpp toolchain list里不出现。枚举问的是
toolchain_frontend(root / "bin", pkg),而 cl.exe 在
VC/Tools/MSVC/<ver>/bin/Host<h>/<arch>/—— 深四层。拿不到就continue,
于是每一个 msvc payload 都装得好好的、然后看不见。这个布局有三个地方需要知道:安装知道、构建知道、列表不知道 ——
一条规则被内联抄了第三份时的典型结果。三者现在共用
payload_frontend(payloadRoot, pkg, family)。2026.8.16.1带着这条(tag 打在修复之前),其余部分不受影响 ——
install / build / default / remove 都是好的,只有 list 少一行。
v2026.8.16.1
工具链
-
MSVC 不再是「唯一版本无法声明」的工具链。
gcc / llvm 由 mcpp 自己安装、按声明解析;MSVC 是体系里唯一的例外——每一个
msvc spec 都是系统 spec,manifest 写了版本也会被丢掉。后果不是不够优雅,是
同一份源码在两台机器上会被不同的编译器编译,而且不报错。xrgui#3 实测:同一轮 CI 里 mcpp 用 14.51、xmake 用 14.52,直到 14.51 触发
ICE 才暴露。导出完整 vcvars 环境无效,最后只能在 CI 里把vswhere.exe
挪开,逼 mcpp 落到VSINSTALLDIR那一步。现在由 spec 的版本轴决定来源,两条来源并存:
spec 来源 用哪个编译器 msvc@system(或裸msvc)机器自己的 Visual Studio 这台机器上装的那个 msvc@<toolset>(如msvc@14.44.35207)mcpp 安装的 xlings payload 声明的那个,每台机器都是 msvc@<toolset>与gcc@16.1.0在每个方面都同构:多版本共存、
toolchain remove msvc@<toolset>可卸载、manifest 里写了就自动安装。
payload 自带编译器、STL,并通过xim:windows-sdk依赖带上 ucrt/um 头与库,
机器上什么都不必预装。实现上,「获取」与「解析」被拆成两条正交的轴:获取与 gcc 共用一条
(xim 安装),解析与msvc@system共用一条(installation_from_tools_dir)。
所以受管 toolset 不是第二条代码路径,也就不会长出自己的 bug。 -
⚠️ 破坏性变更:msvc@19.44不再是 pin-verify。它过去表示「用系统 MSVC,并校验 banner 前缀」——而且只有
mcpp toolchain default会校验,构建路径完全忽略它。版本轴现在到处都表示
toolset。写成19.x时,mcpp 会用这台机器自己的 cl 版本说清楚,并给出两个
替代写法(msvc@system或该机器实际的 toolset 版本)。 -
VSINSTALLDIR现在优先于 vswhere 探测。vswhere 在几乎每台开发机上都能返回点什么,于是
VSINSTALLDIR事实上不可达:
一次已经导出了完整 vcvars 环境的构建,仍然用 vswhere 排第一的那个编译器。
猜测不该压过答案。 顺带给 vswhere 加了-prerelease——没有它,只装了
Insiders 的机器会被报告成「没有 MSVC」,而磁盘上明明有一个可用的 cl.exe。VS*COMNTOOLS仍排在 vswhere 之后:那是机器全局的残留(2017 的
VS150COMNTOOLS不该压过当前安装),而VSINSTALLDIR是有人为这个 shell
设的。 -
Windows SDK 不再只认两个写死的绝对路径。
顺序改为:
WindowsSdkDir(+WindowsSdkVersion,vcvars 本来就导出这两个)
→ 受管 toolset 在 mcpp 自己 store 里的xim:windows-sdkpayload
→ 原来的绝对路径(降为回退)。第二条不需要任何配置:编译器自己的路径就说明了它来自哪个 store,
SDK 是它在那里的邻居。所以 mcpp 里没有任何地方写死 SDK 版本。
接口一致性
-
cxx_runtime = "self-contained"在 MSVC 上真的生效了。过去两个旋钮只有一个管用:
linkage = "static"确实发/MT,而
cxx_runtime = "self-contained"报「未实现」——对着同一个物理开关。
两处注释也互相矛盾(flags.cppm:605说发了/MT,distribution.cppm:202
说「根本没有 /MT」)。在 MSVC ABI 上这两条不是可以二选一的旋钮:
/MT把 C 运行时和 C++ 运行时
从同一个库里链进来,它们本来就是一个开关。现在两种写法都选中它,由
msvc_wants_static_crt()统一推导——项目的 TU 与 std 模块问的是同一个函数,
不再各写各的表达式(#422 正是这样分叉的)。默认仍是
/MD:判据取的是manifest 里写下的字面值,不是解析后的
contract——后者对多数 role 默认就是 self-contained,拿它做判据会把每一个
Windows 构建都翻成/MT。MSVC 的 CRT 模型是整个项目的属性(一个项目只编一份 std 模块,cl 把
_MSVC_MT/_MSVC_MD烤进去),所以按 role 覆盖会被明确拒绝并说明原因,
而不是在 ucrt 头文件里炸出 C5050/C2375。
测试
- msvc 的发现逻辑第一次可以在 Windows 之外测试:
installation_at()接受目录
而不是去探测机器,find_windows_sdk()接受 root 列表。6 个新单测在 Linux CI
上跑真实的 fixture 目录树,包括「两个 toolset 都在,要老的那个」这条——
「取最新」的实现会在这里失败。 - 新增 e2e
239_msvc_managed_toolset.sh。它的每一条断言都写成
系统编译器来应答就会失败:cl.exe 必须在 mcpp 的 store 里、toolset 目录
必须是 spec 声明的那个、同一台机器上换回msvc@system必须仍然解析到系统
的 cl(两条来源互不污染)。
v2026.8.15.3
(no CHANGELOG entry found for 2026.8.15.3)
v2026.8.15.2
(no CHANGELOG entry found for 2026.8.15.2)
v2026.8.15.1
(no CHANGELOG entry found for 2026.8.15.1)
v2026.8.11.3
修复
-
⚠️ 回归:产物加载的库不是它链接的那一份 ——$ORIGIN被 SubOS 库视图遮蔽。2026.8.11.2(PR #413)首次把 SubOS 库视图(farm)写进产物的DT_RPATH,但它
落在$ORIGIN之前。于是 imgui/GLFW 应用链接的是 mcpp 从compat.x11源码
构建、部署到产物目录的libX11.so,运行期加载的却是 farm 里 xlings 装的
xim:libX11—— 链接期用 A,运行期加载 B,程序在 main 之前就死:undefined symbol: _ZNKSt13runtime_error4whatEv真因不是「放错了位置」,而是一条链接命令行的顺序由两个互不知情的生产者用
+=决定:flags.cppm把 farm 拼进全局 ldflags(并注释「so it is LAST」),
plan.cppm把$ORIGIN拼进 per-unit,而每条链接规则渲染的是
$ldflags $unit_ldflags。三处各自都对,合起来是错的。新增
mcpp.build.link_line:把 per-unit 尾部声明成具名槽位,相对顺序写在
类型里、由单测钉死。新增一个生产者必须先选一个槽 —— 而"选"正是"在产物自己的
目录之前还是之后"这个问题被提出来的地方。 -
⚠️ 共享库不再把自己的 C++ 运行时导出给别人(ELF)。SharedLibrary此前与可执行文件共用Distributable角色,于是拿到同一份
self-contained 契约:-static-libstdc++。在 ELF 上这不是"私有一份" —— 只有一个
全局符号命名空间,共享对象会导出它定义的每一个全局符号。一个纯 C 的 compat 包
因此导出了 777 个 GLOBAL 标准库符号(libXau.so:39KB 的 Xau + 9.5MB 的 libstdc++)。可执行文件链接时
-lX11排在驱动的-lstdc++之前,ld 就用它满足了
std::runtime_error::what(),归档成员从不拉入 —— 可执行文件的
-static-libstdc++变成空操作,它的 C++ 运行时事实上是那个.so。上一条的
库替换之所以致命,根源在这里。共享库默认契约改为按目标格式分档:ELF
toolchain-coupled,
Mach-O / PE 维持self-contained(两者都没有这个危害 —— Mach-O 的机制本就是
-load_hidden,PE 没有全局命名空间)。显式cxx_runtime = { shared = "…" }
仍可选回自包含,此时自动补-Wl,--exclude-libs,让内嵌的运行时留在动态符号表之外。实测(helloegui,imgui + GLFW + X11):
libXau.so9.5MB → 39KB,
libX11.so导出 std 符号 2931 → 0,GUI 正常启动。
内部
-
dist::default_contract从没有任何生产调用方变成唯一真源:角色→契约的策略
此前在flags.cppm被第二次推导,而这正是distribution.cppm开篇声讨的那类债
(「used to be derived independently in five places」)换个位置复发。 -
e2e 219 的断言由「farm 是最后一个绝对路径条目」收紧为「字面最后一项」,
并补一条行为不变量(LD_DEBUG=libs实测同名 SONAME 解析到$ORIGIN)。
旧断言把$ORIGIN过滤掉了,对坏顺序与好顺序给出同一个结论 —— 它在一个 farm
并非最后的二进制上报告「farm is last」。测试工程也从int main()换成消费依赖
共享库,否则它连$ORIGIN都不产生,整条断言链是空转的。
v2026.8.11.2
修复
-
⚠️ 回归:SubOS 没有自我描述时,mcpp build/mcpp test直接失败
(xlings#543)。Windows 上 xlings 不写
subos_info块,而 mcpp 把「缺声明」当成了错误,于是
每一次构建都停在一条讲 GL 驱动的消息上 —— 在一台没有 ELF、没有PT_INTERP、
没有私有 libc 的机器上。2026.8.10.2引入(PR #400),2026.8.8.4正常。判据改为:矛盾报错,缺席降级。 点名的 SubOS 不存在仍是硬错误(该请求无法被
满足);SubOS 存在但没描述自己则记declared=false+ 一条必被打印的 note,
runtime 规则报inconclusive而不是给出判决,构建继续。同一位置还有第二颗雷:schema 检查是
!=,而它的读取器明写着「更高的 schema
照读」。xlings 写出 schema 2 的那天,全平台所有构建会同时停摆。已改为上限语义 ——
发布数据不得使读它的程序失效。 -
图形/系统库链得上却跑不起来:运行期搜索路径补上 SubOS 库视图。
mcpp 在编译与链接两条线上都发
--sysroot=<subos>,所以-lGL零 flag 就解析得到;
但运行期的搜索路径是另一套独立推导(只有工具链载荷目录)。结果是
mcpp buildrc=0、./bin/app报libGL.so.1: cannot open shared object file。$ readelf -d bin/app | grep RPATH before: [<store>/glibc/2.39/lib64 : <store>/gcc/16.1.0/lib64] after: [<store>/glibc/2.39/lib64 : <store>/gcc/16.1.0/lib64 : <subos>/lib]新增
mcpp.platform.runtime_search契约模块:一条搜索目录有来源
(payload/package/subos_farm/host_default)、次序与是否机器本地。
次序 = 不可变性递减,farm 在最后 —— 载荷目录装一次不再动,<subos>/lib每次
xlings install都重写;载荷在前,libc/libstdc++永远从被 pin 的载荷解析。
交叉目标与非 ELF 格式不发 farm。 -
validation: pass曾对一个跑不起来的产物成立。闭包解析在搜完 rpath 后回落到宿主默认目录,而宿主通常自带
libGL.so.1⇒
模型认为解析到了。但产物跑在私有加载器下,它的默认路径里没有宿主目录。
现在宿主默认目录只在非 hermetic binding 下参与;hermetic 产物上一个谁都提供
不了的DT_NEEDED是可证的失败(新判决unresolvable),会让构建变红并指名。实测佐证(
LD_DEBUG=libs):私有加载器的内建默认路径是 glibc 载荷自己的构建期
前缀(…/fromsource-x-glibc/2.39/lib),这台机器上根本不存在;/usr/lib从不
被查。因此 e2e206里那条「安全的宿主 DSO 对照」此前报pass也是假绿 —— 它
刻意不运行产物,而产物其实 127。已改为断言inconclusive并说明原因。[build] allow_host_libs同时退出两个阶段。 它本就关掉链接期 hermeticity 检查;
既然用户已声明「我有意伸到沙箱外」,mcpp 就不能再断言产物起不来(他们可能用
LD_LIBRARY_PATH跑,或装在私有加载器会看的地方)。⇒ 该档下未解析的NEEDED
报inconclusive并指名,而不是变红。一条声明,一个含义。另两处精度修正(CI 抓到的):
unresolved此前混装三种东西 ——「找不到的 SONAME」
「读不了的对象」「512 上限」。只有第一种可证,故拆出unresolvedSonames;
且产物的格式由产物决定,不由 binding 决定 —— Linux→Windows 交叉构建拿的是宿主
binding(hermetic),产物却是 PE,「不是 ELF」落进unresolved后被判成「缺库」,
让crosswin.exe构建失败。
变更
- xlings 强相关模块归入
src/platform/xlings/:mcpp.platform.xlings、
mcpp.platform.xlings.subos_info、mcpp.platform.xlings.runtime_selection
(命名空间不变)。RuntimeBinding与runtime_search留在src/platform/——
它们是 provider 中立的契约类型,不是 xlings 专属。 resolution.json的runtime.search增加closure数组(路径 + origin +
machine_local,保序);mcpp why runtime按加载器次序打印它。- mcpp 为自己启动的每个进程声明
XLINGS_SUBOS_LD_PATHS=0—— xlings 链接器包装器
路径注入的退出声明(xlings#540)。
今天是无操作;它落地后 mcpp 的 DT_RPATH 仍只含 mcpp 自己决定的内容。
不读$XLINGS_SUBOS_LIB:实测它指向当前 shell 的 subos,而 mcpp 有自己的
registry home,两者通常由不同物理 glibc 载荷支撑。 - 内带 xlings pin →
2026.8.11.2。
v2026.8.11.1
新增
-
[build] module_extensions—— 哪些扩展名是模块接口,由工程声明。[build] module_extensions = [".ixx", ".ccm"]
追加到内置的
.cppm。声明一个扩展名会同时做三件事:默认 sources glob 跟着
变宽(文件才能被找到)、这些单元走模块规则(产 BMI、.o无条件进链接)、
新鲜度快路径扫描它们(加import会让构建图作废)。一个键而不是三处配置。任何扩展名都接受,唯独拒绝已代表其他角色的(
.cpp.c.h.S…)——
manifest 错误而非警告,因为宣称.c是模块接口会把 C 文件送进 C++ 模块规则,
最终失败在一个既不提文件也不提这个键的地方。⚠️ .ccm/.cxxm/.ixx不进内置默认。进了的话默认 glob 会跟着变宽,
于是src/下躺着 vendored MSVC-only.ixx的已发布包会在一次 mcpp 升级后
突然开始编译它 —— 而包作者改不了已经发出去的 tarball。 -
[build] build_program_timeout——build.mcpp的运行上限可配置。优先级
MCPP_BUILD_PROGRAM_TIMEOUT> 该包自己的 manifest > 内置 600s,
与macos_deployment_target同构。超时报错点名要改的那份mcpp.toml——
依赖超时时改自己的那份不会有任何效果,这正是 #410
从外面看到的样子。不写这个键与写0不是一回事:不写=用默认上限,0=不设上限。 -
mcpp self doctor报告构建策略。 生效的模块接口扩展名表、生效的超时值
及其来源、以及本平台的 deadline 是否真的强制执行。
另外module_extensions里零命中的条目会告警 —— 否则打字错误(.ixxx)与
「这个工程还没有」无法区分。
修复
-
超时上限在 Windows 上从来就是空操作。
capture_exec_deadline只在 POSIX
生效,其余平台直接回落到无界启动器 —— 于是mcpp test --timeout、
--build-timeout、以及这个新键在 Windows 上设了等于没设。现在两侧各有实现:
Windows 把子进程放进 Job 对象并在到期时关闭它,杀掉的是整棵进程树而不只是
直接子进程(否则一个还攥着捕获管道的孙进程会让杀掉之后的读取一直挂住)。 -
.mm(Objective-C++)的对象编了但永远不进链接 ——is_implementation_source
的清单漏了它。 -
stage 一个含汇编的依赖会静默丢掉那些源文件 —— 兜底 glob 漏了全部三种汇编扩展名。
架构
-
「扩展名 → 角色」此前在 9 个文件 20 处推导,分成 8 份互不一致的清单
(三份「什么算实现单元」、四份「什么算源文件」)。
#272 修了链接侧,却漏了
pick_rule—— 边上声明了 BMI 产物(那行读providesModule),命令行却丢了
-fmodule-output=。收敛的形状不是「大家都调同一个函数」,而是分类只发生一次(文件进图时),
之后当数据传递(SourceUnit::kind→CompileUnit::kind)。扫描器手里本来就有
所属包的 manifest,所以这一步没有新增任何管道。 -
mcpp 现在每次都显式告诉编译器某个单元是模块接口(
-x c++/-x c++-module/
/interface /TP),而不是去维护「哪个驱动认哪个后缀」。实测(GCC 16.1 / Clang 22.1):
Clang 根本不认.ixx—— 把它当链接输入、警告、退出码 0 且不产 BMI;
而显式旗标在已识别后缀上幂等(Clang 的.cppmBMI 逐字节相同)。
一张会过期、错了还静默的表不该存在。 -
src/platform/拆成unix/ windows/ linux/ macos/;mcpp.platform.process
对有界运行单点if constexpr分派,取代此前散落的平台分支。
兼容性
- 未配置时构建图零差分:同样的文件被编、同样的 BMI、同样的对象进链接、
同样的指纹目录。 module_extensions进指纹(它改图的形态);build_program_timeout不进
(它不改任何一条边 —— 进了会让「抬高超时」重建全世界)。⚠️ 旧版 mcpp 遇到module_extensions会警告+忽略,然后把那些文件当普通翻译单元
编译 —— 错误的构建而不是干净的失败。发布用了这个键的包必须声明 mcpp 版本下限
(见docs/10-publishing-a-library.md)。
v2026.8.10.3
修复
-
宿主能力清单从「解析后的图」取,不再从根 manifest 取。
2026.8.10.2引入的
「自带 libc 的档拒绝宿主能力」只在根工程自己声明能力时生效 —— 而几乎没有应用
会自己声明capability:opengl.glx.driver,它依赖某个声明了的包(glfw / SDL 封装 /
GL runtime),resolver 会给每条需求盖上请求者身份。读根 manifest 回答的是
「作者写没写」(几乎总是没写),而该问的是「解析出来的图需不需要」。实测:一个真实 imgui 工程的
mcpp why runtime列着
capability:opengl.glx.driver [run] <- compat.glfw@3.4 (required),
而mcpp pack --mode self-contained照打不误。修复后它正确拒绝,并列出
abi:glibc, opengl.glx.driver, x11.display三条;--mode vendored正常打包
并把三条写进HOST-REQUIREMENTS。同一处也修好了
mcpp pack的HOST-REQUIREMENTS:此前对真实工程是空的
(空文件会被读成「什么都不需要」,而它现在根本不写空文件)。为什么原来的测试没抓到:它的 fixture 在根工程声明了能力 —— 恰恰是真实工程
唯一不具备的形态。216已改成依赖声明、消费方什么都不声明,并加了一条
前置断言:那条需求必须先出现在mcpp why runtime里,否则测试等于什么都没验。
v2026.8.10.2
图形栈闭合与分发档位。完整设计与实施计划见
.agents/docs/2026-08-10-graphics-closure-and-distribution-tiers-design.md 与
.agents/docs/2026-08-10-graphics-closure-implementation-plan.md。
修复
-
依赖 BMI 缓存命中时,被恢复的包传递依赖的
std没进构建图(#405)。
消费方自己不import std时,gcm.cache/std.gcm的 stage 边没有任何消费者,
ninja 从不执行它,于是被恢复的 BMI 报No such file or directory/
Bad import dependency—— 一条完全不指向缓存的错误。缓存 miss 时依赖在本地编译,
这个动作把 std 边带进图,所以第一个构建它的人永远是好的、之后每个人都坏,
看起来像升级回归。修法把 std BMI 放进_mcpp_staged_cache聚合 ——
那个聚合本来就是为「stage 边丢掉编译边携带的次序」而存在的。
不动 cache key、不改 entry schema,现有缓存全部继续有效。 -
mcpp build会重放mcpp test留下的构建图(#407)。 三种模式写同一个
build.ninja(指纹不含 dev-deps 与 test targets),而快路径只比源码 mtime、
从不校验这张图是哪一种。于是build → test → build链测试、不链 target、
还报Finished;改坏一个测试文件会让普通mcpp build失败,而src/一个字没动。
修法是让build.ninja自己声明形态(# mcpp:graph=normal|test),快路径校验它即将
重放的那张图。这是读取侧不变式:#387 那半边的写入侧修补被同时删掉了 ——
写入侧修补要求每一个未来的图重写者都记得调它,而这正是mcpp test那半边
在--configure-only修好之后仍然坏着的原因。
新增
- 加载器标签契约(
mcpp.build.loader_contract)。 可执行文件必须 DT_RPATH,
共享库保持 DT_RUNPATH。DT_RUNPATH 只对携带它的对象自己发起的dlopen生效;
图形程序到驱动要经三到四层dlopen,而这些 dlopen 都不是它发起的,
是libGLX.so.0/libEGL.so.1代发的 —— 所以标签(不是路径)决定它能不能拿到 GPU。
同一路径只翻标签,egl / gles2 / egl-surfaceless 从 llvmpipe 变 NVIDIA。
反过来在库上强制 RPATH 有害(传递性会打断eglInitialize),所以这是一分为二、
不是一起翻。链接期与mcpp pack的 patchelf 期读同一条契约。 - rule E:标签校验落在产物上,并写进
resolution.json的loader_tags。
warn-first,与既有闭包规则同一推进节奏。记录而不只是告警 ——
一个只在沉默中通过的检查,和一个根本没跑的检查,输出完全相同。 mcpp pack:产物里不再残留构建机路径。 此前只有主二进制被重写,
bundle 进来的每个.so都保留着链接时的 RUNPATH,而在这个生态里那是一串指向
构建机 xlings store 的绝对路径。「依赖 xlings 生态」是设计选择,
「依赖这一台机器的这一份 store」是缺陷,而且它在构建它的机器上跑得好好的。
另外patchelf --set-rpath默认写 DT_RUNPATH —— 对可执行文件而言是同一个缺陷晚一层。HOST-REQUIREMENTS:自包含也有底。 驱动类库只能来自目标机器
(与内核模块锁步,且专有栈禁止再分发),所以打包这类程序的诚实产出不是一个悄悄
少了它的 bundle,而是 bundle 加上一份声明。带discovery一列,因为几种发现
机制互不通用 —— 一种是烙进派发库的搜索路径,另一种是 JSON 里的绝对路径,
「把目录搬过去」只满足其中一个。- 自带 libc 的档遇到宿主能力硬拒。
--mode self-contained与--mode static
在存在 run 期能力需求时于 plan 期失败,并指出改用--mode vendored。
两者坏在同一件事上:那个.so带着对宿主 libc 的要求进来,而进程里没有那份
(#392 / #401 的两个方向)。此前两档都链得过去、然后运行时崩或静默降级。 [[runtime.requirements]] discovery。 声明式,mcpp 绝不推断 ——
从能力名推断机制就是把 provider 专属知识写进 mcpp(test_runtime_contract正是
为此设的门),而且它是 provider 的属性、会脱离 mcpp 变化。未声明报unknown。- 声明的 runtime artifact 带身份判决。
resolution.json每个 artifact 增加
identity:ok/mismatch/missing/unverified。这正是 mcpp 已经
对私有 libc 执行的规则(glibc@2.44只解析那一个载荷,陈旧即错误)的推广,
纯路径事实、跟随符号链接、不需要认识 GL。mcpp why runtime的
artifacts: (none declared)改为(not declared by the environment — nothing to verify):一个有名无物的 provider 是未验证,不是验证通过。
变更
- 内带 xlings 升到
2026.8.10.4。 其中2026.8.9.2的 semver 重写让四段版本可以
参与范围比较 ——xim:libglvnd@>=1.7.0.1此前解析成package not found,
这正是 mcpp-index 全量跑里 8 个图形成员全红的原因(不是数据缺陷)。