本文件追踪
mcpp-community/mcpp公开仓的版本演进。 格式参考 Keep a Changelog。
机器可读输出有契约了。设计与实测见
.agents/docs/2026-08-08-machine-readable-output-protocol-design.md,用户文档见
docs/11-machine-output.md。
-
--format json:统一的输出信封(mcpp.wire)。 覆盖self env、xpkg parse、cache list,含schemaVersion/kind/kindVersion/effects/data/diagnostics。信封与 kind 的版本分开 —— 单一全局版本号会让给mcpp.env加一个 字段推高客户端为mcpp.xpkg读到的版本。 -
mcpp --protocol-version。 回本 build 支持的信封版本、各 kind 版本,以及 命令 → 效应的静态表。它是优化而非地基:在它出现之前的每个 mcpp 上,它自己就是 未知选项,失败与成功同走 stdout —— 所以客户端的判据只能是正向识别(stdout 解析 得出schemaVersion+kind)。这条写进了文档的第一节。 -
效应集合,不是
destructive布尔。 实测:全新MCPP_HOME上xpkg parse与cache list什么都不建,self env建 6 项。布尔分不开「mcpp 给自己做初始化」和 「执行工作区里的代码」,而 IDE 的 untrusted 门只在乎后者。 -
mcpp self env --format json走独立只读路径。 不调load_or_init:客户端问 「东西在哪」不该成为把东西放到那儿的原因。全新 home 上返回完整路径 +initialized: false,创建项数 0(人类路径不变,仍会初始化)。
-
CDB 的
arguments带着 shell 引号。 消费者(clangd)逐字 exec 它,不经 shell, 于是带引号的 token 不是 flag 而是不存在的文件名。Windows 上每个带路径的 flag 都中招 (shell_quote_arg的触发集含反斜杠);带空格的路径上更糟 —— token 在引号内部 被切断,一个参数变成两个,其中一个带着永不闭合的开引号。 -
usage 错误不再进 stdout。 未知选项过去打在 stdout、rc=1、stderr 为空,于是
mcpp cache list --format json | jq拿到的是人类文本。现在未知选项与不支持的值 统一 stderr + rc=2,stdout 一字不写。
--json永久保留它的 payload,不打 deprecation 警告。 拼写兼容不等于 payload 兼容:cache list --json顶层是{root, entries}且本仓库 e2e 已断言。两种拼写由 同一来源产出,不会漂移。pack --format tar|dir是产物形态而非输出格式,声明为本协议的例外。
-
subos 的
prepend把调用方的值整个丢掉了(#382 顺带发现)。解析出的键值对是替换子进程里的变量的(作为 extraEnv 进去),而
prepend只发出 声明值 —— 对 PATH 形状的变量,丢掉的是用户的整条搜索路径。现在它接在调用方的值 前面,且对已存在元素幂等(嵌套运行不会让列表增长)。空值不算「用户设过」。同一处的
set保持不变。set与「默认值」是两种意图:有些变量 subos 必须说了 算 —— 指向它自己 loader 配置的那类,用户 shell 里一个陈旧值就能把环境弄坏。而且envs的 op 词汇表是 xlings 的契约,消费方悄悄给一个 op 加第二种含义,会让同一个 subos 因为「由谁启动」而行为不同。#382 想要的「用户可覆盖的默认值」应由 xlings 新增 一个 op,mcpp 认它;因为 mcpp 会丢弃未知 op,那个 op 必须两侧同时到位。
- xlings pin 提升到 2026.8.8.1。
编译器交付的是能力,不是配置。设计与实测证据见
.agents/docs/2026-08-08-payload-version-and-contract-drift-design.md。
-
产物加载哪个 glibc,现在有权威,不再靠目录顺序。
payload-first 的构建要链接到某个具体 glibc。旧规则是问目录"那个 glibc"、 取
readdir的第一项——只装了一个时它永远对,所以从没有东西逼它对。一条带xim:glibc@>=2.38的依赖就足以装进第二个:编译侧取了 2.44,而产物的 interpreter(装机时冻结在 gcc specs 里)仍是 2.39,于是二进制引用GLIBC_2.42符号却跑在没有它的运行时上,报错还落在与那条依赖无关的包上。现在由 subos 自述的 runtime 作答(
[xlings] subos可指定),没有答案就拒绝 走 payload-first,而不是挑一个。该绑定计入工具链指纹(11 字段),两次只在 runtime 上不同的构建不再共用缓存。旧 subos 回落到工具链自身烙入的值。 -
不再改写 GCC 的
specs,产物也不再带别人机器的死路径。旧的 specs 重写用单路径 needle 配双路径 replacement,每个跑过它的 home 都漏下 一条:一台开发机产出的每个 gcc 产物里都有 68 条陈旧
RUNPATH,全指向已删除 的mktemp目录。改为-specs=一份逐构建生成的干净文件(-dumpspecs取内建*link:,去掉 loader/rpath),loader 与 rpath 由 mcpp 显式写在链接行上。逐构建、 不需写权限,继承来的只读 payload 也能用。e2e201_gcc_no_specs_pollution.sh断言的是产物而非 specs 文件。 -
落后于 pin 的 vendored xlings 会被替换——但只在替换品确实更新时。
acquire_xlings_binary过去见到文件存在就返回,于是一个 home 会永远留着它第一次 获取的 xlings(实测 2026.8.2.1 对 pin 2026.8.6.3)。subos_info是 2026.8.5.1 才有的,所以那台机器上 subos 的自述一直被一个太旧的客户端丢弃。修复的第一版直接 删了重取,结果把 2026.8.2.1 换成了系统的 0.4.51——更旧,且同样没有那个特性;现在 先给替换品定价再动手。 -
--sysroot的判据从"存在"改为"归属"。 gcc 把--sysroot=<...>/.xlings/subos/default当字符串烙进去,而一台机器上有很多同名 目录,所以那条烙入的路径经常存在、却属于另一个 checkout(实测:mcpp 里的构建解析 到了无关仓库下的 sysroot)。
mcpp self doctor新增两条检查:vendored xlings 落后于 pin;sysroot 既不属于本 mcpp home 也不属于当前项目。- xlings pin 提升到 2026.8.7.1。
xlings 作为运行时底座:subos 环境到达程序,以及 self-contained 的
/proc/self/exe 陷阱(#352、#375)。设计见
.agents/docs/2026-08-07-xlings-as-runtime-substrate-design.md。
- subos 声明的环境变量现在真的到达被运行的程序(#352)。 之前 mcpp 读的是自己
想象出来的
subos_info.envs线格式(数组),而 xlings 写的是以 binding 为键的 对象,于是整条修复是空转的。现在按 xlings 实际写出的格式解析,并用逐字取自真实 xlings 输出的 fixture 钉住。 - 升级后的 mcpp 会重放升级前的缓存,导致 subos 环境丢失。 快路径命中时不带
subos 环境;该缺陷是靠一条"必须走快路径"的自证断言抓到的,而那条断言随即又抓到
第二个:缓存行写在
profile=之前却在cacheMode=之后解析。 - self-contained 打包的
/proc/self/exe陷阱(#375)。 新增MCPP_BUNDLE_DIR契约。
两处「模型比生态少一层」。设计与实测证据见 .agents/docs/2026-08-07-windows-resources-and-version-identity-design.md。
-
[resources]:exe 图标与版本信息现在是mcpp.toml里的一行(#365)。[resources] icon = "assets/app.ico"
FILEVERSION/ProductName/FileDescription/CompanyName/LegalCopyright全部从[package]取默认值,资源脚本由 mcpp 生成。自写.rc写files = [...],mcpp 编译并跟踪它。只有 PE 目标消费这一节;在 Linux/macOS 上它「不适用」——不是降级、不是带警告地跳过:没有消费者,构建逐字节不变,也不说话。所以不需要(也不能)加
cfg(windows)谓词。节名不叫[windows]是因为「图标」作为概念不是 Windows 专有的,将来 macOS.icns扩同一节而不是把这条轴按 OS 切三份。声明了却不存在的文件是硬错误,这是对 issue 第 3 条请求的有意偏离:mcpp 里每个「声明过的输入」都是这个规则(
main = "…"必须匹配恰好一个文件、nasm 缺失是硬错误),而「缺失就跳过」会把这个 feature 要消灭的失效模式写成规定行为——一个没有图标、没有版本信息、且什么都没说的正式二进制。不要图标已经可表达:把那一行删掉。这条校验在每个目标上都跑,「不适用」只停在编译那一步。路径存不存在是关于工作树的事实、不是关于目标的事实;按 PE 设门会让 Linux/macOS 的构建与 CI 完全看不见
icon里的拼写错误,只有 Windows job 变红——正是这条硬错误要消灭的「太晚才知道」。 -
role = "object":build.mcpp 的 action 现在能把产物接到链接输入上。 角色表原本三格接在「编译输入 / 无 / 链接输出」上,缺的正是「链接输入」——一个构建图显然有的接线点。后果不是理论上的:预编译对象只能塞进[build].ldflags,而那是链接命令里的一串字符、不是图里的文件,于是改了图标得到ninja: no work to do。可选
.target("name")指定接哪条边;省略是推荐写法,它接到本次构建产出的每个镜像——可执行、动态库与测试二进制。测试二进制在默认集合里不是顺手加的:它链接的是同一份库代码,排除掉会让mcpp build通过而mcpp test在这个 action 本来要提供的那个符号上报undefined symbol;而改成显式点名也不成立——测试链接单元是从tests/*.cpp发现出来的,名字不在mcpp.toml里,写了它的 build.mcpp 在普通mcpp build下会直接构建失败。每一个匹配不到链接单元的名字都是错误,包括写在一个匹配得上的名字旁边的那个(拼错的真实形状)。本次构建里没有任何镜像可接时报 degradation——这条边只能经由链接被达成,没有链接就意味着命令一次都不跑。
-
解析出的版本现在是索引里的字面键,不是重新渲染的数字(#363)。
resolve_semver一直把索引的字面版本键读到手里,然后return parsed[i].str()—— 从解析出的数字重造一个地址。渲染器复现不了的东西就变成了不存在的地址:上游键 旧行为 1.92.8-docking截断成 1.92.8,与非 docking 那个塌成同一个可比较版本(两个不同 tarball)25.0.4.7.1(jdk-corretto,五段)截断成 25.0.4.7—— 索引里没有这个键pre-v0.0.5(khistory,唯一的发布)静默跳过,然后报「no valid versions in index」——把责任推给一个发布得好好的包 现在字面键与序一起传递,
version_req只负责排序。连带修的:- 预发布按 SemVer 排序,且范围按 npm/Cargo 规则看不见预发布,除非约束自己在同一数值元组上带了预发布。
^1.92.8因此确定性地选1.92.8,不再在两个 tarball 之间由一个看不见差别的序做取舍。同一条规则顺带修掉^1.2.3会漏进2.0.0-alpha。 - 数值段不再截断在第四段。真实索引里
jdk-corretto发五段键;截断让25.0.4.7.1与25.0.4.7.2比较相等。 - 别名条目(
{ ref = "…" })不再是范围候选。jdk-temurin的["25.0.4"] = { ref = "25.0.4+7" }曾与它自己的目标构成一次「平局」。精确寻址不变。 - 不可排序的键(
b10069、latest、pre-v0.0.5)成为一等公民的一类:只参与精确匹配,范围约束下报指名的错误并给出可粘贴的 pin 行。 - 真平局硬错。只差 build metadata 的两个键(
1.0.0+a/1.0.0+b)是两个 tarball、两个 sha256,序说不出该要哪个;旧行为按描述符里的行序取第一个,意味着索引的一次排版调整会改变构建出来的东西。
- 预发布按 SemVer 排序,且范围按 npm/Cargo 规则看不见预发布,除非约束自己在同一数值元组上带了预发布。
-
mcpp.lock 记录解析结果,并覆盖传递依赖。 它记的一直是约束本身(
version = "^1.92.8"),而一个记录范围的 lock 不锁定任何东西;Compiling compat.imgui v^1.92.8这行也一样。两者读的都是m->dependencies(未解析的输入、且只有直接依赖),而解析结果ResolvedRecord早就覆盖整张图——修法是把两个消费者都指过去,而不是补第三处回写。lock 头部现在自己声明它还不 pin 后续构建(index 依赖仍每次从约束重新解析)。一个记着真实版本却不生效的文件,比一个明显记着范围的文件更容易被误当权威。
dev-dependencies 不进 lock。 解析结果覆盖整张图,而
mcpp test解析 dev-deps、mcpp build不解析——照单全收会让一个进 VCS 的文件取决于「上一条命令是什么」,build/test/build 写出三个不同的文件。判据:lock 是 manifest 的函数,不是命令的函数。头部注释也写了这一条。 -
[resources]在 MSVC 下找不到rc.exe。 工具链 PATH 覆盖按find_first_of(";:")切分,而 Windows 路径的盘符冒号就在下标 1——C:\Windows Kits\…被切成C加一段「当前盘相对路径」,同盘时侥幸命中、跨盘必然找不到。而这条 PATH 遍历正是 msvc 下的主路径(rc.exe属于 Windows SDK,从不在cl.exe旁边)。两个调用点各自推导同一条规则、且已经彼此不一致,现在收敛成一个split_env_list。 -
.rc现在能穿过工程级 fast path。sources_newer_than只扫src/**/*的 C++ 扩展名,一次只改资源脚本的构建因此报Finished dev in 0.15s—— 而.rc的 implicit input 集合来自扫描它、扫描发生在 prepare,所以往脚本里新加一行#include "ids.h"那个头文件永远不会被跟踪。与build.mcpp、glob 输入(#359)是同一类:mtime 扫描看不见,但改了它图就该长得不一样。只扫files——icon与extra-inputs已经是 ninja 的 implicit input。
- 版本号 2026.8.6.3 → 2026.8.7.1。
- 行为变化(生态可见):
cc-connect这类「稳定版 + 预发布版」并存的包,^1.3从1.3.3-beta.1改为解析到1.3.2;jdk-corretto/jdk-temurin这类带别名的包,范围解析改为选中真条目(25.0.4.7.1而非别名25.0.4),store 目录名随之变化。 - 诊断口径:
resources/versioninfo(版本资源 Windows 读不到)与resources/no-image(声明了却没有任何镜像可嵌)由 warning 改为 degradation —— 它们的 impact 正是本批要消灭的静默失效,--strict必须看得见;role = "object"无消费者新增action/no-target,同口径。 - README 的包索引链接由仓库地址改为 https://mcpplibs.github.io/mcpp-index/。
命令长度这一族缺陷的第七次,这次不再补洞。架构分析见 .agents/docs/2026-08-06-command-length-architecture.md。
-
windows 上的 clang 链接改用 lld,消掉最后一个随对象数增长的上限。 2026.8.5.3 把 mcpp自己写的响应文件改成按行分隔,但 clang 作为 driver 时会再生成一个响应文件转发给链接器,而那个是单行的 —— 我们改不到它。mcpp-index 的
opencv-module因此在 2026.8.5.3 上仍然LNK1170,而且是在编译完 356 秒之后。为什么现在才出现:#344 给每个依赖的对象加了一层包目录(缓存正确性的要求),路径因此变长——同一条边在 linux 上从 56 840 涨到 161 687 字节。而 mcpp-index 的 CI 一直 pin 在 2026.8.3.3,正好是那之前一版,所以它的 windows 腿从没用长路径链接过。把 pin 抬上来才第一次撞到。
为什么这不是 workaround:lld 用 LLVM 的 tokenizer 解析响应文件,没有单行上限 —— 消掉的是一整类,不是把某个数字调大;路径也缩不短(那层包目录正是 #344 需要的,其余是源码树自身结构)。而且 linux 与 macOS 早就在用 lld,windows 是唯一还在用系统链接器、也是唯一有单行上限的平台 —— 这是消除平台不一致。原生 cl.exe 保持 link.exe:那条路径上响应文件是我们自己写的。
-
命令长度预算现在是一张表,不是七处注释(
src/build/cmdlimits.cppm)。 前七次每次都在注释里写下「构建系统不该有靠崩溃才发现的规模上限」,然后换个地方再犯。根因是:命令构造层对「这条命令要穿过哪些通道、每个通道上限多少」一无所知,而这份知识只存在于注释与 CHANGELOG 里 —— 是「同一决策在 N 处推导」的镜像:一个关键约束在零处被表达。表里除了字节数,还记症状:这一族最贵的从来不是修,而是认出。
posix_spawn: Argument list too long、LNK1170、cmd.exe 的裸127三种表现毫无共同点,下次遇到第四种时表能直接把人指过来。新增执行通道必须回答「你的上限是多少」,与directives::kTable里「Scope 是必填字段」同一个手法。 -
超限改为在计划期拦下并指名道姓。 以前是 ninja 或 link.exe 在构建末尾崩,而报错的是别人的程序,信息里没有 mcpp 的上下文(哪条边、哪个包、多少对象)。现在生成完
build.ninja就统一扫描,超限时报出边名、通道、实测字节、上限、解法、文档路径,且发生在还没编译任何东西的时候。两处判断在实施中被纠正:校验点选在「manifest 生成后统一扫描」而不是「逐个 emit site 插桩」——后者在新增一种边时没人会想起来加,而那正是前七次的漏法;
phony必须排除,否则会误报到 #274 的修复本身(它为解决 argv 超限,正是把几千个目标收进一条 phony 聚合边,而 phony 根本没有 command)。
- 内带 xlings 升到 2026.8.5.2(
.github/下 16 处 pin 由check_version_pins.sh机器校验同步)。
-
链接响应文件按行分隔(
$in_newline)。 一条链接边和操作系统之间有两道上限,而「改用响应文件」只拆掉了第一道:-
命令行:Windows
CreateProcess32 KiB;POSIX 下 ninja 用sh -c "<整条命令>",整条命令是一个 argv 项,撞的是MAX_ARG_STRLEN128 KiB。这道在 #344 / PR#345 已经拆掉。 -
响应文件的单行长度:
link.exe上限 128 KiB。所有对象写在一行,于是fatal error LNK1170: line in command file contains 135135 or more charactersmcpp-index 的
opencv-module与opencv-module-dnn在 windows 上正是死在这里 —— 链接前的 795s / 1166s 编译全部白做。
改成
rspfile_content = $in_newline之后,没有任何上限再随对象数增长。全平台同一条规则形状:GNU 与 LLVM 的响应文件解析把任何空白(含换行)当分隔符,而 link.exe / lib.exe 要的正是这种写法。同一族的第四次(#274 / #247 / #344 / 本条)。前三次的教训写的是「命令有多长不该有人放在心上」;这次补上的是它的孪生兄弟 —— 一行有多长同样不该。e2e 190 两面都钉:既断言生成的规则,也断言 ninja 真正写出来的文件(
-d keeprsp),因为只断言前者的话,ninja 哪天改了$in_newline的展开方式测试仍会绿。 -
修复 host-module = true(规则包)的两个缺陷。二者都是 2026.8.5.1 引入的,合起来的效果是:规则包只能写「手工 printf 指令」的玩具规则,一旦规则要用它本该用的 API 就编不过。第一个真实使用者(grpc-m 的 protoc/gRPC codegen 规则)在第一分钟就同时撞上了这两个。
-
规则里的
import std;。build_program.cppm先编 host module、后建 std 模块,所以规则拿到的stdFlags是空的,报module 'std' not found。而且「是否需要 std」只扫了build.mcpp的源码 —— 一条只有规则用import std;的构建根本不会去建 std 模块。现在两处都修:扫描把规则接口一并计入,std 那一段整体挪到编译 host module 之前。这里的顺序是承重的,不是风格问题:BMI 必须先存在、
stdFlags必须先指向它,规则才可能 import。 -
规则里的
import mcpp;。host-module = true只做了「注册这个模块」,从没把这个包移出消费者的普通依赖图 —— 于是同一个.cppm又被当作消费者的一个普通库编译了一遍,而在那次编译里内置mcpp模块并不存在,报fatal error: module 'mcpp' not found。一条构建规则是纯构建期的东西,本来就不该进消费者的二进制(Cargo 用
[build-dependencies]划的是同一条界线)。现在只经由 root 的 host-module 边到达的包会被清空源码集,不再参与编译与链接;仍会被解析落盘,因为 lib 根要从那里读。有护栏:同一个包完全可以既是规则、又是别处(或 root 另一种拼法)的普通库,此时不清空 —— 否则会变成一个离现场很远的 undefined reference。
docs/05-mcpp-toml.md的示例此前写的是import mcpp.rules.protobuf;,做不到:mcpp 用依赖的裸package.name注册 host 模块,而 SPEC-001 要求name是单一原子段。随之澄清一条会咬人的约束:规则包的名字必须是合法 C++ 模块名(grpcgen可以,grpc-rules不行),否则报的是module 'grpc_rules' not found,不会提示你名字有问题。
build.mcpp 机制的架构地基:把「一条指令是什么」收敛成一张表,并补上三个今天就存在的稳定性缺口。架构分析见 .agents/docs/2026-08-05-build-mcpp-extensibility-architecture.md(本次实现其中的步 0 与步 1)。
-
新增一条 directive 从改 9 处降到改 1 处。 一条指令原本要在九个地方定义:
Directives结构体字段、parse_line分派、write_cache序列化、read_cache反序列化、apply落到 manifest、cache_fresh的产物存在性校验、prepare.cppm的DirectiveMark字段、markDirectiveTail、foldDirectiveTailIntoPrivateBuild,再加内置mcpp模块的类型化包装。prepare.cppm自己的注释还承认这个拆分仍不完整(「link/source/fingerprint residues stay at the call sites」)。这是本仓库反复付过学费的「同一决策在 N 处推导」形态(#233/#240/#242/#344):它不在你新增指令时失败,而是过一阵子在别的地方失败。现在一条指令就是新模块
src/build/directives.cppm里kTable的一行,解析、缓存读写、落盘应用、声明产物契约、私有作用域折叠全部由该行驱动。作用域是必填字段,不是可选注释:
include-dir之所以是PackagePrivate,是「构建期程序不得静默拓宽包的公开接口」这条供应链规则(Cargo 纪律),把它变成表里一列意味着下一条指令不回答这个问题就加不进来。为什么是新模块而不是继续写
build_program.cppm:那个文件的匿名 namespace 在 clang 22 + C++20 modules +-O2下会误编译自己的邻居(PR#332 证实一个从未被调用的新函数就足以破坏contract_env,PR#334 复现)。mcpp.build.hostprogram当初就是为此拆出去的。 -
build.mcpp有了线协议版本(S1)。 用import mcpp;的程序在main之前自动声明mcpp:protocol=<N>(你不用自己写)。于是:- 程序声明的协议高于本 mcpp 所理解的 → 拒绝执行并给出升级提示。原先的行为是警告后忽略,那会让「构建成功了但那个 flag 根本没到」——最难查的一类构建 bug——静默发生。
- 既然双方已被证明一致,未知指令即错误:同一协议版本内它只可能是拼写错误。
手写
printf("mcpp:…")的程序什么都不声明,保留历史上的「警告并忽略」。这条不对称就是兼容性契约本身:那一面冻结在现有 11 条指令上,新能力只在类型化 API 里落地。 -
build.mcpp缓存带上了语义 epoch(S2)。 缓存条目里的指令是命中时原样重放的,所以当引擎对某条指令的解释改变时,旧条目必须失效而不是被按新含义重放。纪律照抄cache_key::kCacheEpoch:只在旧条目真的不可用时 bump,且与 mcpp 版本号解耦(否则每次发布都会让所有构建程序白重跑一遍)。一条本 mcpp 不认识的d记录(更新的 mcpp 写的)同样让整条条目作废——只重放认识的那部分,等于应用了程序所要求的一个真子集。 -
build.mcpp的运行有了时间上限(S3)。 默认 600 秒,超时杀掉并让构建失败,错误点名是哪个包、并说明怎么改。原先没有任何上限:一个死循环或卡在网络读上的构建程序会让整个构建挂死且毫无诊断。编译这一步刻意不设上限——与
mcpp test同一条不对称纪律(run 有限 / build 无限):编译跑得久通常是正当的(首次构建std模块就是分钟级),杀掉它只会产生莫名其妙的失败;构建程序跑得久通常是卡住了。capture_exec_deadline顺带补上了cwd形参——没有它,加超时会静默改变构建程序相对写入的落点。
-
依赖产出的 host 工具:
tools = ["protoc"](#355)。 一个包能构建出消费者在构建期需要的二进制(protoc、grpc_cpp_plugin、flatc、moc、转译器),但消费者此前完全拿不到它 ——mcpp::dep_dir()给的是源码树,而依赖的kind = "bin"target 从不被构建(plan.cppm只遍历 root 的 targets;唯一的例外kind = "shared"是按--target构建的,当 host 工具用不了)。为什么它不能是主图里的一个节点:时序。
build.mcpp跑在 prepare 内,那时 BuildPlan 还不存在、build.ninja 更在其后 —— 主图产出的东西对需要它的程序来说永远来得太晚。再叠上交叉编译,它连架构都不对。所以工具由嵌套的、面向 host 的子构建产出,落进全局 store。这正是 Cargo[build-dependencies]/ Bazel exec configuration / vcpkg"host": true/ Conantool_requires的形状。为什么它便宜:工具是可执行文件,与主构建零 ABI 接触。子构建因此可以用工具包自己的
[toolchain]、自己的 profile、自己的依赖解析,不必与消费者一致 —— 对照kind = "lib"依赖,这几条必须一致。单一版本轴:工具的版本就是依赖的版本,所以「protoc 与 protobuf 运行时错配」这类运行期才炸的问题结构上不可表达。默认关闭(成本由消费者付),成本门复用已有的
[features]+required_features。全局 store 按 包版本 × host 工具链 × feature × 自身依赖闭包 缓存。逃生舱
[tools.overrides]/MCPP_TOOL_<PKG>_<TOOL>:直接指一个现成二进制,完全跳过构建。每个同类系统都提供这一条(vcpkgVCPKG_HOST_TRIPLET、CMakeLLVM_NATIVE_TOOL_DIR、QtQT_HOST_PATH),理由一样 —— 源码在本机构建不出来的工具不能是死路。它刻意不进 cache key:逃生舱不是可复现输入。 -
mcpp:action=:声明构建图节点,而不是在 build.mcpp 里干活。 在程序里直接写生成逻辑,是每次 prepare 跑一遍、全量、串行,失败报「build.mcpp exited 1」。声明成节点后它是图里的一条边 —— 增量、并行、失败可归因到具体那条边。一个原语,三种接线(
role只决定输出接到哪,不是三套机制):source进编译集(protoc、转译器)、check产出 stamp 且默认与编译并行(clang-tidy、格式/ABI 检查;blocking可改成前置)、artifact的输入是链接产物(签名、打包、size budget)。顺序完全由 ninja 的文件依赖决定,不需要任何 phase 机制 —— 这也是为什么artifact不会像朴素的「post 钩子」那样把自己重复施加一遍。必须写出输出文件名:mcpp 在 prepare 期就定死源码集、fingerprint 与模块图,名字未知的产物无法进图。内容可以晚到,名字不行。畸形 action 是硬错误而非静默跳过。生成模块接口时用
.provides()/.imports()声明,mcpp 会按该声明播下占位文件让 prepare 期的扫描与生成器将要产出的内容一致 —— 与[modules].scan_overrides同一条「声明+验证」的取舍,build 期由编译器自己的 P1689 复核。命令是 argv 而非 shell 字符串(不假设存在 shell),插值只有封闭的四个:
${mcpp.out_dir}/${mcpp.bin_dir}/${mcpp.compile_db}/${mcpp.target_file:<name>}。 -
host-module = true:可复用的构建规则以普通包分发。 「跑 protoc」这类规则应该写一次,而不是每个消费者的 build.mcpp 复制一遍。把它做成普通 mcpp 库包,消费者import mcpp.rules.protobuf;即可。规则因此有版本、能测试、能发布,走的是已有的包管理机制,而且是用 C++ 写的 —— 不引入第二门语言(xmake 用 Lua rule、Bazel 用 Starlark),这正是 build.mcpp 存在的理由。实现上的关键:规则模块与 build.mcpp 在同一条命令里、用同一套 flag 编译。这不是优化 —— BMI 只对「在 standard / dialect / 编译器身份上与它一致」的编译可用,分成两次独立解析的构建则毫无理由一致,而不一致的表现是
module X CRC mismatch而不是一条清楚的错误。 -
工作目录可外置(
BuildOverrides::work_dir)。 一次mcpp build会往工程根写 5 处(target/、mcpp.lock、compile_commands.json、.mcpp/、target/.build-mcpp),而「注册表包根是共享的、可能只读、绝不写入」是build_program.cppm自 G2 起的明文不变量 —— 在此之前没有任何东西能对 build.mcpp 的临时目录之外兑现它。把「源码在哪」与「往哪写」拆开,是 host 工具子构建能够存在的前提。五处一起搬:只搬一部分比一处都不搬更糟,那等于照样写进共享目录、只是更不显眼。
- 内带 xlings 升级到
2026.8.5.1(自2026.8.4.1)。13 个 pin 点由check_version_pins.sh机器校验并全部更新。
- 自举 pin 指向了索引已不再提供的版本。
ci-aarch64-fresh-install自 2026-08-03 起在 main 上就是红的:version '2026.8.3.2' not found for 'mcpp'。.xlings.json的自举 pin 是自举起点、故意滞后、不随发布走(docs/09 §4),平时不该动 —— 但它有一条硬约束是必须命名一个可安装的版本,这条被打破时正是该 bump 的场合(而不是「发版顺手 bump」那种误用)。改到2026.8.4.1。
-
ci-fresh-install11 个 job 全红 —— 两个独立缺陷。 都不是某次发布引入的,而是每次发布之后必现;两个各自都能单独把整个矩阵打红。分析见.agents/docs/2026-08-04-ci-fresh-install-two-defects.md。A. 仓库的 workspace pin 伏击了被测版本。 job 第一步就 checkout,于是仓库的
.xlings.json(声明自举 mcpp,手工维护且故意滞后)落在工作目录里。它是目录作用域的,在 checkout 内部压过全局安装。于是「装的是MCPP_PIN,跑的是自举版本」——而后者根本没装:✓ 1 package(s) installed [error] xlings: version '2026.8.3.2' not found for 'mcpp' [error] available: 2026.8.3.4ci-aarch64-fresh-install.yml早就遇到过并靠「checkout 放最后」规避,注释写得很完整 —— 但那招在这里用不了:build mcpp步骤要在仓库里跑mcpp clean && mcpp run,checkout 必须在场。所以改为中和那个 pin:这个 workflow 验证的是已发布的 mcpp,自举 pin 在这个问题上没有发言权。B.
wait-index守的是另一条分发通道。 它轮询索引的 git 真源(raw.githubusercontent.com/openxlings/xim-pkgindex),而 job 从发布出来的 artifact(xlings-res/xim-index→ 指针 → tarball)安装。两条通道延迟完全不同:git 在 PR 合入瞬间更新,artifact 还要打包 + 过 CDN。2026.8.3.5 那次实测:守卫报「就绪」,11 个 job 随后全部package 'mcpp@2026.8.3.5' not found。测量一条没人从那里安装的通道,不叫守卫。 现在改查 artifact 通道。两处修复连同「装了 ≠ 跑的是它」的激活(
-u+xlings use)与断言,收敛进一个共享脚本.github/tools/install_released_mcpp.sh(5 处调用点)。断言是唯一能防住下一个的部分:它对着 PATH 解析出来的mcpp求值 —— 也就是后续步骤真正会执行的那个 —— 于是任何未来的静默重定向(workspace pin、陈旧激活、PATH 意外)都会变成一条点名的失败,而不是一个悄悄测了错二进制却报绿的矩阵。CDN 是按边缘节点传播的:轮询的 runner 不是安装的 runner。所以守卫收窄窗口、脚本内的有界重试兜住残差 —— 两层分工,缺一个要么留偶发红、要么让整个矩阵陪跑等待。
-
内带 xlings 升级到
2026.8.4.1(自2026.7.28.4)。该版本落地了索引快照的版本契约与自动路由(openxlings/xlings#476,由本仓库提出):索引声明它需要的客户端版本,客户端自动路由到自己能用的最新快照,版本错配从硬失败变成路由决策。对 mcpp 的直接意义是它补上了 mcpp 自己做不到的那一半 —— mcpp 不下载索引(
update_index就是 shell out 给xlings update),此前无法要求「给我索引版本 X」。新增的xlings index list --json会把requires里非xlings的键原样透传给对应消费者,xlings index use提供钉选。mcpp 侧消费这套接口(按min_mcpp自动路由)是后续工作,需要索引侧先声明requires.mcpp;本次只做版本升级与验证。16 个 pin 点由
check_version_pins.sh机器校验并全部更新;已实测 mcpp 在2026.8.4.1下new/build/run正常。
-
索引侧的改动不再能让 mcpp 从「能用」变成「不能用」。 一个索引树可以声明客户端版本下限(
index.tomlmin_mcpp)。此前xlings update会原地覆盖本地索引,一旦上游抬了下限,一台一分钟前还好好的机器就报error: index requires mcpp >= 2026.8.3.3 but this is mcpp 0.0.109 [E0006] error: ... package 'compat:compat.libarchive@3.8.7' not found而且没有退路 —— 旧的、能用的那份树已经被覆盖掉了。刷新本身就是把机器弄坏的那个动作。
刷新现在是单调的:判据是「刷新前能用 ⇒ 刷新后必须仍能用」。
mcpp::xlings::update_index(全进程唯一的刷新入口)在同步前归档每一棵可读的索引树,同步后复核;变得不可读就回滚,并提示可以升级 mcpp 拿新包。2026-07-08 的索引设计里写过这个行为("staged refresh keeps the last compatible snapshot"),从未被实现 —— 下限只在描述符读取处检查过一次,刷新路径对它一无所知。 -
不再把「这个索引读不了」伪装成「这个包不存在」。 违反下限会让该树的每次描述符读取返回空,与「包确实不在」不可区分。后果有两层:终止构建的那条错误是
dependency '...': not found,把责任推给包(指向发布/命名),而真正的答案(升级 mcpp)早已滚出屏幕;同一个无法区分的 miss 还会喂给刷新策略,被读成「本地缺东西,去拉」—— 不可用状态自己在驱动重复刷新。「这个索引不可读」现在是一个可查询的事实:刷新策略据此不再徒劳重试,而每一处终止构建的 not-found 都会带上真正的原因。
-
mcpp cache/索引之外的一处分层修正:MCPP_VERSION移到叶子模块src/version.cppm。 它此前住在mcpp.toolchain.fingerprint里,而后者import mcpp.toolchain.detect→import mcpp.xlings,于是「这个二进制是什么版本」这件事传递依赖了整个工具链探测与包管理子系统。这不是洁癖:mcpp.pm.index_contract只为一次比较需要这个常量,却因此依赖上 xlings,反过来使得 xlings 不可能依赖 index_contract —— 而刷新守卫恰恰必须装在那里。编译器报的那个循环依赖,就是分层在告诉我们常量放错了地方。 版本号的唯一真源随之移动;check_version_pins.sh与发布文档同步更新。 -
补上了从未存在的端到端覆盖。 下限行为此前只有纯谓词单测(
floor_violation()),tests/e2e/里一条都没有 —— 这正是上面两个缺陷能长期存在的原因:谓词是对的,谓词周围的行为从没被端到端跑过。新增tests/e2e/185_index_floor_degrades.sh(错误必须点名真正的原因、不可用索引不得波及健康索引、逃生舱仍有效)与tests/unit/test_index_snapshot.cpp(9 条,含「刷新弄坏索引必须回滚」)。
-
全局 build cache:同一个 key 下第二个消费者必挂
missing and no known rule to make it(#344)。 一个依赖的.o存在 cache 条目里的路径,此前取自消费方 build dir 的相对路径。而 build dir 里的对象布局由 #233 的 basename 消歧决定,消歧的判据是一次跨越整个 build dir的普查 —— 也就是说,它取决于消费方还拉了哪些别的包:- 同时拉了
compat.zlib与compat.bzip2(两者上游各有一个compress.c)→obj/compat_zlib/zlib-1.3.2/compress.o - 只拉了
compat.zlib→obj/compress.o
cache key 刻意不含消费方(这正是跨工程共享成立的前提),于是两种布局落进同一个条目,后跑的那个工程按自己的布局去取,必然缺一个文件。失败发生在 ninja 的 graph 加载阶段 —— 一条命令都还没跑,上一行却刚打印过
Cached … (15 units)。mcpp-index 全量 workspace 在三个平台上共 32 个成员因此失败。条目内的对象地址现在是包自身的纯函数:镜像源文件相对它自己包根的路径,不含任何消费方信息。构建目录里,依赖包的对象一律落在
obj/<pkg-slug>/…下 —— 跨包撞名结构性地不可能发生,依赖包因此完全退出消歧普查。根工程(永不入 cache)保持沿用至今的扁平obj/<name>.o。#233(编译边撞名)、#240(链接输入未跟改名)与本条是同一台机器的三个产物:布局由一次全局普查决定。所以修的不是再补一处同步,而是把依赖包从普查里彻底拿掉。
- 同时拉了
-
链接/归档命令一律走 response file,不再有「项目大到一定程度就崩」的隐形上限。 此前只有 Windows(CreateProcess 32 KiB)与 msvc 方言用 rspfile,POSIX 走内联
$in,理由写的是「ARG_MAX 很宽裕」。两半都错:ninja 在 POSIX 上是sh -c "<整条命令>",整条命令是一个 argv 项,撞的是MAX_ARG_STRLEN(32 页 = 128 KiB)而不是 2 MiB 的ARG_MAX;而且它从来就不宽裕 —— 实测 mcpp-index 的opencv-module,内联链接行本来就已经 56840 字节,占那条无人看守的上限的 43%。上面 #344 让依赖对象路径变长(多一层包目录),同一条边到了 161687 字节,于是 ninja 直接
ninja: fatal: posix_spawn: Argument list too long—— 不报是哪条边、哪个文件、什么原因。构建系统不能有一个「靠崩溃才被发现的项目规模上限」,「这条命令有多长」也不应该是选对象路径时需要有人记在脑子里的事。clang/gcc driver、link.exe、GNU ar、llvm-ar 全都认@rspfile,现在全平台一个规则形态。
-
cache 条目与本次构建的布局分歧,现在降级为 miss 并明确报告,而不是让 ninja 崩在图加载阶段。
is_cached此前校验的是条目自述的文件表,而消费方随后按自己算的地址去取 —— 两处独立推导,从不比对。命中判据现在校验「本次实际要读的那批产物」,任何不匹配都只是一次重编。同时新增一行 warning:一个系统性的分歧否则会表现为「cache 永远不命中」而毫无信号,这正是 v2026.7.30.2 之前那个假Cached骗了三个月的失败模态。 -
可缓存性改判磁盘出处,不再只认标签。 规则一直写着「无法证明来自不可变的 xpkgs store 就不准入」,但代码实现的是更弱的代理(
sourceKind == "version")。多版本共存(mangling)会把消费方包的根重锚到<project>/target/.mangled/…并改写其源码,而标签仍是"version"—— 它今天不出错只靠轴 F 侥幸。现在按包根的实际位置判定。同一批还加了「全有全无」:任何一个单元拿不到与机器无关的条目地址,整个包退出缓存,不留下半 staged 的包(那会表现为三条边之后的 BMI CRC mismatch)。 -
mcpp cache verify现在会报告逃出条目的对象地址。 让「条目地址必须是包内相对路径」这条不变量可以离线审计,而不是只能通过复现一次双工程构建才看得见。 -
kCacheEpoch1 → 2。 产物布局变了,旧条目描述的是本版本不会去要的布局。它们本来就会被判为 miss,但让两套布局共用一个目录会让cache gc的体积统计和cache verify的输出失去意义。用户侧表现为一次全量重建,无需任何手工步骤。
-
交叉构建每次都重新链接(增量构建对 PE 目标实际失效)。
plan.cppm的target_output()用mcpp::platform::{exe_suffix,lib_prefix,static_lib_ext,shared_lib_ext}拼产物名 —— 这四个是主机常量,由#if defined(_WIN32)/__APPLE__选择。主机构建下「这台机器叫什么」与「产物该叫什么」恰好同解,所以它一直没被发现;host ≠ target时两者分岔。后果不是命名难看:Linux 交叉到 PE 时,ninja 被告知产出
bin/foo,而 mingw 的 GCC driver 写出的是bin/foo.exe—— 声明的那个文件从来不存在,ninja 每次都发现输出缺失并重跑链接边。这条路径正是 CI 每天在跑的。产物命名现在由
ArtifactNaming决定,每个 plan 从 target triple 求值一次。它是 (os, env) 二元函数,不是 os 一元:x86_64-windows-gnu用 GNU 约定(libfoo.a),x86_64-windows-msvc才是foo.lib。空 triple 表示「为本机构建」,只有那时主机答案才是对的,因此它作为回退传入而非被直接读取 —— 主机构建逐位不变。同样的处理给了
shared_library_link_flags:消费者链接一个共享库时用完整路径(PE)、@loader_path(Mach-O)还是$ORIGIN(ELF),是产物的属性;按主机求值在交叉时方向就是反的。 -
windows-gnu静态库命名错误(行为变更)。 在 Windows 主机上用 mingw 工具链构建静态库,产物此前叫foo.lib—— 一个 GNU archive 顶着 MSVC 的名字,MSVC 拿不去用。现在按 GNU 约定命名为libfoo.a。这修正的是一个今天就是错的名字,与交叉编译无关。
-
非 ELF 目标上声明
SharedLibrary现在明确报错,而不是产出未经验证的东西。 全部 5 个共享库 e2e 都声明# requires: elf,而这个 capability 只在 Linux 上授予 —— 也就是说共享库在 PE 与 Mach-O 上从未被端到端验证过。相关分支是推测代码:mingw 的 ld 容忍直接链.dll,MSVC 的link.exe不行,而两者都没有 import library 可链,因为 mcpp 还没有建模它。一段既没有测试覆盖、又不肯明确拒绝的分支是最难清理的债 —— 它既不能被信任,也不能被删除,因为没人知道谁在依赖它。先把边界写死,等真要支持时再补(前置条件是先有 PE/Mach-O 的共享库覆盖)。
-
在 Windows 上构建 Linux 程序(
--target x86_64-linux-musl)。 补上host ≠ target最后一个空象限:一台 Windows 机器直接产出完全静态的 Linux ELF,不需要 WSL、不需要容器、不往系统里装任何东西。mcpp build --target x86_64-linux-musl # 在 Windows 与 Linux 上拼写完全相同命令两边一字不差,因为「cross」在 mcpp 里从来不是一个名字,只是
host ≠ target这个关系的取值。由谁来服务这个目标是自动解析的:Linux x86_64 主机装原生musl-gcc,Windows 主机装 canadian cross(build=x86_64-linux-gnu/ host=x86_64-w64-mingw32/ target=x86_64-linux-musl)。两者都是 GCC 16.1.0,都带bits/std.cc,所以import std在哪边都一样能用。产物无PT_INTERP,任何 Linux 发行版上都能跑,与它的 libc 无关。不支持的两种组合是明确拒绝而非碰运气:Windows →
linux-gnu(glibc 需要xim:glibc/xim:linux-headers两个 sysroot 载荷,只为 Linux 主机发布),以及 Windows → 跨架构(canadian cross 载荷按主机架构构建)。mcpp toolchain list只列当前主机真能装的目标,所以某个目标没出现在 Targets 块里,就是这台机器确实服务不了它。
-
-static由构建主机而非目标决定,导致 Windows→Linux 交叉产物根本不是静态的。supports_full_static是一个主机常量(is_linux),描述的是「这台机器自己的二进制能否全静态」;flags.cppm却拿它来决定产物要不要-static。在 Linux 主机上这两个问题对所有 Linux 目标恰好同解,所以它一直没被发现 —— 只有当非 Linux 主机交叉编译到 Linux 时才会现形:-static被静默丢弃,而x86_64-linux-musl这个目标存在的全部理由就是产出可移植的静态 ELF。没有报错,没有警告,那个 flag 就是不在。判据改读解析后的 triple:空 triple(目标即主机)沿用主机答案;PE 目标返回 false,它们的
-static来自 C++ 运行时分发契约,在这里也答 true 会让两套机制都发一遍;macOS 返回 false(libSystem 必须动态);Linux 返回 true。今天所有能工作的路径逐位不变。 -
Windows 主机上交叉工具链的前端永远找不到。 候选名是
{ "<triple>-g++", "g++" },用filesystem::exists解析,而 Windows 上的文件叫<triple>-g++.exe—— 载荷装得好好的,然后不可用。archive_tool的 musl 分支(<triple>-ar)有同样的遗漏。
- 「这台主机能否服务这个目标」收敛为单一判据
host_can_serve。 它此前在两处独立推导:registry.cppm选载荷时一次,lifecycle.cppm决定toolchain list能否把目标标为available时又一次 —— 而且两者已经漂移:载荷侧会解析出一个可用性侧宣称不可能存在的 windows-hosted musl 包。现在判据只有一份,就放在它必须与之一致的载荷解析旁边。
-
[build] static_stdlib = false对测试二进制静默失效(#336)。 #124 在 0.0.52 明确写下这个 opt-out,docs/05-mcpp-toml.md至今也还这么写;但 #202(0.0.86)把测试二进制改成与分发目标相同的静态-load_hiddenlibc++ 时,新增的那条推导没有带上staticStdlib门。结果是约一年时间里 macOS 上的mcpp test没有任何办法回到动态 libc++,而文档一直在承诺它可以。 -
macOS 上全局对象在静态初始化期访问
std::cout必崩(#336)。 Mach-O 没有按优先级排序的初始化段(init_priority只在单个 TU 内有效),归档成员的初始化器按链接顺序排在最后;而 libc++ 的<iostream>不像 libstdc++ / MSVC STL 那样自带ios_base::Init守卫,流的构造只存在于库内对象里。两件事叠起来的后果是:默认配置下,任何在构造函数里碰std::cout的全局对象都会读到 vptr 为零的流,进程启动即 SIGSEGV —— 而且包侧无法修复,因为std::ios_base::Init在 libc++ 的头文件里只有前向声明(ios:70),标准为静态初始化次序提供的官方解药在 libc++ 上用户根本写不出来。修法是让静态链接恢复动态链接本来就有的保证:macOS + 自包含时,mcpp 生成一个极小的 C 翻译单元并把它的对象排在链接行最前,由它先把流顶上去。它对
ios_base::Init::Init()的引用是 weak 的 —— 换一份不这么拼这个 ABI 符号的工具链,链接与今天完全一样,shim 退化为空操作。
-
C++ 运行时分发契约
[build] cxx_runtime。 三档:self-contained(默认)/toolchain-coupled/host-coupled,可按角色({ default = ..., tests = ... })也可按目标三元组([target.<triple>] cxx_runtime,与linkage并列 —— 它们本就是同一根轴)。static_stdlib保留为忠实别名(true↔self-contained,false↔host-coupled)。这个字段替换的旧字段名描述的是手段("静态链接 stdlib"),而它承载的其实是意图(产物能在哪些机器上跑)—— 这正是同一个
true在四种配置上展开成四种不同结果的原因,其中一种是静默空转:Linux + clang/libc++ 工具链上static_stdlib = true一个 flag 都不发,交付的是工具链耦合的产物,而 manifest、文档和--version都说它是自包含的。 -
Linux + libc++ 工具链现在真的能自包含:显式链入
libc++.a/libc++abi.a/libunwind.a(缺 libunwind.a 时产物仍会拉libunwind.so.1,所以它是机制的一部分而不是可选项)。实测NEEDED只剩 libc / libm / loader。 -
兑现不了的契约一定会被报出来。 工具链不带
libc++.a、macOS 没有 deployment floor、MSVC 运行时没有/MT机制、macOS 上没有可用的toolchain-coupled形态(LLVM 的 libc++abi/libunwind dylib 向上链/usr/lib/libc++,会把第二份 libc++ 载进进程)—— 这些格子现在都会打印实际退到了哪一档。
-
五处独立推导收敛成一处。 "这个产物自带 C++ 运行时吗"过去在
flags.cppm的ldStdlibDefault/ldStdlibTest/-static-libstdc++/ MinGW-static四处,加上ninja_backend.cppm里那个按LinkUnit::TestBinary的二分派,各推一遍 —— 这正是新语义只落到其中一处的成因。现在是src/build/distribution.cppm里的三层模型:角色(由链接单元内在决定)→ 契约(按角色取默认,可覆盖)→ 机制(唯一放 flag 的地方,且是总函数)。 -
相应地,C++ 运行时相关的链接 flag 从全局
ldflags移到了每个链接单元的unit_ldflags—— 两个角色在同一次构建里可以持有不同契约,这一点全局通道表达不了。它们都是驱动级 flag,相对库的位置无意义,Linux/Windows 的链接语义不变。
-
mcpp.lock成为 git 依赖的解析输入,而且是权威的那一份。 在此之前 lock 只写不读:branch = "develop"这类依赖每次进prepare_build都要打一次git ls-remote,而写进 lock 的那个 commit 从来没有人看过。现在先读 lock——分支已经解析过就直接用记录的 commit,不再有网络往返。「权威」是这里的关键词,不是「缓存的提示」。一个只在本地克隆恰好还在时才生效的 lock,等于把「构建哪个 commit」这个决定交给了缓存目录的存活状况:清一次
~/.mcpp/git、或者换一台机器 clone 同一个工程,构建就会静默滑到分支的新头上,并把 lock 改写成新值——而mcpp new生成的.gitignore并不忽略mcpp.lock,它本来就是要提交的。实测(见 e2e 24 新增断言):分支移到 v2、缓存删除后,旧行为构建出 v2,新行为构建出 lock 里记的 v1。要新的分支头依然只有一条路——mcpp update <dep>,它丢掉 lock 条目,下次构建重新解析。连带把这块的结构收敛成两个各自独立的问题,各带恰好一个
--offline闸:(1) 是哪个 commit ——tag/rev自带答案,branch由 lock 回答、否则ls-remote;(2) 它在不在盘上 —— commit 选定缓存目录,miss 就 clone。原来 tag/rev 有一条自己的分支腿、打一行每次构建都出现的from cache噪声,并且在 dep 根本没进 lock 时报「is locked but its local cache is missing」;这条腿现在整个不需要了。
-
--offline不再拒绝本地 git 远端。docs/05-mcpp-toml.md写明--offline的语义是「完全不碰网络……已安装的东西照常构建」,prepare.cppm里依赖下载闸的注释也把线画在同一处:「这条线以上全是本地操作,一个依赖齐备的离线构建必须成功」。但git = "../sibling-repo"这种指向本地目录的远端,ls-remote/clone都只是文件系统读取,拒绝它买不到任何隔离性。现在按远端形态判定——file://、以及不带 scheme 也不是git@host:path的存在路径,算本地(Windows 盘符C:\repo含冒号但不含@,因此仍归本地)。 -
克隆被中途杀掉后不再永久提供错误的 commit。 缓存目录以 commit 命名,但内容是
git clone之后再git checkout两步做出来的;进程死在两步之间,目录名和 HEAD 就对不上了,而后续构建只检查目录存不存在。现在分支依赖会比对git rev-parse HEAD,不符即删除重克隆(tag/rev 以 ref 名为身份,无可比之物)。 -
git 克隆在 Windows 上不再依赖
cd跨盘。 克隆命令用的是git clone … && cd <dir> && git checkout,而 cmd.exe 的cd不带/d是不换盘的——缓存根(MCPP_HOME)与工程不同盘是常态。仓库里其它跨盘位置(process.cppm、msvc.cppm)都写了cd /d,这里漏了。改用git -C <dir>,连 shell 都不需要。 -
mcpp.lock读取失败改用 degraded 通道。 按src/diag.cppm的划分,「引擎做得比要求的少」属于 Degraded 且必须给出 impact——lock 读不出来,每个 git 分支依赖都会退回网络解析、并可能越过记录的 commit。原来记的是普通 warning,--strict提升不到它。 -
parse_git_source不再切开名字里带@的 tag/rev。 写侧只对branch追加@<commit>,解析侧却无条件按最后一个@切分,于是一个叫v1.0@rc1的 tag 会被读成 refv1.0+ commitrc1。现在只有 branch 走这条切分(且仍按最后一个@,以容纳feat@v2这类分支名)。
-
mcpp的进度输出在非 TTY 下不再被 libc 缓冲区吞掉。 全部状态行此前都是裸std::println且无一处 flush,仓库里也没有setvbuf—— 于是「什么时候能看见」完全由各平台 libc 的缓冲区大小决定,而这个数字三个平台都不一样:musl(Linux 发行版的链接方式)写死BUFSIZ = 1024且忽略st_blksize,Apple libc 取st_blksize(管道上是 65536),MSVCRT 用 4096 且不支持行缓冲。实测同一个 97 成员工作空间的同一段 ~13 KB 状态输出:Linux 冲了 13 次,macOS 0 次。macOS 的 CI 日志因此只有测试二进制自己的输出(它们是独立进程,各自退出时 flush),mcpp 自己的一行都没有;等这一步被 job timeout 杀掉时,整个缓冲区连同那 13 KB 一起没了 —— 一个 45 分钟、零可归因输出的步骤。
现在
main()把 stdout 设为行缓冲,mcpp::ui的每个写 stdout 的函数额外显式 flush(Windows 上 MSVCRT 会把_IOLBF当_IOFBF,所以两条腿都要有)。实测:同一条命令从 3 次块写变成 391 次逐行写。 -
mcpp test的finished in不再漏算构建时间。 计时起点此前在包级构建与批量测试构建之后,只覆盖逐测试循环。实测一个成员打印finished in 6.53s,真实墙钟 93.5s —— 14 倍的低报,而且恰好在构建最重、最需要这个数字的成员上最不准。现在起点移到 Phase A 之前,并拆成finished in 93.5s (build 87.0s + run 6.5s):测试只要几毫秒、链接要 90 秒的成员,在合并数字里和「测试套件很慢」长得一样,拆开才分得出。 -
--message-format json的 stdout 不再被非 JSON 行污染,且污染是不对称的。 静音标志此前由run_tests自己设置,而--workspace的Workspace testing member表头由扇出层在调用之前打 —— 第一个成员的表头漏进 NDJSON 流,第二个起被静音。现在扇出层在第一个成员之前就静音。
-
mcpp test默认有界。--timeout的默认值从0(不限)改为 300 秒;--timeout 0仍然表示不限,只是现在要显式要求。无人值守的 CI 不该被一个挂住的测试吃掉整个 job,而「不限」作为默认值恰恰保证了它可以。 -
--build-timeout(默认关闭)——--timeout覆盖不到的另一半。 此前--timeout只包住测试进程的运行,而mcpp test的三次 ninja 驱动(包级构建、批量测试构建、逐测试构建)全都没有期限。实测一个 macOS lane 卡在某成员的 14 次可执行链接上超过 44 分钟,--timeout设多大都无效。现在每次驱动各自独立计时,超时报成该成员的构建失败并继续下一个成员,失败行明确写「build timeout」而非笼统的 compile 失败。它与
--timeout不同,默认不开,这个不对称是实测出来的而非风格选择:单个测试跑过 5 分钟不寻常,而冷依赖构建跑过 15 分钟很平常 —— mcpp-index 有一个成员要从源码建 OpenCV,实测 linux 1019s、windows 1289s。给它一个默认上限会把「慢但正确」的构建判红并让人以为是 mcpp 的错。构建可以跑多久是工程自身的性质,所以由工程来说;mcpp 只需要让「说得出来」成为可能 —— 而这正是原先缺的东西。平台限制如实说明:deadline 执行器在 Windows 上没有 kill-by-handle 路径,该值被忽略(POSIX only)。
-
--workspace-timeout(默认 0 = 不限)。 扇出是串行的,一个没有上界的成员会拖住它后面的所有成员。超时后停止扇出、如实汇总已跑完的成员并列出未运行的,而不是把进程留给 CI 去 kill(那会把它想说的话一并丢掉)。 -
mcpp test --workspace有了进度与计时。 逐成员M/N进度、逐测试耗时(t1 ... ok (0.31s))、逐成员耗时,以及 workspace 级汇总 +slowest:榜:workspace result ok. 97 member(s); 412 passed; 0 failed; finished in 355.20s slowest: libs/jsc 93.5s, libs/install 32.2s, libs/http 24.1s此前扇出只打
all N member(s) passed—— 定位「哪个成员吃掉了墙钟」只能靠反解 CI 日志时间戳。 -
JSON 记录带成员限定。 每条 test 记录新增
"member",summary新增"member"/"build_ms"/"run_ms",流末尾新增一条workspace_summary(含failed_members/not_run)。一旦两个成员都有名为smoke的测试,裸测试名就不再可归因。 -
macOS 上不注入运行期库路径这件事,现在会说出来。
runtime_library_path_key()在 macOS 返回空串(理由正确:DYLD_LIBRARY_PATH会波及 ninja 启动的每个可执行文件,可能让系统框架加载到私有 libc++)。后果是依赖[runtime] library_dirs的测试在 Linux/Windows 通过、在 macOS 以 dyld 错误失败,而这条差异此前零诊断。现在当计划里确有runtimeLibraryDirs时给一条点名平台与 rpath 兜底的警告。
分析与实施计划见 .agents/docs/2026-07-31-test-workspace-observability-analysis.md 与 .agents/docs/2026-07-31-test-observability-implementation-plan.md。
-
standard = "c++20"成为一等档位(默认仍是c++23)。 白名单新增c++20/c++2a/gnu++20/gnu++2a。C++20 是这个白名单的地板 —— 命名模块本身就是 C++20 特性,再往下 mcpp 的构建模型不存在。import std;在 c++20 依然可用。 它虽然是 C++23 的库特性,但三家实现都在 C++20 模式下提供std模块:libstdc++ 的bits/std.cc与 libc++ 的std.cppm都没有__cplusplus守卫,MSVC STL 在 microsoft/STL#3977 解禁(修复 #3945,原话:C++20 的封锁"是纯策略选择,没有技术原因")。本次实机验证了 gcc 15.1.0 / 16.1.0 × glibc / musl / mingw-cross 与 clang 22.1.8 + libc++(含std.compat):在-std=c++20下建 std 模块、编译消费者、链接、运行全部通过(musl 与 mingw 分别以-static在本机与 wine 跑通)。不改默认、不改模板:未声明
standard的工程指纹逐字节不变,mcpp new与所有示例继续用 C++23。C++20 是给外部约束(公司内规、只到 C++20 的第三方 API)准备的逃生舱,不是新推荐值 —— 它意味着放弃std::print/std::expected等 C++23 库设施。缓存零改动:标准早已进入指纹、
import std的 BMI 身份与依赖构建缓存键,所以 c++20 与 c++23 各自拥有产物目录与 std BMI。这一点由编译器本身兜底 —— 跨档位复用 BMI 会被直接拒绝(language dialect differs 'C++20', expected 'C++23')。 -
import std的可用性判定从「有没有」升级为「从哪一档起」。Toolchain::importStdMinLevel由各 provider 自己填(GCC / libc++ 答 20;MSVC 按 cl banner 版本答 20 或 23)。STL#3977 之前的 MSVC 仍然封锁 C++20,那些机器上import std;+/std:c++20现在得到一句指名工具链与工程档位的可行动错误,而不是std.ixx内部的报错。
build.mcpp在standard = "c++fly"/"c++latest"下拼出非法的-std=旗标。 它此前用"-std=" + canonical直接拼接,而这两个值的 canonical 不是合法的-std=拼写,宿主编译因未知方言失败。现在与主构建走同一个方言层(cppfly::std_flag),按真正执行编译的宿主工具链解析。
-
索引刷新从「时间驱动」改为「解析驱动」(#315)。
mcpp build/run/test此前只要刷新 marker 超过 1 小时,就无条件跑一次xlings update(同步全部索引仓库,失败还带 3 次 2s/4s 退避重试)——不管有没有东西真的缺失。网络差时这就是「每小时一次、为已经在本地的数据等几分钟」。这不是「功能缺失」:offline-first 的策略早就在仓库里了(
xlings.cppm的 xim 安装门,注释里甚至点名了 Termux 上的 build hang),但prepare.cppm那道 TTL 门先开火,把它变成了不可达代码。同一个决策此前在 5 处各推导一遍,其中两处互相矛盾。现在只有一个真源
mcpp.pm.index_refresh:mcpp build/mcpp add/ xim 安装门 / 安装失败重试全部走它。刷新只在三种情况发生 —— 本地没有索引、描述符不在本地、SemVer 约束在本地版本集内无解。依赖全部能在本地解析的构建零网络请求,无论本地索引多旧。判据换轴的理由:「索引够不够新」不可判定,而 mtime 是它的坏代理(CI 缓存恢复、时钟回拨、tar 保留时间戳都会让 marker 两个方向说谎)。可判定的问题是「解析器能不能用磁盘上的东西干活」——而所有解析输入都在磁盘上(描述符是文件,
resolve_semver解析本地 xpkg.lua)。marker 因此降级为纯去抖计时器。最危险的一条判据(
SuppressedInconclusive):「本地查不到」单独不能推出「需要刷新」。xim 描述符不写namespace,(xim, x)永远匹配不上身份门 —— 把这种 miss 当真,任何带 xim 依赖的工程会每次构建都刷,比被删掉的 TTL 更糟。判据复用IndexRoute::authoritative_for(#307),单测 + e2e 双闸锁住。语义变化:
^1.2对本地索引已知的版本求解。上游新发的 1.3.0 需要mcpp index update或mcpp update才可见 —— 这正是那两个命令存在的意义,已写进docs/05-mcpp-toml.md。 -
mcpp update不再是空操作。 它此前只删 mcpp.lock 条目、然后叫用户去跑mcpp build—— 而构建路径从不读 mcpp.lock(prepare只写不读),所以删了等于没删,行为影响为零。它现在先强制刷新索引(显式意图 ⇒ 不看 TTL、不看去抖),并报告索引 rev 的变化;工程里没有任何走共享 registry 的依赖时跳过(刷了也没用)。 -
新增
--offline/MCPP_OFFLINE=1。 一次调用内完全不碰网络:不刷索引、不下载包、不自动装工具链。已安装的东西照常构建 —— 检查点都放在「真要下载」的那一刻,而不是更早。MCPP_NO_AUTO_INSTALL作为它的旧式窄化拼写保留(只管工具链),文档标注 deprecated:同一个概念此前有三个名字。 -
新增
[index] auto_refresh(全局config.toml)。 机器级关闭自动刷新,下载仍可用。刻意做成布尔而不保留旧 TTL 模式:为模拟一个正在删除的策略而留第二条代码路径,正是本次要还的那笔债。策略归全局配置而非mcpp.toml—— 它描述机器的网络环境,写进工程清单会让工程在内网/家里/CI 之间不可移植。
-
未来时间戳的索引 marker 被判为「永远新鲜」。
age < ttl对负数恒真,所以时钟回拨、容器时间、tar保留 mtime、CI 缓存恢复产生的未来 marker 会让索引一直「新鲜」到墙钟追上为止。不可用的时间戳现在一律读作 unknown,而 unknown 必须是 stale。 -
索引刷新此前没有任何并发保护。 BMI 缓存早就用着
platform/fs.cppm的跨平台 flock/LockFileEx,索引一个锁都没有 —— 并行 CI job 共享MCPP_HOME、多个终端同时构建,就会并发重写同一棵树并并发写 marker。改为非阻塞互斥:拿不到锁就跳过,不排队、不报错(持锁者正在做的就是我们想要的事,排队只会把本 issue 要消除的症状原样复制一遍)。 -
项目级 env 下刷新永不打标。
mark_known_indexes_refreshed在projectDir非空时整段早退,于是带自定义[indices]的工程刷新完全局索引却不留痕,mcpp index status对这些机器永远显示 unknown。
-
mcpp index status增加 revision 列。 索引自带内容身份.xlings-index-version(artifact 分发把 rev 打进文件名:mcpp-index-8d67478.tar.gz→8d67478),mcpp 此前零引用。它是唯一能回答「两台机器/CI 缓存与本地是不是同一份索引」的信号,age 永远回答不了。按不透明字符串处理:子索引的值是日期版本号(2026.7.30.1)而不是 sha,探针实测,绝不解析。 -
依赖解析失败时附带索引身份与年龄(
index: local index 8d67478 (refreshed 12d ago))+mcpp index update建议 —— 变懒之后,「没这个包」和「你的索引是上个月的」必须能被区分开。
-
依赖的全局缓存此前净收益为零 —— 命中也全量重编。 命中的依赖产物由
prepare_build直接拷进 build dir,而这些路径在build.ninja里仍然是 compile edge 的输出;ninja 对「输出存在但.ninja_log里没有该输出的命令行记录」一律判脏,而新 build dir 天然没有.ninja_log。ninja 自己的话:$ ninja -C target/x86_64-linux-gnu/<fp> -d explain -n ninja explain: command line not found in log for obj/zutil.o ninja explain: obj/zutil.o is dirty于是每一个「命中」的 TU 都被重编一遍,而 CLI 照旧打印
Cached <pkg>—— 缓存只在「同一个 build dir 已经有.ninja_log」时"生效",而那时本地产物本来就在。修法不是加
restat(log entry 根本不存在,restat 只在有 entry 时改写 mtime 判定),也不是给 compile edge 打generator = 1(那会把「命令行变了要重编」整条规则关掉,用错误换性能)。命中的依赖改发stage_file边、不发 compile 边、不发 scan/dyndep 边:产物落在 compile edge 本来会产生的同一个路径上,所以链接行、消费者 TU 的 BMI implicit input、运行期部署边全都不变,变的只是「这些文件由谁产生」—— 而 ninja 从此有了可比对的命令行记录。状态行同时带上省下的 TU 数(
Cached compat.zlib v1.3.2 (15 units)):光秃秃一个Cached能骗人三个月,一个必须与 ninja 实际跳过的边数吻合的数字骗不了。 -
mcpp build --release之后裸mcpp build会 0.00s「成功」并交付 release 产物。 与缓存无关的独立缺陷,同根于下面的 profile 轴:.build_cache的条目只按 target triple 去重,而 fast path 的准入条件只挡显式--profile/--dev/--release,挡不住裸mcpp build(那恰恰是「我要默认 profile」的意思)。于是 fast path 拿着 release 的build.ninja跑,build.ninja里写着-O2,产物无 debug info。条目改按
(target triple, resolved profile)去重,fast path 先用同一条纯规则(新的resolve_profile_name,manifest 之外无输入)算出本次请求的 profile 再匹配;不匹配即当 miss 走完整prepare_build。旧条目没有该字段 ⇒ 读成空 ⇒ 任何 profile 都不匹配 ⇒ 自愈,无需迁移。 -
Finished release [optimized]是硬编码的。 唯一调用点传字面量"release",ui::finished又硬编码[optimized],所以--dev(-O0 -g)也自称优化过的 release 构建。现在 profile 名来自本次真正解析出的 profile,描述符来自本次真正解析出的开关(unoptimized + debuginfo等),没有调用方需要猜。 -
传递的
path/git依赖被写进全局缓存。 排除谓词在 root manifest 的dependencies/dev-dependencies里查该包并在 spec 为 path/git 时跳过 —— 但传递到达的包两个表里都没有,于是specIt == end()让谓词返回「不跳过」,本地源码被缓存,且indexName回落到默认索引(一个 workspace 成员B于是以mcpplibs/B@0.1.0的身份落在磁盘上)。它的源码可以在name@version不变的情况下改变,缓存键看不见这种变化。唯一可采信的证据改为解析期记录的来源(
DepCacheIdentity::sourceKind,两个 push 点都在作用域内拿得到):非"version"一律不进缓存。判据方向与mcpp add的存在性门相反 —— 那里「不可证伪就放行」,缓存这里「不能证明来自不可变的 xpkgs store 就不缓存」,因为这里的错误代价是静默的错误对象,不是被拒绝的命令。此前这个缺陷被上面第一条掩盖着(ninja 反正会重编);修好第一条之后它就是静默错误产物,所以两者必须同批。
-
依赖缓存键:全工程指纹 → 每包 Merkle 键。 旧键是整个工程的 fingerprint,而它的 flags 字段序列化了图里每一个包,含 root —— root 的包名、版本、
[build]flags 都在里面。后果:改自己的版本号(0.1.0→0.1.1,实测3b8a8ae4fc217233→2138d7ce160e1154)就让 std BMI 与全部依赖缓存整体失效;两个工程只要包名不同,即使依赖集与工具链完全一致,也一条都不共享。本机实测:26 GB / 1198 个指纹目录,compat.zlib@1.3.2存了 162 份,compat.x11@1.8.1373 份共 1.49 GB。新模块
mcpp.build.cache_key按七个轴逐包成键:工具链身份 / 语言与方言 / profile / 包身份 / 该包自身的构建配置(含 features 折叠后的结果)/ 每个直接依赖的键(递归,Merkle) / cache epoch。不含 root 的身份与 flags —— 实测 root 的[build] cflags/cxxflags不会下发到依赖 TU,这正是跨工程共享成立的机制依据。F 轴之所以递归而不是「枚举上游的 public 接口」:GCC 把被导入模块 BMI 的 CRC 烙进导入者的 BMI,上游接口或 ABI 一变,旧的导入者 BMI 就
module 'B' CRC mismatch+Bad import dependency(手工三组对照实测:只改函数体通过,改签名/-D改布局都硬失败)。可枚举清单必然漏项(上游 re-export 出去的传递模块接口在上游自己的 manifest 里根本看不出来),而失败模态是不对称的 —— BMI 轴取窄是编译器硬报错,.o轴取窄是静默错对象。递归的代价是上游的私有变化会级联下游;对真正吃这个缓存的人群 ≈ 0:index 包的描述符按版本冻结,
B的键只能因版本 bump(那本就该让消费者失效 —— 它链接B的对象)或全图轴而变,而唯一能不改版本就动私有 flag 的 path/git 包整体不进缓存。 -
std 模块缓存键:全工程指纹 → std 自己的身份。
stdmod.cppm写在产物旁边的 15 字段 metadata 一直就是正确完整的 std 身份,metadata_matches一直在按它校验命中 —— 唯一错的是目录名用的不是它。本机实测代价:1014 个目录里只有 15 个真身份,16.10 GB(需要 ~0.5 GB),且mcpp version bump会重新 precompile 一次 std(几十 MB、十几秒)。(实现上有个自指要解:build commands 里含 cacheDir,而 cacheDir 由含它的 metadata 算出 —— 先用占位段生成一份规范化 metadata 算键,再用真目录生成落盘的那份。) -
缓存布局迁到
$MCPP_HOME/build-cache/v1/,条目自描述。pkg/<index>/<pkg>@<ver>/<key16>/与std/<identity>/。刻意不放$MCPP_HOME/cache—— 那个名字已经归GlobalConfig的索引元数据缓存所有,而它的 reset 路径会整目录删除;把编译产物塞进别人会清空的目录里,会以「构建缓存有时会自己空掉」的形态浮现。v1是布局版本段,所以换布局永远不需要迁移或删除旧树。每条目写
entry.json,记下键的全部输入;命中判定从「目录存在 + 文件齐」升级为「schema 匹配 ∧ 键匹配 ∧ 记录的输入与本次算出的输入逐字段相等 ∧ 清单文件齐」。这是把 std 侧一直做对的事搬到依赖侧:此前依赖条目只有一份文件清单,所以怀疑命中错了的时候什么都审计不了。缺字段视为不匹配,永不因为 hash 相等就放行。旧的
$MCPP_HOME/bmi/(按全工程指纹分目录)不读不写不自动删:mcpp doctor报出它的体积,mcpp cache clean --legacy回收。它从来没产生过收益(见上面第一条),但删几十 GB 必须是用户的显式动作。 -
profile 成为失效轴。
[profile.<name>]把开关落在buildConfig.optLevel/debug/lto/strip,由 flags.cppm 变成-O<n>/-g/-flto,而canonical_compile_flags只序列化 cflags/cxxflags/ldflags —— 于是--dev、--release、--profile dist三者同一个 fingerprint、同一个target/<triple>/<fp>/、同一条缓存条目。可感知的行为变化:
target/<triple>/下从此每个 profile 一个哈希目录。好处是 dev↔release 来回切从「每次全量」变成增量,两个 profile 的二进制可以并存;代价是磁盘随实际使用的 profile 数增长(每个 build dir 带一份 staged std BMI,本机std.gcm= 31,466,112 字节)。 -
缓存失效不再挂在 mcpp 版本号上。 缓存键的兼容轴改用独立的
kCacheEpoch,只在缓存布局/键算法/staging 契约真的不兼容时递增。fingerprint.cppm的MCPP_VERSION保持在 fingerprint 里(build dir 命名与本地增量归它管,宁可保守),但每次发版把整个缓存作废对纯 C 目标文件是过度失效。
-
--cache <mode>:三种构建模式。global(默认,读+写全局缓存)/local(不读不写,所有依赖在本工程target/内编 —— 排障时一次性排除「是不是缓存的问题」,也给 CI 一个无共享的可复现基线)/off(不读不写,并先清掉本次的target/<triple>/<fp>/做冷构建)。优先级--cache>MCPP_BUILD_CACHE>[build] cache>global;无法识别的值会被报出来(--strict下为错误),而不是静默回落到global。--no-cache保留为off的兼容别名。它的旧 help 文案「Force-clear target/ before building」两处不准:它清的是构建目录(target/<triple>/<fp>/)而不是整个target/,而且名字与缓存无关。mcpp run/mcpp test一并补上这两个 flag(此前它们连--no-cache都没有)。⚠️ --no-cache的语义有一处收紧:此前它只清构建目录、仍然会回填全局缓存;现在它等于off,即不读也不写。想要「从零重编但仍然刷新缓存」的,用mcpp clean或rm -rf target后正常构建。这个收紧是为了让三个模式正交:一个叫off的模式还偷偷写缓存是说不通的。 -
mcpp cache补齐到可运维。cache dir(缓存到底在哪 —— 此前cache */doctor/clean --bmi-cache各自解析根目录,而 config 的 reset 路径用GlobalConfig::bmiCacheDir,两者可能不是同一个目录)、cache gc --max-size <N>{MiB,GiB} / --older-than <N>{s,m,h,d}(真 LRU)、cache clean --deps|--std|--all|--legacy、cache list --json、cache verify(逐条目校验清单与磁盘,残缺条目非零退出)。cache info现在打印该条目的键输入 —— 怀疑命中错了时第一件想看的东西。prune此前按目录 mtime 排序,而那只记录条目被写入的时间:一个每次构建都命中的热包,和一个一个月没人碰过的冷包一样「陈旧」。entry.json的accessed由每次命中刷新(只重写entry.json,不动产物 mtime —— 那些 mtime 参与 ninja 的 restat 判定),gc按它排序。cache clean开头那句remove_all(<root>/"deps")指向一个从不存在的路径(dep 条目在<root>/<fp>/deps),是死代码。gc刻意不动 std 条目:一份 std BMI 全机共享、重建要几十秒,为了腾一点磁盘赶走它是拿很多时间换很少空间。达不到容量预算时会明确说出来,而不是静默少删。
-
#311 Windows 上 clangd 映射住 std BMI 会让整个构建报
build failed。 mcpp 把 staged std BMI 的路径写进compile_commands.json,好让 clangd 解析import std;—— clangd 于是把这个几十 MB 的文件 mmap 住;而 staging 步骤(rule cp_bmi)用powershell Copy-Item -Force原地覆写同一个文件,Windows 拒绝替换带 user-mapped section 的文件(error 1224)。也就是说,mcpp 一边把这个路径交给别人读,一边在原地重写它。POSIX 侧看不见:GNU
cp -f在目标打不开时会 unlink 重建,POSIX 本身也允许覆写被 mmap 的文件 —— 所以 CI 一直全绿,只有 Windows 用户中招。staging 改为走 mcpp 自己的内部子命令
mcpp stage(形态对齐既有的mcpp dyndep):目标已等价就一个字节都不写,否则先写同目录临时文件再 rename,再退化为原地覆写,失败按退避重试,最终失败时给出点名文件与可能持有者(clangd / 编辑器索引 / 杀软 / 上一次mcpp run还在跑的程序)的诊断。绝不降级为 warning —— staged BMI 过期或缺失会变成难以归因的module 'std' not found,或者更糟:旧 BMI 配新std.o静默链接。「已等价」由逐字节比较判定 —— 无条件正确:内容相同就是不需要写。这个判断只在 ninja 已经认定 edge 脏了才会跑,所以代价可以忽略。(
--verify size保留给确知源是 fingerprint 作用域的调用方:build dir 与缓存目录共享同一个 fp,而 fp 已覆盖编译器身份/target triple/stdlib/std 源哈希/标准与方言 flag。但它不是默认 —— 同一条 rule 还搬 DLL,而 PE 的节对齐让「真的重建了、大小却一样」十分常见。)dep BMI 缓存一直就是「已存在就不动」的(bmi_cache.cppm: "Existing project outputs are left untouched")—— std staging 是全仓库唯一强制覆写的那一处,这个不对称本身就是缺陷。verify 档位是 per-edge 的:std BMI /
std.o/std.compat.*走--verify size,Windows DLL 部署与runtime_alias走默认的逐字节比较。原因是判等要读目标就必须 open 它,而持有者可以连读都不给(WindowsFileShare.None映射 → open 即ERROR_SHARING_VIOLATION);size 来自目录元数据、不需要 open。于是 #311 那条真实路径(clangd 映射 std BMI)连排他锁都扛得住,而可能过期的 DLL 仍逐字节把关 —— 前者靠 fingerprint 作用域保证等长即等价,后者没有这个保证。同一条 rule 也用于 Windows 运行期 DLL 部署,以及 Windows 上的
runtime_alias(PE 没有 soname 符号链接,别名就是刚建出来的 DLL 的副本),因此「往上一次mcpp run还加载着的 DLL 上覆写」这个同类失败一并治好。POSIX 的runtime_alias保持符号链接不变 —— 那里符号链接是语义,不只是写法。 -
重新 stage 一个未变的 std BMI 不再重编整张模块图。 staged BMI 是每个 importer 的 implicit input,而 staging rule 既不保留 mtime 也没有
restat,于是缓存侧 BMI 只要 mtime 变新(在下面那个缓存根缺陷下,换个 cwd 跑就会发生),所有import std的 TU 全部重编 —— 即使字节完全相同。修法是「不写字节」+
restat = 1。实测:touch缓存侧 BMI 后,只有 staging edge 重跑,main.cpp不再重编,下一次构建回到no work to do。一条记录在案的实现约束:跳过时对 mtime 的任何触碰(包括对齐到 src 的 mtime)都会让 restat 失效并重新引发级联 —— ninja 的 restat 只把「mtime 未被命令改变」的输出视为从未需要构建。所以跳过路径不动任何时间戳。
-
私有 glibc 的 strip 不再被「组合出的显式覆盖」绕过。
process.cppm::merged_environ会把继承来的LD_LIBRARY_PATH里的私有 glibc payload 条目剥掉,但显式extra覆盖是原样采用的;而env::prepend_path_list组合该覆盖时,把继承值(含毒)整段追加了进去 —— 于是 strip 恰好在它唯一有用的场景下失效:嵌套的mcpp run→mcpp test链里,子工具拿到一个版本不匹配的 libc.so.6,在动态链接器里 main 之前 SIGSEGV(签名:一行裸__vdso_time)。修法是把判据下沉到
mcpp.platform.env(路径列表组合的所在地),并让prepend_path_list只清洗继承来的尾部:调用方显式传入的 payload 目录必须保留 —— 那正是沙箱二进制需要的那一条。PATH 不受影响。这个缺陷与 #311 无关,是排查 e2e 156 在本 PR 上失败时找出来的:两次 CI 恢复了不同的 sandbox 缓存(
…-01baa227…vs…-0e74cc64…),payload 版本集不同,于是同一个潜伏缺陷只在一侧显形。本改动前该测试的绿灯取决于「毒化的 payload 版本恰好与工具期望的一致」。
-
BMI 缓存根统一为
$MCPP_HOME/bmi。toolchain/stdmod.cppm的default_cache_root()是 home 解析逻辑的一份私有拷贝,自 v0.0.1 起一字未改:没有 Windows 的USERPROFILE分支,也没有 self-contained 安装探测。后果是 Windows PowerShell(不设HOME)下 std BMI 缓存落进当前工作目录的.mcpp-bmi/,而 dep BMI 缓存在%USERPROFILE%\.mcpp\bmi—— 一个缓存两个根,其中一个还随 cwd 漂移(从子目录跑就重编一次 std);release tarball 形态的安装在 Linux 上同样分家。新增叶模块
mcpp.home作为唯一解析器,config/stdmod/prepare的 git 缓存,以及doctor里另外两份拷贝(其中self init --force那份在 self-contained 安装下会去删错的树)全部收敛过来。单测锁死default_cache_root() == mcpp::home::bmi_root()。升级影响:Windows 用户与 self-contained 安装的用户首次构建会重新编一次 std 模块(10–60 s);遗留的
.mcpp-bmi/不再使用,mcpp doctor会指出它,可手动删除。 -
FAILED: <target>不再被输出过滤器整行丢掉,归一成failed: <target>保留。#311 的报告读不出「失败的是 BMI staging 而不是编译」,一半原因就是这行被丢了。同时把 mcpp 自身的可执行路径纳入命令行前缀集合,于是被回显的命令行被过滤、mcpp 打印的诊断保留。 -
mcpp new生成的.gitignore加上.mcpp/(per-project xlings sandbox,以及解析不出 MCPP_HOME 时的本地 BMI 缓存)。
-
版本号改为日期格式
YYYY.M.D.N。 与 xlings 生态对齐(xlings 于同日从0.4.70迁入)。月/日不补零。第 4 段的约定:
.0保留给正式版本 / 稳定版本,日常迭代默认从.1开始 —— 一天内可以有若干次常规发布,.0这个槽位只在该版本被认定为正式 release 或稳定版时使用。跨方案的序是单调的:
0.0.109<2026.7.27.1,第一段由0变2026,不存在回退。
-
#291 私有 glibc payload 不再无条件出现在运行目标的
LD_LIBRARY_PATH里。 该变量会被整棵进程子树继承。当运行目标是个会 shell out 的程序(例如课程 provider 调popen("mcpp test ..."))时,/bin/sh是宿主二进制 —— 它的PT_INTERP烙死在文件里,装载它的永远是宿主 ld.so,而这个变量却把 payload 的libc.so.6递给它。glibc 的 libc 与 ld.so 之间通过
GLIBC_PRIVATE版本锁定(实测:payloadlibc.so.6对ld-linux-x86-64.so.2有GLIBC_PRIVATE依赖),二者必须同一次构建。于是只要宿主 glibc 与 payload 不同版本,shell 就在main之前死于装载器内的 SIGSEGV —— stdout 全空,没有任何诊断。报告者是 Ubuntu 22.04(glibc 2.35)对 payload 2.39。注意这不是「构建机不同导致 ABI 不兼容」:它是纯粹的版本错配,任何宿主 glibc ≠ payload 的用户都会中招。也正因为版本相同就不复现,它一直没被发现。
payload 目录是
LD_LIBRARY_PATH里唯一不同时出现在可执行文件 RUNPATH 中的一项(flags.cppm刻意把它排除,以保持静态/musl 链接干净)。它存在的唯一理由是:被dlopen()的库其自身的 DT_NEEDED 闭包不会查主程序的 RUNPATH。因此现在只在构建确实存在这类依赖库时才注入(depRuntimeLibraryDirs非空)——真实的 host-GL 透传场景走compat.glx-runtime的[runtime] library_dirs,正好落在这个条件内,行为不变;而零依赖的二进制不再拿到它。process.cppm的strip_private_glibc早已保护 mcpp 自己的子进程,但它够不到再外一层:变量是 mcpp 有意设给目标的,目标之后再 fork 什么已超出 mcpp 的控制 —— 能控制的是不必要时就不发。e2e 166 双向锁住(零依赖工程必须拿不到、有
[runtime] library_dirs的工程必须拿得到)。它断言的是发出的环境变量而非「shell 是否崩溃」:后者在宿主与 payload 版本相同的机器上会因为错误的理由通过,那正是这个 bug 长期存活的原因。 -
4 段版本号在比较时被静默截断,致 E0006 索引底线检查失效。
version_req::Version原本是严格三段,parse_version("2026.7.27.1")解析出{2026, 7, 27}后丢弃第 4 段且不报错 —— 同一天内的所有版本互相比较相等。后果落在
pm/index_contract.cppm:索引写min_mcpp = "2026.7.27.5"、用户跑2026.7.27.1,两者比较相等,have >= need成立,底线检查放行。用户拿着不够新的 mcpp 去读新索引,得到的是描述符读取返回空这类难以归因的次生故障 —— 而挡住这种情况正是该检查存在的唯一理由。修法是把
Version扩到 4 段,并新增components记录源串实际写了几段。两条约束:比较只看 4 个数字(components若参与比较,"1.2"就不再等于"1.2.0");str()必须能回写第 4 段 —— 它是载荷性的,pm/resolver.cppm用它重建已解析的依赖版本串,该串会流向 lock 文件与 xlings wire 地址,而.0结尾的版本一旦塌成三段就会指向一个不存在的索引 key。既有三段依赖的解析逐字节不变。
-
所有 xlings pin 升至
2026.7.27.2,并加机器校验。src/xlings.cppm的pinned::kXlingsVersion现为唯一真源,.github/tools/check_version_pins.sh强制.github/下全部 16 个 pin 点与之一致,并同时校验 mcpp 自身版本在四处的一致性。此前靠常量上方一句「keep in lock-step with release.yml / cross-build-test.yml / ci-linux-e2e.yml」的注释人工同步,而那个清单本身就是不全的 —— 漏掉了两个 composite action,CI 沙箱因此在 0.4.30 上静默跑了数周。落实检查时又找出三处从未被记录的 pin(
release.yml里硬编码的 aarch64 xlings tarball 字面量)和三处落后的 bootstrap pin(ci-fresh-install.yml的v0.4.38×2 /v0.4.51)。
-
Windows:显式 ninja 目标集不再撑爆命令行(0.0.104 起的回归)。
mcpp test会把每一个共享前置(除测试自身 main 外的全部编译单元 + 非测试链接产物)列为 ninja 目标,好让包内源码出错时报一次包级错误,而不是 N 次雷同的逐测试编译失败。对大包来说这就是几千个对象路径 —— FFmpeg 的 2281 个编译单元拼出 50,781 字符的 argv。Windows 把 argv 合并成单条命令串交给 cmd.exe,而后者上限 8191 字符,于是命令根本没有执行:返回的是 cmd.exe 的裸 127,ninja 和 mcpp 都没有机会打印任何东西。症状因此极难归因:mcpp-index 的 Windows CI 自 0.0.104 起在
ffmpeg成员上失败,日志里最后一行是无关的下载进度条,既没有build failed诊断,target/.build_cache也没写出。它是全仓唯一大到能触发的成员,mcpp build/mcpp run走default(不带目标参数)则始终正常。修法是把目标集合写进 build.ninja 的一条 phony 聚合边,命令行只留一个词。清单文件没有长度限制,ninja 仍在一次调用内构建整个集合,并行度不变。e2e 164 锁住该机制。
与 0.0.107 修掉的 soname 别名回归同源:都来自 0.0.104 引入的"显式 ninja 目标"(#274)—— 一处设计改动,两个只在特定条件下现形的症状。
-
mcpp test不再漏建共享库的 SONAME 别名(0.0.104 起的回归)。 声明了soname的共享库产物写作bin/libX11.so,但记录的 SONAME 是libX11.so.6,链接器解析传递NEEDED、加载器启动程序时找的都是后者 —— 别名不是附属产物,而是任何链接该库的目标的前置条件。此前别名是一条无人依赖的独立 ninja 边,只能经default到达;0.0.104 的 test 能力批次(#274)让mcpp test改为显式指定 ninja 目标以隔离逐测试编译,于是这条边被静默跳过。症状离根因很远,这也是它潜伏三个版本的原因:消费者链接期报
libX11.so: undefined reference to xcb_connect(伴libxcb.so.1 ... not found),或测试二进制以exit 127退出。mcpp build/mcpp run走default,一直正常,所以只有以mcpp test驱动的工程会中招 —— mcpp-index 的 CI 正是如此,gui-stack/imgui-module/imgui-window三个成员自 0.0.104 起持续失败。修法是把别名挂到消费者的隐式输入上(
plan.cppm的append_direct_shared_deps),与被链接的.so并列。无论 ninja 的目标是default还是某个测试二进制都会生成,且不会多编译任何东西。e2e 64 补了mcpp test一段覆盖此路径 —— 它此前只覆盖build+run,正是缺口所在。
落地 SPEC-001(
docs/spec/package-identity.md)—— 包身份的规范形态。0.0.105 为修 #278 引入的「name必须写成完全限定名」是编码约束而非设计规则:它的唯一成因是 mcpp 构造安装目标时丢弃了已读到的字面name、改用<ns>.<短名>重新渲染一遍。本版本改为直接使用字面值,规范形态回归namespace承载层级、name是单一原子段。配套 xlings 0.4.69(#381,索引改按(namespace, name)建键)。
- 规范文档目录
docs/spec/:存放规范性文档(语义、约束、匹配机制),每条规则标注实现状态,与使用文档docs/*.md、设计文档.agents/docs/*.md分工明确。首篇 SPEC-001 覆盖package.namespace/package.name形态、[dependencies]四种书写文法及其候选阶梯、完整匹配流程、派生量公式与端到端示例。 - 描述符文件名自由:描述符按声明的身份被发现,文件叫什么都行。推荐文件名仍作为快路径优先探测,全部落空时才回落到按身份扫描
pkgs/**/*.lua—— 符合推荐命名的索引零扫描开销。这补齐了 identity-first 解析长期只兑现「验证」半边、「发现」半边受固定候选文件名约束的缺口。 - 同一索引内同短名不同命名空间的包各自可寻址:
(alpha, widget)与(beta, widget)共存于一个索引,分别安装到alpha-x-widget/beta-x-widget。需要 xlings >= 0.4.69。
package.name规范形态改为单一原子段,层级一律放namespace:namespace = "chriskohlhoff", name = "asio"。已发布的完全限定拼写(name = "chriskohlhoff.asio")仍被接受,前缀会被剥离后再判定 —— 现网全部描述符无需改动即可继续工作。反过来,namespace = "mcpplibs", name = "capi.lua"这类短名仍带点的写法被拒绝,因为它描述的是一个没人声明过的命名空间。- 安装目标改为
<namespace>:<字面 name>@<版本>。冒号前缀是包的命名空间(xlings 的 effective namespace),不是索引名 —— 这正是同索引同短名得以消歧的原因;无命名空间的上游包用裸字面名。 - 身份归一化不再 split-on-last-dot:身份就是描述符声明的两个字段。旧规则会从
name反推命名空间(ns="a"+name="a.b.c"静默变成(a.b, c)),现在改为拒绝而非猜测。 - xlings 依赖升到 0.4.69(
kXlingsVersion与 release/CI 的XLINGS_VERSION同步)。
mcpp xpkg parse与安装路径的name形态校验语义随规范反转,共用同一谓词,lint 与运行期不会漂移。
包身份口径双侧收敛(#278)。事故:mcpp-index 把
chriskohlhoff.asio的name从"chriskohlhoff.asio"改成"asio"(namespace 不变),lint 全绿,三个平台的 workspace job 跑满 20~58 分钟后全挂E_NOT_FOUND—— 描述符能解析、能过 mcpp 的身份闸门,却没有任何消费写法能装上。根因是身份归一化(容忍三种拼写)与安装目标构造(只支持一种)口径断层,契约只写在注释里、无人执行。设计见.agents/docs/2026-07-25-issue278-descriptor-name-form-canonicalization-design.md。
- 裸依赖名不再解析到第三方命名空间的包。 命名空间缺省时,
[dependencies]里的裸名只解析三类:mcpplibs(默认)、compat(包装)、无namespace声明的上游包。此前裸名会跨命名空间命中(例如裸tensorvia-cpu能装上aimol下的包),现在必须写全:"aimol.tensorvia-cpu" = "…"或[dependencies.aimol] tensorvia-cpu = "…"。 取舍理由:全域按名发现的便捷性换来三条稳定性损失——同名包的裁决依赖索引优先级(而用户[indices]添加的索引之间无全序)、新增一个索引可能悄悄改变既有依赖解析到的包(供应链隐患)、同一份mcpp.toml在不同机器上可能解析到不同包。依赖解析的可复现性优先于书写便捷性。 迁移无需查文档:失败时 mcpp 会扫描索引并直接给出应当改写成的那两行(见下)。
- INV-NAME 校验(
mcpp xpkg parse):描述符声明了非空package.namespace时,package.name必须是完全限定名(以<namespace>.开头),否则报错退出。索引是以package.name字面值为键的扁平空间,而 mcpp 按<ns>.<short>寻址,两者不相交即永久不可安装。诊断直接给出应写的字面量(fix: name = "chriskohlhoff.asio"),--json同步error字段供索引 CI 机读。mcpp-index 的 CI 本就跑mcpp xpkg parse pkgs/*/*.lua,免费获得秒级防护。 mcpp xpkg parse --allow-split-name:跳过 INV-NAME 检查。xlings 原生索引(xim-pkgindex、-scode)里package.namespace是安装目录分类(config/scode/awesome)而非包命名空间,索引按裸package.name建键,split 形式在那个世界里是正确的——这些树用此开关 lint。- 运行期 fail-fast:
mcpp build在构造安装目标之前用同一个谓词校验手上已有的描述符(零额外 I/O),把"三平台一小时后的 E_NOT_FOUND"变成秒级自解释失败。lint 与运行期共用一份判定,不会再各自推导。已装旧快照的路径降级为 warning,让"本机绿、干净 CI 红"的遮蔽陷阱可见。 - 依赖解析失败的 did-you-mean:候选全部落空时(且仅在此时)扫描索引,若该短名存在于其他命名空间,直接列出 FQN 与两种可直接抄写的正确写法。该扫描是纯诊断——结果只进错误文案,绝不回灌解析、lockfile 或安装层(否则就退化成被否决的全域模糊匹配)。
mcpp emit xpkg --namespace <NS>:为归档场景提供命名空间,无需改mcpp.toml。
mcpp emit xpkg不再生成破损雏形:此前只写裸name、完全不输出namespace,维护者归档进命名空间索引时手补一行namespace = "<org>",那一刻描述符就变成无法安装的 split 形式(aimol.tensorvia-cpu正是这么来的,文件头还留着"AUTO-GENERATED, do not edit by hand")。现在[package] namespace非空时同时输出namespace与 FQNname;为空时给出明确警告,说明不要事后手补。此修复在 2026-06-26 设计 §4.5 里就已写明,一直未落地。- 依赖候选全部落空时不再静默回退:此前会退到第一个候选并把 mcpp 自己编造的命名空间当作结论继续跑,失败被推迟到下载/安装阶段,错误文本里还带着用户从未写过的命名空间。现在当场失败并列出试过的每一个身份。
- discovery 档不再泄漏空命名空间(P3):
(∅, name)命中后写回的是描述符声明的命名空间,而非候选的空值——空命名空间此前会流进 lockfile 与安装层。无namespace声明的上游包(opencv/musl-gcc)保持空命名空间,那是它们的合法身份。
mcpp test能力批次(两轮):逐测试编译隔离、子目录路径命名、过滤器、JSON 输出——四项均为通用能力(cargo/ctest 同形),首个下游消费者是 d2mcpp「练习即测试」重设计(见 d2mcpp 仓.agents/docs/2026-07-23-exercises-as-tests-design.md§4,验收标准即出自该文档)。实施计划见.agents/docs/2026-07-23-test-isolation-json-plan.md。
mcpp test逐测试编译隔离:两阶段构建——Phase A 先建包级共享前置(全部非测试 main 的编译单元 + 非测试 link 产物),失败是包级错误(报 build failed,绝不渲染成 N 个红测试);Phase B 每个测试作为独立 ninja goal 构建,编译失败只标记该测试FAIL (compile)(诊断按测试分组输出到 stderr),其余照常编译运行。此前一个编译不过的测试会让整轮error: build failed零执行。- 测试按
tests/相对路径命名:tests/00-a/0.cpp→00-a/0,子目录下同名 stem 不再冲突(此前直接duplicate test name硬错);平铺布局名字不变。 mcpp test <pattern>:按路径名子串过滤要构建/运行的测试。过滤只作用于构建/运行阶段——计划始终含全部测试,compile_commands.json保持完整(clangd 依赖)。无匹配时报错退出码 2。mcpp test --message-format json:NDJSON 输出,逐测试一条记录流式发射(test/status=pass|compile_fail|run_fail/exit_code/signal/compile_output/run_output),包级失败单独{"error":"package",...}记录,末行{"summary":...};stdout 纯协议流,人读输出全部静默。供 CI/IDE/d2x Provider 消费。[build].flagsglob 覆盖测试 TU:glob 指名文件,是 source 还是 test 是正交的——匹配的条目经既有 per-target flag 通道(#131)挂到合成测试 target 上,per-test 编译选项从此有 toml 承载。配套:死 glob 警告改为「磁盘上无文件匹配」才触发(此前只数被扫描的 source,指向 tests/ 的合法 glob 会被误警)。
- Phase B 并行化:逐测试隔离改为「keep-going 全量预构建(-k 0,跨测试并行)+ 逐测试缓存命中验证/失败重试取诊断」——语义不变,49 测试全量墙钟 29s → 3.7s。
mcpp test --list:只枚举(可过滤的)测试,不解析工具链、不构建;--message-format json输出{"test":…,"main":…}逐行记录 + total 汇总,坏文件同样可列出。供 d2x Provider 等工具做单一真相源发现。mcpp test --timeout <secs>:每测试运行截止,超时 SIGKILL 并计为FAIL (timeout)/ JSON"timed_out":true(POSIX;Windows 暂为尽力而为,不生效)。- JSON 记录增加
duration_ms(该测试构建+运行墙钟);schema 只增不改。 - 信号退出码规范化:
WIFSIGNALED → 128+WTERMSIG(shell 惯例)。此前返回原始 wait status——SIGSEGV 仅因 core-dump 位碰巧显示 139,SIGTERM 死亡会伪装成exit 15;JSON 的signal字段推导从未可靠触发。属 bug 修复。
- #273 沙盒围栏:post_install 的所有改写者(
patchelf_walk/fixup_gcc_specs/fixup_clang_cfg)以显式信任根(cfg.registryDircanonical 化一次后穿透)逐文件设防——物理位置(符号链接解析后)在根外的一律拒绝改写并 fail-closed。修复既有入口 guard 的三处残余缺口:error_code 复用导致的错误掩蔽、裸字符串前缀无组件边界(registry-evil可穿过)、binutils 兄弟目录 walk 不在 guard 覆盖内。13_toolchain_pin.sh的 symlink 种子保留为金丝雀。单测 test_post_install_containment 覆盖事故拓扑。 - 嵌套 mcpp 的
LD_LIBRARY_PATH段错误:外层mcpp run为其子进程指向私有 glibc payload 的 loader 路径,子进程再 spawn mcpp 时(如课程 Provider 驱动mcpp test),毒化值继续流入内层 mcpp 的工具子进程——sandbox ninja/gcc 加载错配 libc 后在动态链接器里段错误(残片签名__vdso_time)。现在merged_environ从继承的LD_LIBRARY_PATH/DYLD_LIBRARY_PATH里只剥离xim-x-glibcpayload 条目:用户自己的条目保留,per-child 显式 override 一如既往优先。下游(d2xplatform.cppm、d2mcpprunner.cppm)的unsetenvworkaround 可随本版删除。
- e2e 新增 152(子目录命名)/153(隔离与包级归因)/154(过滤器)/155(JSON)/156(嵌套 loader 路径免疫)/157(测试 TU 吃 glob flags + 死 glob 判定)/158(信号退出码+duration)/159(--list)/160(--timeout)。
- 首个下游消费者已完成端到端接入:d2mcpp「练习即测试」迁移(104 练习 zh/en 52/52 全绿)+ d2x checker 引导链路,全程使用本分支 musl 静态二进制。
- docs 措辞修正:"(gtest style)" → 框架无关表述;记录合成测试名含
/的命名豁免(zh+en)。 BuildOptions::ninjaTargets(构建计划子集)为支撑性通用接口,空 = 原行为。- cross-build CI 的 wine 安装改为缓存 .deb 依赖闭包(首跑回填,命中免 apt update/下载)。
- musl 静态构建(
--target x86_64-linux-musl)已验证:五个新 e2e 全绿;静态 mcpp 本体对 loader 毒化免疫,与上述修复共同覆盖嵌套链路的两端。
#267/#269:索引侧健壮化一批——mcpplibs 索引 URL 组织迁移(不再依赖 GitHub 重定向)+ 采纳 xlings 0.4.68 per-repo artifact 来源(索引同步不再硬依赖可用的 git)。设计见
.agents/docs/2026-07-22-issue267-269-index-artifact-and-org-migration-design.md。
- mcpplibs 索引 URL 仍指向已迁移的旧组织(#267-A):索引仓已迁
mcpplibs/mcpp-index,mcpp 4 处硬编码旧mcpp-communityURL 全靠 GitHub 仓库重定向才能工作——旧名一旦被再次占用即静默指向错误内容。新/旧 URL 与 artifact base 收敛为mcpp::config三常量;canonicalize_legacy_index_names导出并承担"URL 归一→名字迁移→artifact 填充→去重"单循环(URL 归一先行,老配置的名字迁移仍命中、新旧条目在去重处折叠);存量安装经既有文本迁移钩子治愈(config.toml 与 registry.xlings.json的 URL 就地替换),publish Fork 提示与 docs 链接同步。
- 采纳 xlings 0.4.68 per-repo artifact 来源(#269,#267-B):默认
mcpplibs条目(config.toml 模板 + 内存级默认)声明artifact = xlings-res/mcpp-index,source = "git"为显式退出口;seed 管线由(name,url)pair 升级为SeedRepo{name,url,artifact,source},非空才发射——无 artifact 的仓输出逐字节不变(测试锁定)。存量 registry.xlings.json唯一治愈通道是文本迁移:URL 归一后就地注入 artifact 字段,幂等闸=res base 已出现即跳过,双间距变体覆盖 mcpp/xlings 两个写者,subos/版本绑定等无关状态逐字节保留(不整文件重生成)。旧 xlings(<0.4.68)安全忽略新字段,发布端xlings-res/mcpp-index布局实测零改动。 - 项目级
[indices]artifact/source(#269 可选项):IndexSpec增两字段,mcpp.toml 与 config.toml[indices]长格式同步解析,经项目 seed 透传;artifact_applicable()编码设计规则——rev/tag/branch pin(及本地 path)强制 git(artifact 通道只跟 latest pointer),声明了 artifact 但不适用时告警而非静默同步错误版本。 - 捆绑 xlings pin 0.4.67 → 0.4.68(release/cross-build/e2e 三 workflow):0.4.68 携 openxlings/xlings#377(自定义 index artifact)/#380(shim env 去重)。陈旧无消费的
kXlingsVersion(0.4.51)对齐至 0.4.68 并由mcpp self env打印,常量不再能静默漂移。
- e2e 新增 151:fresh init 播种新 URL + artifact;老 org URL/无 artifact 的存量 config.toml 与
.xlings.json(pretty/compact 双间距)就地治愈且幂等,无关.xlings.json状态保留。 - CN region 对象形态(
{"GLOBAL":..,"CN":..})待 gitcode 侧xlings-res/mcpp-index镜像仓真实部署后再扩(设计文档 P5),避免声明 404 base 白付探测。
四条 issue 一个批次:#261 windows 命令行长度、#257 clang 依赖跟踪缺口、#258 条件段表达力、#254 host/target 双轴不自洽。贯穿主线是同一决策不许两处推导,以及本批次新立的第二条不变量失败必须响,不许静默降级。设计见
.agents/docs/2026-07-22-v0.0.102-batch-254-261-design.md,issue 裁决见.agents/docs/2026-07-22-issue-triage-254-261.md。
- windows scan-deps 撞 8191 字符上限(#261):clang 扫描规则此前用 shell 重定向产出 P1689 JSON,ninja 在 windows 走 CreateProcess 不解释
>,于是整条命令被cmd /c包裹——而 cmd.exe 的命令行上限是 8191,只有 CreateProcess 的 1/4。改用clang-scan-deps -o(LLVM 17+,两个自带工具链均实测可用),重定向与包裹一并消失,与 GCC 的-fdeps-file=、MSVC 的/scanDependencies三条分支形态统一。cmd /c自此在全仓 ninja 规则中绝迹。 - windows 编译/扫描命令行无长度兜底(#261):
local_include_flags()每依赖一个-I、无上界,而cxx_module/cxx_object/c_object/asm_object/cxx_scan全部内联该载荷;扫描命令包住编译命令,只是恰好先炸。把 #247 给链接规则的 response file 推广到所有携带无界载荷的规则(gcc/clang 驱动与 cl.exe 均展开@file,clang-scan-deps 会把--之后的@file透传给驱动;NASM 因为拼写是-@ file而排除)。顺带把escape_flag_path改用generic_string()——这是 #247 那个坑往下一层:response file 内容按 GNU 文法分词,反斜杠是转义符,windows 的-I路径一旦离开命令行就会丢分隔符。POSIX 逐字节不变。 - clang 路径 purview
#include不触发重建(#257):编辑模块接口 purview 内#include的文件后,构建静默复用陈旧 BMI,导入方对着旧接口编译——最差的一类失效,错误结果看起来像一次正常的增量构建。根因是 0.0.97 把两个决策捆在一个谓词里:是否发 depfile 与 是否剥离 GCC-fmodules附加的 reversed make-rules。实测两个自带工具链,clang 的 module-TU depfile 就是一条普通规则(x.o: x.cppm ops.inc),没有任何需要过滤的东西——当年那道闸在防一个不存在的形状,代价是把与它捆在一起的正确性契约一起关掉了。现拆为posixDepfile(所有 POSIX 非 MSVC)与needsGnuModuleFilter(仅 GCC)。同时补上同一处不对称的另一半:c_object/asm_object此前在任何工具链上都没有 depfile。windows 非 MSVC(mingw gcc / 托管 clang)仍无 depfile(GCC 过滤器依赖 awk),但现在经diag::degraded明确上报影响,不再静默。 - xpkg per-OS 段按宿主而非目标拼接(#254):per-OS 段的 sources/flags/deps 与 xpm 版本/资产表全部按
xpkg_platform这个编译期宿主常量选取,而[target.'cfg(...)']按解析后的目标求值——同一决策两处推导。交叉编译时依赖包因此被拼进宿主那条腿。原生构建 host == target 恰好掩盖了它,所以三平台 CI 从未发现。
mcpp::diag统一降级/告警通道:degraded()强制填写impact——作者必须回答"用户会因此遭遇什么",而这正是每一个静默降级 bug 当年缺的那句话。记录按整条载荷去重、报告即渲染(告警与ui::status的交织顺序、以及提前返回前已报内容都不变),--strict提升策略收敛到单点(此前在prepare.cppm里 copy-paste 了 5 处)。告警内容首次可单测。prepare.cppm的 8 处裸println(stderr, "warning: ...")已迁入;渲染仍走ui::warning,输出逐字节不变。[target.'cfg(...)'.build]支持 per-globflags与include_dirs(#258,xpkgtarget_cfg同步):此前sources可按 OS 条件化而flags不能,于是一份覆盖三 OS 的 manifest 必须让每个 OS 都看到另外两个 OS 的 flag 条目,而它们按构造必然零命中。vendored-opencv 移植为此付出 703 个 stub 文件(唯一用途是给 windows TU 制造 OS-唯一路径好让全局 flag 表 key 上去)、32 条指向它们的 glob,以及每次构建 ~23 条结构性死 glob 告警。条件条目追加在 base 之后,GNU last-wins 使撤销可表达(windows 需要-UHAVE_UNISTD_H对抗 base 的-D,而无条件覆盖层做不到——-U反条目自身也需要按 OS 条件化,递归回同一缺口)。死 glob 告警由结构消除:不匹配当前 OS 的条目根本不进globFlags,与 #253 在 feature 轴上的做法同构;issue 里提的optional = true逃生口明确不做,那会成为第二套抑制机制。mcpp.platform.axis:host / target 两轴成为不同类型(#254):互不可转换,且都不能由裸字符串构造;API 声明自己要哪一轴,调用点必须指名自己给的是哪一轴。synthesize_from_xpkg_lua的平台参数去掉宿主默认值改为必填——静默默认正是这个 bug 得以存活的方式。三段 triple 与 xpkg 的词汇差异("macos" vs "macosx")收进TargetPlatform::for_os。分类:依赖包 manifest 合成与版本/资产选择走 target;工具链版本列举走 host(其注释本就写明了理由);--all-oslint 走刻意起得别扭的for_lint_of()。BuildInputs类型(#258 的架构面):cfg 轴与 feature 轴此前各自手挑可条件化的字段子集,且挑得不一样(cfg 拿 cflags/cxxflags/ldflags/sources,feature 拿 sources/defines/flags)。"哪些构建输入可被条件化"这一决策被提炼成一个类型而非一张要靠人记得更新的表。入选判据两条:合并语义是追加;消费点在条件合并之后。据此排除选型轴(target用来选定 triple,而谓词要靠该 triple 求值——构造性循环)与策略标量(需 last-wins,是另一场设计);generatedFiles因为是写磁盘的副作用动作而非输入、featureDefines因为是沿 Public 边传播的接口贡献而非私有构建输入,各自按范畴排除。BuildConfig以继承方式承载(该字段在 ~150 处被读,嵌套只会是 150 次无收益的机械改动),ConditionalConfig则嵌套——保证需要落在那里。合并收敛为单一append(BuildInputs&, const BuildInputs&)。- #256 clang 模块运算符模板 canary + 文档:mcpp 侧无缺陷(
--precompile成功,崩溃在 clang 前端的导入侧),但 mcpp 自带 LLVM,且该 hazard 正落在本项目推荐的模块包模式上。文档给出可操作规则(导出的运算符模板,所有模板参数应由第一个实参绑定;否则改为对整个推导操作数类型建模)与可直接套用的改写范式。e2e 150 是状态 canary 而非通过/失败契约:期望表记录 LLVM 20-22 崩、18-19 正常,一旦现实与期望不符即失败——未来某次 clang bump 修好它与再弄坏它同样是需要被看见的信号。
- 新增不变量写入批次总账:任何"因条件不满足而少做一步"的分支,必须要么返回错误,要么经
diag::degraded上报;log::debug/log::verbose不算用户可见;丢弃std::expected返回值视为缺陷。 - #254 的覆盖是单测级:per-OS 段按 target 选段、host==target 逐字节不变、轴类型互不可转,三条单测锁定;原计划的"cross 腿消费带 per-OS 段的 xpkg dep"e2e 未落地(需要 e2e 侧先有"复用宿主 registry + 本地索引 + 预置 payload"的夹具,临时 MCPP_HOME 会触发整套交叉工具链重装),记为已知缺口。
- e2e 新增 148(64 个 include dir 的宽 include 表,POSIX 验内联形态、windows 验 response file)、149(条件段 per-glob flags 四象限:命中生效/覆盖 base 的 removal/非命中零告警/真死 glob 仍告警)、150(#256 canary);118 去掉
# requires: gcc并加 clang 腿。 - 未包含:#259(根因在 xlings 侧的 dep 静默丢弃,调查结论已发 issue 评论;mcpp 侧另发现 sysroot 兜底是死代码——
resolve_xpkg_path("xim:glibc")缺版本必然失败且两处都丢弃返回值——一并另行安排);manifest 未知键策略五处不一致(#263,有兼容性面,与 #258 无依赖)。
#253:feature 模型两缺口收口——per-feature per-glob
flags+ per-OSfeatures语义锁定,解锁 compat.opencvdnnoff-linux 腿并消除 feature-off 构建的死 glob 告警。设计见.agents/docs/2026-07-20-issue-253-feature-flags-and-per-os-features-design.md。
features.<name>.flags(#253):feature 级 per-glob 编译旗标,与[build].flags共用同一 entry 文法(glob必填 +cflags/cxxflags/asmflags/defines),xpkg 与 mcpp.toml 双文法一数据模型(共享解析 helper)。激活时折入既有 globFlags 单一漏斗(scanner 匹配/per-TU 落旗标/死 glob 告警/fingerprint 四个下游零改动),追加在 base 规则之后、feature 按名序,"last flag wins" 使 feature 规则可覆盖 base;折入点在includeDevDeps门外(0.0.94 双路径不变量)。私有 per-TU、不传播(与接口开关defines的语义分界)。feature off 时规则不存在 → opencv mlas 一类"结构性必死 glob"的告警自然消失;feature on 而 glob 空仍告警,且文案点名归属 feature(features.<f>.flags glob '...' matched no source file)。TOML 侧[[features.<name>.flags]]AOT 拼写同步进 #227 封闭文法 allowlist(features.*.flags通配段)。- per-OS
features语义锁定(#253):mcpp.<os>段的文本拼接 additive overlay 本就覆盖features键——同名 feature 逐子键 append(中性段在前、OS 段在后),per-OS 段可注册 OS-only feature;现以单测(逐 OSosOverride)+ e2e 锁死并写入文档,配合features.<f>.flags构成 opencvdnn的 common/delta 形态(中性段放跨平台公共载荷,per-OS 段放 mlas x86 / NEON 差集与其旗标)。
- 依赖包 per-glob flags 此前不入 per-package fingerprint:
canonical_package_build_metadata只序列化 cflags/cxxflags/ldflags/genfiles,globFlags 靠"descriptor 随版本冻结"间接成立;feature 折入使该向量随构建变化,现与根侧同款全量有序序列化(globflags:/gc:/gxx:/gas:/gd:)。
- e2e 新增 146(feature flags 四象限 + 死 glob 告警消除/点名 + 私有不传播对照)/147(per-OS features 端到端,宿主段生效、非宿主段投毒不可见);单测新增 xpkg/TOML 解析与 per-OS 合并锁定。host/target 轴缺陷(per-OS 拼接键=宿主常量,交叉编译选段错误)另开 issue 跟踪,不入本版。
大型源码直编包(ffmpeg/opencv 级,数千 TU)全平台化批次:#247/#248/#249 平台三修 + 增量构建修复 + build.mcpp 指令面补全(P1)。设计见
.agents/docs/2026-07-19-large-source-pkg-platform-fixes-and-buildmcpp-generation-design.md。
- windows driver-style 链接命令行溢出(#247):gnu 方言(g++/clang++ 作链接驱动——windows 托管 clang-MSVC 即此)的
cxx_link/cxx_archive/cxx_shared此前内联$in,数千对象直接溢出 CreateProcess 32 KiB 上限。现 windows 上 driver-style 也走 response file;两方言分支收敛为单一link_rule发射器(msvc 规则文本逐字节不变),POSIX 保持内联零变化。配套:ninja 节点名统一generic_string()正斜杠——rsp 内容按 GNU 文法分词,反斜杠是转义符(obj\cli.o会被吃成objcli.o)。 - macOS dep/root build.mcpp 收不到 G3 契约环境(#248,launcher-unify):非 Linux 的
capture_exec走 shell 字符串拼接,ENV… cd <cwd> && bin里 env 只绑给cd(全仓唯一 env+cwd 双非空调用点恰是 build.mcpp)。现 macOS 与 Linux 同走 posix_spawn 直启路径(child-only env +addchdir_np,environ经_NSGetEnviron()portable-correct),顺带消掉 macOS 的 shell quoting/注入面。windows shell 回退补cd /d(跨盘符)。 - generated_files 每次构建无条件重写毁增量:materialize 此前不比内容直接写,mtime 抖动令 ninja 把 include 该头的全部 TU 判脏——冻结快照包(config.h × 数千 TU)每次 build 都全量重编。现内容逐字节相同即跳写(变更检测本就由指纹负责)。
- xpkg feature 表未知子键(如
features.X.include_dirs)此前静默吞掉,现进xpkgUnknownKeys走统一告警面。
[build] include_dirs_after→-idirafter(#249):排在工具链系统目录之后搜索的 include 目录(descriptor/xpkg 与 mcpp.toml 双文法、*tarball 根 glob、沿 Public/Interface 边传播且不升级为-I)。治大小写不敏感 macOS 上依赖源根VERSION顶替 libc++<version>一类系统头遮蔽;-isystem不解此症(仍先于默认系统目录)。方言降级单点收敛:cl.exe → 末尾/I,NASM 单元 → 普通-I(NASM 会把-idirafter<p>误吞成-i dirafter<p>)。附带统一主工程 include 路径的 glob 展开(此前与 dep 路径两套推导)。- build.mcpp 指令面补全(P1,0.0.100+):
mcpp:source=(选既有 payload 源入编译集,generated=的"绝对路径灰色用法"正名)、mcpp:include-dir=/mcpp:include-dir-after=(私有 include,Cargo 纪律不进公共接口;typed 通道享方言降级);typedimport mcpp;同步source()/include_dir()/include_dir_after()。契约环境拆分MCPP_TARGET_OS/ARCH/ENV(免手撕三元组)。root build.mcpp 后移至依赖解析之后,与 dep 一样拿到MCPP_DEP_<NAME>_DIR;指令落通道收敛为 root/dep 共享的单一 fold(防"同一决策两处推导")。顺修:rootbuild.mcpp变更此前被整库 fast-path 吞掉不触发重跑。 mcpp xpkg parse --all-os:按 xpm 声明的平台集逐 OS 校验 per-OS 段(构建路径只 splice 宿主段,windows 段的 typo 在 linux CI 上原本不可见);mcpp-index CI 可单 runner lint 多平台描述符。
- e2e 新增 141–145(-idirafter 语义/增量跳写/source= /include-dir/root dep-dirs);linux 全量 e2e 134 过(3 项为既知环境性失败)。macOS/windows 断面由平台 CI 与 mcpp-index spike 复现件(PR#89/90/91)收口。
#230–#243 批次收尾:#243 feature 转发落地(0.0.98 只出了设计)+ #238 根因修复随 xlings 0.4.67 vendored 入包 + #230 windows build.mcpp 次生面补齐。设计见
.agents/docs/2026-07-19-v0.0.99-feature-forwarding-238-230-design.md。
- feature 依赖 feature 转发
dep/feat(#243,Cargo 平价):[features]里含/的 token(或表格形专用forward = ["dep/feat"]键)表示"本包该 feature 激活时,顺带打开依赖dep的featfeature"——一个 feature 既能本地拉源、又能开依赖的重档 feature,解阻塞模块包的可选模块接口(opencv-m 的import opencv.dnn;:dnn一档同时拉dnn.cppm源集并把compat.opencv以features=["dnn"]参与构建,而非对所有消费者全量 +309 TU)。收敛为一数据模型featureForwards两文法(TOML 与 xpkg 描述符共享唯一切分点split_feature_forward_token);转发注入 0.0.98 既有的aggregatedRequest依赖边漏斗(在子依赖 push 进 worklist 前把转发 feature 并入其请求集),一处注入同时覆盖解析(mergeActiveFeatureDeps拉被转发 feature 的条件依赖)与激活(边图 union →apply()发宏/源集)两个消费点;沿 BFS 前向边天然传递(root→mid→leaf)。与 #242default-features = false加性组合(转发进显式请求集、不受默认门控影响),mcpp build/mcpp test双路径一致。转发到未声明依赖 strict 报错 / 非 strict 告警;转发未声明的依赖 feature 复用既有 "does not declare requested feature" 门。单测 3 例、e2e 128(含双路径)。
- ≥2 项目级 index_repo 时
install_packages静默失败(#238,根因修复上游落地):vendored xlings 由 0.4.62 升至 0.4.67,携 openxlings/xlings#374 的多仓安装修复(fix(xim): surface multi-repo install failures + best-effort catalog,commitcf9b60d5)。此前 workspace 根级[indices]继承(#224)× default 重定向(R6)组合会给每个成员播下 ≥2 个index_repos,任一未缓存包安装裸exit 1无 error 事件;0.0.98 已在 mcpp 侧把它变成可操作诊断,0.0.99 随 bundle 带上真正的解析修复。发布/交叉构建/e2e 三处 workflow pin 同步 0.4.67。 - windows 下依赖/成员
build.mcpp产物名缺.exe无法执行(#230 次生面):build.mcpp编出的宿主程序此前恒名build.mcpp.bin;windows 的capture_exec走 cmd.exe,.bin不在 PATHEXT 故无法按名启动 PE。现按平台取后缀(windows=build.mcpp.exe,其余保持.bin,is_windows为 constexpr,非 windows 字节不变)。此面在 0.0.96 的 scanner symlink-逃逸崩溃(df985df,裸 127 的真凶)修复后才会被 workspace 的 build-mcpp 成员在 windows 走到。
- #230 主因(scanner glob 顺
.mcpp/.xlingssymlink 逃逸进 vendored 索引、CJK 文件名触发 MSVC 窄串转换抛异常→__fastfail→裸 127)已于 0.0.96 根治并在 0.0.98/0.0.99 在库;src/main.cpp顶层 catch 兜底(未捕获异常→exit 70,不再裸 127)。0.0.99 补齐 build.mcpp 次生面后,mcpp-index 的 workspace(windows)CI 从临时钉回的 0.0.94 升到 0.0.99 复验全绿即关闭。
#230–#243 批次(单 PR 统一发布,逐 commit):#233 对象路径消歧的两个后续缺口(#239/#240,解阻塞 mcpplibs #79 opencv 收录)+ #237/#241/#242 根因级实现 + #238 mcpp 侧诊断(根因在 openxlings/xlings#374)+ #243 设计。总账 + 架构评估见
.agents/docs/2026-07-19-issues-230-243-batch-ledger-and-architecture-assessment.md;各设计文档见.agents/docs/2026-07-19-*。
MCPP_DEP_<NAME>_DIRbuild.mcpp 契约(#241):包的build.mcpp现可经mcpp::dep_dir("<name>")拿到依赖的安装目录(verdir/payload 根),不必再逆向 store 布局(实例:compat.opencv 的unifontfeature 读数据资产包 compat.opencv-unifont 的字体 blob)。用权威的 consumer→dep 边图注入,覆盖 feature 激活的依赖;canonical + short 双发(dep_dir("compat.zlib")/dep_dir("zlib")皆可)、同款 sanitize、碰撞守卫、自动进 rerun hash。作用域为依赖侧 build.mcpp(root 工程 build.mcpp 早于依赖解析运行,列为后续项)。- 消费端
default-features = false(#242):依赖 spec 支持关闭该依赖的默认 feature 集({ default-features = false, features = ["x"] }),Cargo 平价。根因收敛在feature_closure的单一seedDefault门:关闭时不 seed 该依赖[features].default,仅显式请求 +implies激活;root 包/工程 build.mcpp 仍默认 seed。(挡住 compat.ffmpeg 裁剪档形态。)
- 消歧后链接输入未跟随改名,依赖与消费者同名源即挂(#240):当依赖包与消费者存在同名源(近乎必现——双方都有
src/main.cpp,如 OpenCV 自带的 samplemain.cpp× 消费者的入口)时,#233 已把被扫描的消费者main编到obj/<pkg>/src/main.o,但链接步骤仍引用消歧前的扁平obj/main.o→ninja: error: 'obj/main.o' … missing and no known rule。现将对象路径分配收敛为单一来源:被 glob 进来的入口复用其编译边已消歧的对象;未被 glob 的入口也纳入同一碰撞普查后消歧——链接输入与编译边永不背离。常见单二进制工程(main唯一)仍为扁平obj/main.o,字节不变。 - 绝对/越根路径源的消歧对象逃逸出
obj/(#239):依赖build.mcpp写进 OUT_DIR(target/.build-mcpp/deps/<name>@<ver>/out/,在包根之外)的生成源,其relPath携带..,#233 曾原样拼进obj/<pkg>/../…致对象路径爬出构建树(甚至在 CWD 镜像整棵绝对路径树);且路径里的@会被 ninja 单引号包裹,连带压垮 #235 的"$out.d"depfile 重定向。现消歧前缀逐分量净化:去绝对根、.丢弃、..→__up、非可移植字符(如@)→_——对象永远向下且 shell 安全;逐分量单射,保住 #233 的唯一性(L1b 断言兜底残余)。 - xpkg 描述符 mcpp 段未知键 build 时静默忽略(#237):
dependencies误写(正确键deps)等未知键此前只有mcpp xpkg parse报,build 路径静默丢弃致依赖消失无诊断。现描述符被采纳为依赖时按键响亮告警并给 did-you-mean(封闭词表别名 + Levenshtein 回退);沿用 0.0.97 封闭文法先例,告警而非硬错以保前向兼容。 - feature 请求集收敛到依赖边图(#242 传递边 + #241 命名;架构评估头号优化):此前 feature 请求集在解析(
mergeActiveFeatureDeps,读 per-edge spec)与激活(apply(),只扫 root 直接依赖)两处独立推导且对传递边不自洽——传递依赖的请求 feature 与其消费者的default-features = false被静默丢弃(激活仍 seed 该依赖默认 feature,定义其宏/保留默认门控源,而解析已跳过)。现DependencyEdge携带 per-edgerequestedFeatures + defaultFeatures,新aggregatedRequest对某依赖包所有入边做 union/OR(Cargo 菱形语义),激活与依赖 build.mcpp 共享之;直接依赖行为不变,顺带补掉长期的传递 feature 未传播缺口。e2e 127。 - 多 index repo 下
install_packages失败无诊断(#238,根因在 xlings):裸fetch failed (exit 1)现重建为可操作诊断——点名目标、已配置 index repos 清单、≥2仓的已知 xlings 解析缺口提示、保留子进程输出、MCPP_VERBOSE=1看原始调用。仅诊断改进;多仓解析的根因修复须落在 openxlings/xlings(已开 openxlings/xlings#374)。
- #243 feature 依赖转发(
dep/feat):核实条件依赖半边已存在([feature-deps]/xpkgfeatures.x.deps),真正缺口是转发;设计见.agents/docs/2026-07-19-issue-243-feature-forwarding-design.md,收敛为单一 per-package 请求 feature 漏斗(顺带补prepare.cppm:2653传递性缺口),与 #242 opt-out 加性组合。
- #230(windows workspace exit 127)已于 0.0.96 修复,待 mcpp-index windows CI pin ≥0.0.98 复验后关闭。
架构级修复批次(单 PR,逐簇 commit):ffmpeg-m/opencv-m 全源码直编暴露的第二层缺口 + workspace 测试基建语法空洞。
[[build.flags]]数组表写法(#227):[build].flags现同时接受 TOML 标准数组表[[build.flags]]与内联表数组两种等价写法(声明顺序=应用顺序不变),长 per-glob flags 条目不再挤单行。纯解析层扩展(自研 TOML parser 补全 array-of-tables);并加清单层 closed-grammar 守卫——非白名单段落误写[[x]](如[[dependencies]]手滑)现硬报错而非静默丢数据。- glob 花括号交替
{a,b}(#228):sources/flags glob 支持libavcodec/{aac,bsf,hevc}/**笛卡尔展开(嵌套/多组均可),vendored 大库 per-glob 声明不再重复。 - 默认命名空间索引重定向(R6):
[indices] default = { path = "..." }(亦接受空引号键"")可把默认命名空间(namespace = ""的模块包)指向本地 checkout,补上此前只能重定向具名命名空间的语法空洞——使 index 仓能以mcpp test --workspace声明式验证 imgui/ffmpeg/opencv 等模块包,替代逐包 smoke shell。(url形式的默认命名空间重定向暂不支持,解析期显式报错而非静默失效。) mcpp run -p <member>:run 命令支持选择 workspace 成员并运行其二进制(与build/test的-p对齐)。
- 源发现全树遍历致
mcpp run前慢 + 缓存复用(#225):glob 遍历改从字面前缀起(src/**从src/起走,不再从项目根扫全树),并排除.git/target/git 子模块边界;mcpp run复用mcpp build已解析的缓存,不再每次重扫大子模块(此前含大compat/子模块时每次 ~9.5s)。 - 相对 include 族 flag 未按项目根重写 + 含空格值未转义(#226 #234):
-iquote/-isystem/-idirafter/-iprefix/-L现与-I一样按项目根绝对化(joined 与 separated 两拼写);defines = ["T=long long"]等含空格值发射时 shell 引号保护,不再被拆成孤立参数。含[build] include_dirs在 MSVC 方言下的相对路径绝对化亦一并修正。 - 对象路径按父目录名折叠致同名源冲突(#233):不同目录同名源(
a/src/util.cppvsb/src/util.cpp,OpenCV/LLVM 式modules/<mod>/src/*布局)对象路径改按碰撞时镜像源相对路径消歧 + 构建后唯一性断言;非碰撞项路径字节不变。 - 模块 purview 内文本
#include改动不触发重编(#235):编译边补 depfile 追踪(过滤 GCC-fmodules注入的反向规则以规避 ninjainputs may not also have inputs),purview/GMF/普通头改动均正确触发重编——顺带根治此前非 MSVC 下普通头改动也不重编的潜伏问题。 - 依赖包 cfg 条件 sources 被消费时不展开(#229):path/git 依赖的
[target.'cfg(...)'.build].sources现与 root/version-dep 同样按已解析 target 求值(mcpp build与mcpp test双路径),不再 undefined reference(与 #218 同类,收敛为统一 per-package 求值)。 - nasm 冷环境惰性自举时序(#232):nasm 供给改走工具链同款同步门
Fetcher::resolve_xpkg_path("xim:nasm@3.02", autoInstall=true)(索引刷新前置 + 硬报错 + payload 校验),不再先判死后台补装;config 自举错误不再被if(cfg)门吞成误导性的 "no usable nasm"。 - workspace 根配置无法一次声明全员可用(#224):
[workspace.dependencies]的 path 依赖可被成员经.workspace = true继承;根[indices]的相对path按 workspace 根解析(而非消费成员目录),成员不再需重复声明各自../相对路径。
- #230(windows workspace 崩溃)已于 0.0.96 修复。#215(cppfly Clang 反射行)待上游 Clang 落地 P2996,不排期。
- [windows]
mcpp test --workspace静默崩溃(裸 exit 127,实为 0xC0000409) (#230,修复 #231)。三层根因:- 0.0.95 扫描器的 glob walk 新增
follow_directory_symlink,而项目本地.mcpp/.xlings/data/<index>是指回索引根的符号链接 → walk 逃逸出成员 目录、扫遍整个索引 checkout(CI 里含 vendored xim-pkgindex); path_matches_glob用generic_string()拼窄串,MSVC 在非 CJK ANSI 代码页 (runner ACP=1252)下遇到中文文件名bug-report---问题反馈.md抛std::system_error;- 异常逃出
main未捕获 →std::terminate→__fastfail(0xC0000409), git-bash 显示为无任何输出的 exit 127。
- 修复:
expand_glob/expand_dir_glob按名剪枝.mcpp目录(mcpp 自身 元数据目录永远不是源码目录,从源头切断符号链接逃逸,顺带避免每个成员把 整个索引树白走一遍);path_matches_glob对无法窄化的文件名按"不匹配" 跳过而不是摧毁构建;main()增加最后防线 catch,逃逸异常打印真实错误并 以 70 退出,不再静默 fastfail。 - 取证:runner 开 WER 全内存 dump,崩溃栈+被转换字符串逐帧还原(记录见
mcpplibs/mcpp-index
debug/mcpp230-windows-repro分支及 #230)。 - 回归测试:
tests/e2e/113_scanner_mcpp_dir_prune.sh(.mcpp符号链接逃逸 必须被剪枝;CJK 文件名不得致命)。linux 行为对照:0.0.95 会顺着该符号链接 把项目外源码编进来(链接期 duplicatemain),修复后构建干净。
- 0.0.95 扫描器的 glob walk 新增
- 见 GitHub Release v0.0.95:声明式清单能力(features.sources / generated_files /
cfg 条件 sources / per-glob flags,#223)、汇编源一等公民(.S/.s/.asm 进
sources,NASM 按目标推导
-f,#220)、build.mcpp 补全(环境契约 + cross 下 运行 + 依赖包执行,#222)。 已知问题:#230(本版 windows workspace 崩溃,0.0.96 修复)、#232(冷环境xim:nasm自举产出空载荷,待修)。
- 依赖包被激活 feature 的
sources在mcpp test下不编译。prepare_build()把 feature 源集解析(drop + add)整段门在!includeDevDeps,而mcpp test走includeDevDeps = true→ 激活 feature 的 sources 从不被加回构建图。 descriptor 若把某个 glob 只写在features下(xpkg 的features.X.sources只落进featureSources、从不进 basesources),该包在mcpp build下正常、 在mcpp test下必然链接失败(undefined reference)。- 命中面:
compat.cjson的utils(cJSONUtils_*)、compat.eigen的eigen_blas(dgemm_)、compat.spdlog的compiled。 eigen_blas的dgemm_一直被记为「把 feature 编出的依赖目标链进 test 二进制是 follow-up」——定性是错的:不是链接问题,是源集解析问题。- 修复:drop 仍只在 build 模式做(
mcpp test需要保留完整源面,让 dev-dep 轨的 per-test main 检测看得见gtest_main.cc并逐 test 剪枝——见tests/e2e/79_gtest_regular_dep_feature_main.sh);add 改为两模式都做, 并去重,使 gtest 那种 base/feature 双列的 glob 不会进两次。 - 回归测试:
tests/e2e/100_feature_sources_test_mode.sh(cjsonutils在mcpp test下必须编译并链接;mcpp build仍正常;不请求 feature 时 gated 源仍被排除)。
- 命中面:
- 工具链 × 目标 命名统一 —— 二轴身份模型。toolchain =
family@version(family 只剩 gcc | llvm | msvc),target = mcpp 自有三段 triplearch-os[-env](Zig 式砍 vendor)。变体(gnu/musl/msvc)进 triple env 段, "cross"/"musl"/"mingw" 不再是工具链名字——mingw-cross 16.1.0的本体是gcc@16.1.0 → x86_64-windows-gnu,交叉只是 host≠target 的关系。业界对照 (rustup 零 "cross" 命名/Zig 三段 triple/musl.cc 分发层先例)与决策记录见.agents/docs/2026-07-15-toolchain-target-naming-unification-design.md。- canonical triple:
x86_64-windows-gnu为正典(D1);GNU 拼写x86_64-w64-mingw32及 4 段 Rust 拼写为永久别名,归一后进同一target/<canonical>/目录(同一构建缓存)。macOS 产物目录随 canonical 变为aarch64-macos。 --target封闭词汇表校验:打错字硬错 + did-you-mean (did you mean 'x86_64-linux-musl'?),不再静默 fall through 编成宿主产物 (最坏失败模式根治);自定义 triple 走显式[target.X]节逃生舱;planned 档位(riscv64 等)报「registered but not yet supported」。两条硬编码约定 (*-musl、x86_64-w64-mingw32)改为词汇表数据行(pin + 默认 static)。- 单一 triple 解析器
triple.cppm:cfgpred/abi/model 谓词/registry 四处 平行解析收敛;abi 的 os 维darwin→macos(与 cfg 词汇分叉消灭,darwin/arm64作为约束别名接受)。 compat.cppm兼容层:唯一知道旧拼写的文件(musl-gcc/gcc@V-musl/ -gcc/mingw/mingw-cross/clang),归一 + 单行note:提示;xim 分发包名 (mingw-cross-gcc等)不动——"cross" 在分发层合法(musl.cc/Debian 先例)。- CLI 单名词 +
--target选项(D4,不设mcpp target子命令):toolchain install [gcc 16] --target <triple>(family 可省→约定 pin)、toolchain default gcc@16 --target <triple>(默认变 pair,持久化default+default_target两键)、toolchain remove … --target <triple>; 主路径仍是mcpp build --target <triple>自动装链(零仪式)。 [build] target = "<triple>"新 manifest 键(≙ cargobuild.target): 「默认全静态 musl」的正确归宿(产物属性,非编译器家族属性);优先级--targetflag >[build] target> 全局default_target> host。toolchain list两轴重排:Toolchains 块(family@version)+ Targets 块 (target × 状态 installed/available/planned,planned 行使词汇表用户可见); 修版本字典序排序 bug(9.4.0 不再排在 15.1.0 前);gcc X-musl行不再被llvm劈开。README 平台表从词汇表重画(target × tier 维度,补 MSVC=✅ 与 windows-gnu 行——旧表 MSVC 仍标 planned 是错的)。- 修 Windows host 上
mingw的门:Linux 上toolchain install mingw现在 合法(= 装交叉 payload,同一身份 host 分流);mcpp run位置参数 help 改为 「Binary name」消除与--target的语义撞名。 - 验证:单测 35(新增 triple/compat 套件);e2e 新增 103(typo/planned/逃生舱/
[build] target/别名同目录)、102 双拼写断言;本机实测双拼写同 Resolved 行+同缓存(alias 二跑 0.07s 全命中)、PE wine 真跑、musl 静态链、typo did-you-mean。
- canonical triple:
- Linux → Windows MinGW-w64 交叉工具链(
mingw-cross)—— host≠target 一等公民。 在 Linux 主机上交叉编译出 Windows x86_64 PE,含import std。补 0.0.89msvc-mingw-design§4.4/§7 登记的延期项(交叉维:host≠target)。- 工具链:从源码构建的 GCC 16.1.0 mingw-w64 MSVCRT 交叉链(triple
x86_64-w64-mingw32,跟随 Rust Tier-1x86_64-pc-windows-gnu的 CRT 选型); 自包含(自带 binutils/CRT/libstdc++ 含bits/std.cc+libstdc++exp); 发布于xlings-res/mingw-cross-gcc(GitHub+GitCode)+xim-pkgindex。 - mcpp 侧:用户名
mingw-cross→ ximmingw-cross-gcc(前端x86_64-w64-mingw32-g++);解除 MinGW 的 Windows-host 门(Linux 主机可装,nativemingw仍 Windows-only);--target x86_64-w64-mingw32约定解析(默认 static); 跳过 glibc/linux-headers(PE 自带 CRT)。 - host≠target 化:PE 产物形态判断一律按 target 而非 host constexpr——std 模块源
探测补
<prefix>/<triple>/include/c++/子目录;自包含工具链跳过外部 binutils-B(musl + mingw);MinGW link 分支(-static/-lstdc++exp)由if constexpr(is_windows)host 门提为运行期is_mingw_targettarget 判定。 - 验证:e2e
102_mingw_cross_wine.sh(# requires: mingw-cross wine,build --target → PE 静态自包含断言 → wine 跑 import std + 多模块);CIcross-build-test.yml加mingw-cross-winejob(OS-cross,wine)。全链实测闭环(install→build→wine)。 - 设计:
.agents/docs/2026-07-15-mingw-linux-cross-windows-design.md。
- 工具链:从源码构建的 GCC 16.1.0 mingw-w64 MSVCRT 交叉链(triple
standard = "c++fly"— 一行启用"最新标准 + 全部实验特性"(语言 + 标准库)。 语义三件套,全部按 resolved 工具链自动判定:①族最新-std=档位(GCC16→c++26、 Clang→c++2c、MSVC→/std:c++latest);②该工具链支持的全部实验性语言特性门 (GCC≥16:反射-freflection;契约随-std=c++26默认启用,实机探测定案); ③标准库实验门(libc++→-fexperimental-library)。不支持的特性软跳过并打印 summary(c++fly on <toolchain>: <std>; enabled: ...; skipped: ...)。 新模块src/toolchain/cppfly.cppm承载三张数据表(族×版本×stdlib)——首个真正 使用Toolchain::version做门控的查询点;产物汇入 0.0.90 的图全局方言旗标通道 (全图 TU + P1689 扫描 + std BMI 预构建同源),派生旗标并入指纹。 设计:.agents/docs/2026-07-14-std-features-experimental-gate-design.md。 e2e 100(gcc16 硬路径:零手写旗标跑通 std::meta 反射)/ 101(clang 软路径: c++2c + skipped summary)。
standard = "c++latest"在 GNU 族误拼-std=c++latest(GCC/Clang 不识别, 构建必败):canonical 现经 cppfly 的"族最新档"表解析为真实档位(GCC16→-std=c++26)。e2e 100 尾段回归覆盖。
- MSVC 原生构建后端(cl.exe)落地,0.0.88 的构建门移除。选定
msvc@system后mcpp build/run直接用系统 MSVC 编译链接:- 环境模型:
find_windows_sdk()+ 从检测到的 VC tools/SDK 直接合成 INCLUDE/LIB/PATH(+VSLANG=1033),不跑 vcvarsall;SDK 缺失时检测/选择仍可用, 构建报带指引的明确错误;doctor 新增 SDK 与 mingw 检查行。 - 模块管线:std/std.compat.ixx 单命令 staging(
/ifcOutput→ ifc.cache), 命名模块.cppm经/interface /TP编译、/ifcSearchDir消费;/scanDependencies作为第三个编译器内建 P1689 扫描驱动接入 dyndep。 - 链接:link.exe/lib.exe(SeparateLinker)+ 响应文件(绕 cmd 8191 限制),
DLL=
/DLL /IMPLIB:;deps=msvc(/showIncludes)头文件依赖;/MD|/MTCRT 随 linkage;/std:c++20|c++latest映射;.obj扩展名全链路。 - fast-path 增量:构建缓存 env 槽新增
@env多变量编码,增量构建重建 INCLUDE/LIB 环境。e2e 99(模块/import std/增量)+ 95 改造为真实构建断言。
- 环境模型:
[build] dialect_cxxflags+ 方言旗标全图化(issue #210 修复)。-freflection等"改变标准库头声明集"的 flag 现随-std=的通道到达: 全局 cxxflags(项目+依赖所有 TU)、std/std.compat BMI 预构建命令、P1689 扫描。 known-list 自动提升(reflection/contracts/char8_t/_GLIBCXX_USE_CXX11_ABI)+ 显式dialect_cxxflags逃生舱;指纹早已包含这些 flag,修的是命令构造。 实证:#210 的最小复现(gcc16 +import std;+std::meta)输出x 2/y 3; e2e 98 含依赖模块变体。
- mingw 在非 Windows 主机的
toolchain install/default现在明确报 windows-only(此前是invalid xpkg target 'xim:mingw-gcc@')。 - std 模块 staging 命令在 Windows 用
cd /d(跨盘;工作区 D: + 缓存 C: 的 真实 CI 布局)。 - release 的 publish-ecosystem:镜像脚本改为批量上传+带耐心的 ranged-GET 验证、 验证超时不再删除资产(0.0.89 因逐资产"18s 即删重传"+全量 GET 探测触顶 20min 被杀);timeout 兜底 20→30。
- stdmod 执行层支持工具链声明环境(capture_with_env);shell 引用平台化。
- MinGW-w64 工具链入 xlings 生态(Windows 原生 GCC,无需 Visual Studio)。
mcpp toolchain install mingw 16.1.0/default mingw@16.1.0:xim 包mingw-gcc(winlibs GCC 16.1.0 + MinGW-w64 14.0.0 UCRT 独立构建,镜像于 xlings-res/mingw-gcc,GitHub+GitCode 双端)。复用既有 GCC 后端 (gcm 模块管线、libstdc++bits/std.cc的import std);Windows 上 libstdc++/libgcc 默认静态链接(产物免带 DLL,[build] static_stdlib=false可关),linkage="static"升级全静态。e2e 97 覆盖 install→default→ 多模块 build/run→独立 exe 验证;ci-windows 新增专项步骤。 连带修复:toolchain list的 Available 段与部分版本解析此前硬编码按 "linux" 平台读取 xpkg 版本(Windows/macOS 上恒空);toolchain install在 Windows/macOS 不再错误安装 glibc/linux-headers 依赖。
- 工具链后端抽象层(Part A,对 GCC/Clang 零行为变化,build.ninja 零 diff
实证)。新增
mcpp.toolchain.dialect(命令行拼写 traits:gnu/msvc 两行 数据,-I/-D/-std=/-c/-o/-O/-g/ar及归档命令模板经其发射);BmiTraits并入模块旗标拼写(compileModulesFlag/stdBmiUsePrefix/moduleOutputPrefix/bmiSearchPrefix),flags.cppm 的 is_clang 分支改由 数据驱动;ProviderCapabilities.has_builtin_p1689_scan取代 ninja 后端的 is_gcc 门;Toolchain::envOverrides(EnvVar 表,注入 ninja 子进程环境, 为 MSVC 后端 INCLUDE/LIB/PATH 预留);gcc provider 增加与 clang 同形的std_module_build_commands(命令序列);resolve_link_model在 Windows 显式返回 PE 空模型。设计:.agents/docs/2026-07-13-toolchain-backend- abstraction-msvc-mingw-design.md+ 同日触点审计文档。
- MSVC 系统工具链支持(
msvc@system,detection-first)。MSVC 作为首个 "系统工具链"接入:mcpp 负责定位与识别(vswhere →VSINSTALLDIR/VS*COMNTOOLS→ 标准安装路径三级发现;cl.exe banner 解析出编译器版本/架构, 容错本地化 banner),从不安装/卸载 MSVC 本体。mcpp toolchain default msvc:检测系统 MSVC,打印 VS 产品/VC tools/ cl 版本与std.ixx(import std)可用性,持久化稳定 specmsvc@system(不落具体版本,VS 升级后配置依然有效);未安装时输出安装指引 (VS Installer C++ 工作负载 /winget install …BuildTools)并退出非零。msvc@19.44形式为 pin 校验(仍取最新 VC tools,前缀不符则报错)。mcpp toolchain list:Windows 上新增System:段展示检测到的 MSVC;install msvc报告已装现状或给指引;remove msvc明确拒绝(系统组件)。mcpp self doctor:Windows 上新增 "msvc (system)" 检查段。- manifest 支持
[toolchain] windows = "msvc@system"(types.cppm 注释中的 既有 schema 首次落地);非 Windows 主机使用 msvc spec 时给出明确报错。 mcpp build:原生 cl.exe 构建(.ifc 管线)本版暂不支持,在工具链解析 后以单一 owned 错误信息拦截,并提示可用的llvm@20.1.7(MSVC-ABI Clang)。- detect() 新增 cl.exe 分类路径(文件名短路,banner → 版本/triple),
bmi_traits预置 MSVC.ifc分支;e2e 95/96 + 单测覆盖。 - 设计文档:
.agents/docs/2026-07-13-msvc-system-toolchain-detection-design.md。
- 项目本地模式:不再把官方全局索引
xim注入项目作用域。此前带自定义[indices](本地 path 索引)的工程进入项目本地模式时,ensure_project_index_dir会无条件把官方xim索引 append 进项目.xlings.json的index_repos(config.cppm),意在让xim:*依赖在项目模式可解析。但 xlings 按"repo 落在 哪一组"决定作用域——项目index_repos里的包一律PackageScope::Project,于是xim的全局工具(cmake/glibc/gcc/make/binutils 等)整体被错误地"项目化"、 装进项目 store 而非共享 registry。由此 build-dep 工具(如xim:cmake)的 ELF interpreter 被指向项目 store 里未物化的 glibc →cannot execute,任何在install()里执行 glibc-动态 build-dep 工具的 compat 包(如从源码 CMake 构建的 OpenCV)在mcpp test下必现,且与宿主历史无关(freshMCPP_HOME亦复现)。 修复:移除该注入及配套的项目 data dirxim副本暴露。xim(及其动态发现的 sub-index)是 xlings 全局默认索引,global 即默认作用域——xim:*经全局 index_repos + registry 本地 clone 正常解析、装 registry,并经 additive 对项目 可见;只有用户在[indices]声明的本地自定义索引才项目化。设计与分析见.agents/docs/2026-07-09-project-index-scope-global-infra-fix.md。
- clang 驱动配置文件(cfg)补全头文件搜索路径:
fixup_clang_cfg再生成的 cfg 此前仅包含链接相关条目(-B/-L/动态链接器/rpath),缺少 C 标准库头文件 与内核头文件的搜索路径。该 cfg 服务于直接调用打包内clang/clang++(不经由 mcpp)的场景:缺少这两项时,此类调用仅在宿主系统存在/usr/include时可编译(依赖宿主环境,违背沙箱自包含约束),在无宿主开发头 文件的环境中直接报头文件缺失错误。本次补充-isystem <glibc payload>/include与-isystem <linux-headers payload>/include, 置于 libc++ 头文件条目之后以保持#include_next搜索链;生成内容与 xim-pkgindex 侧llvm.lua安装期生成的 cfg 保持一致,消除两个生成端之间的 内容差异。fixup 修订号升级至hermetic-3,既有 payload 在下一次构建时自动 重新收敛,无需重新安装。验证方式:以--sysroot=<空目录>屏蔽宿主头文件后, 由 cfg 驱动的clang/clang++直接调用编译与运行均通过;移除上述搜索路径的 对照组按预期失败。mcpp 自身构建路径不受影响(构建 flags 由 linkmodel 独立提供,不读取 cfg)。
- Linux llvm 工具链链接失败
cannot open Scrt1.o/crti.o/crtn.o(#195):clang-with-cfg 的 payload 链接路径此前只带-L/-rpath/--dynamic-linker,缺少 CRT 启动对象的发现前缀-B<glibc payload lib>——driver 查找Scrt1.o/crti.o/crtn.o只走-B前缀与 sysroot 派生路径,不查-L。在装有宿主 libc6-dev 的机器上 driver 会静默兜底宿主/lib的 CRT (污染式"假绿"),在没有的机器(如全新 WSL2)上则把裸文件名传给 lld 直接失败。
- 工具链链接模型单一化(hermetic toolchain link model):新增
mcpp.toolchain.linkmodel作为「如何对该工具链的 C 库编译/链接」的唯一解析器(payload-first,--sysroot 回退),flags/stdmod/build_program/ cfg 再生全部消费同一模型,消除四份漂移实现; 动态链接器名按 声明式 payload 元数据 → 按 triple 的 arch 映射 → glob 三级解析,全链 不再硬编码ld-linux-x86-64.so.2(aarch64 glibc 的 loader 障碍随之消除)。详见.agents/docs/2026-07-07-hermetic-toolchain-link-model-design.md。 - post-install fixup 归位为统一管线:
ensure_post_install_fixup成为所有工具链安装 路径(显式 install / 默认工具链 auto-install / manifest[toolchain]auto-install)共享 的唯一 fixup 入口,内容指纹 marker 幂等;此前 manifest 路径不跑任何 fixup。clang cfg 由行级补丁改为从链接模型确定性再生(同一 payload 在任何机器/安装路径产出一致 cfg, 人类直接使用clang++同样获得 hermetic 的 CRT 发现)。 - hermetic 链接校验:构建前用
-###干跑断言 CRT 对象与生效 dynamic linker 全部解析 在沙箱(xpkgs registry)内,越界即报错并指明泄漏路径;逃生阀[build] allow_host_libs = true/MCPP_ALLOW_HOST_LIBS=1。按 flag 集缓存判定。 - 测试与 CI:新增 e2e
86_llvm_hermetic_link.sh(-###前缀断言,双向防「链接失败」 与「宿主污染」回归);llvm e2e 解除 20.1.7 硬 pin(MCPP_E2E_LLVM_VERSION,默认最新 已装 payload);ci-linux-e2e 新增 无宿主工具链容器 job(debian:stable-slim,无 gcc / 无宿主 CRT)——唯一能真实复现 #195 环境类的 CI 形态。
-
Feature 系统 v2 Stage 2a — 由 feature 激活的可选依赖:声明于
[feature-deps.<name>]段(或 Lua 描述符中 feature 的嵌套deps表)的依赖为可选依赖,仅当该 feature 处于激活 状态(根--features或依赖 spec 的features=[...])时才进入解析;声明于[dependencies]的 依赖始终解析。可选性由声明位置表达,无需额外的optional=true标志。实现上,prepare_build在为根包播种解析 worklist 之前、以及在每个依赖的 manifest 加载之后,将该 manifest 的活跃 feature-deps 合并进其dependencies映射,后续既有的 worklist BFS 与 Stage 3 能力绑定即自动 接管——一个backend-openblasfeature 可同时拉取 provider(compat.openblas,provides=["blas"])并开启消费开关(implies=["use_blas"],requires=["blas"]),图中单一 provider 时能力自动绑定。Lua 描述符的 featureimplies亦补齐解析(此前仅 TOML 支持)。详见.agents/docs/2026-06-29-feature-optional-dependencies-s2-design.md。实现注记:上述两个 helper(
activateFeatures/mergeActiveFeatureDeps)必须为 prepare_build 内的局部 lambda,而非文件作用域函数。若作为模块接口单元中的导出(inline)函数,其std::map实例化会泄入发射的 BMI,触发 GCC 16 modules 缺陷——另一导入std的翻译单元随即报fatal error: failed to load pendings for __normal_iterator。局部化可将实例化限制在实现单元内。
- 首次初始化在海外网络与 GitHub 托管 CI 上的冷启动失败(
index missing;patchelf / ninja bootstrap 失败):mcpp self env为新建的MCPP_HOME播种.xlings.json时,将mirror字段 硬编码为"CN"。xlings 的normalize_mirror_仅接受GLOBAL与CN两个合法取值,故"CN"被直接采用并解析至 gitcode,致使 xlings 内置的区域探测detect_install_mirror_()被跳过——该例程 经tinyhttps::probe_latency测量 github 与 gitcode 的连接延迟,择可达且更低延迟者。在美国区域的 runner 上,gitcode 不可达或显著较慢(实测 github 70 ms、gitcode 1060 ms),由此索引与沙箱的冷 bootstrap 失败。本版将播种值改为"auto":normalize_mirror_("auto")判定为非法取值,xlings 视其 为未设置并执行自身探测,在美国区域解析至 GLOBAL、在中国大陆解析至 CN。镜像选择的职责由此归还 xlings(其已基于 tinyhttps 实现该机制),mcpp 不再代为决策。播种仅在.xlings.json不存在时发生, 显式的mcpp self config --mirror CN|GLOBAL配置不会被覆盖。
MCPP_VERBOSE环境变量:取非空且非"0"的值时,为每一次 mcpp 调用启用 verbose 日志,涵盖 e2e 脚本中未携带 flag 的$MCPP调用,便于 CI 诊断。该变量与既有的MCPP_LOG_LEVEL(仅控制文件 日志级别)互补;显式--quiet仍具有更高优先级。该变量已在 fresh-install 等 workflow 中启用,但 不含运行「默认静默」输出断言的 e2e 套件。update_index冷启动重试:索引同步为网络 git 操作,单次瞬时故障原会直接导致冷启动失败。本版 改为有界退避重试(至多 3 次,退避 2 s / 4 s);成功路径于首次尝试即返回,稳态无额外延迟,仅失败 时方触发退避。
-
Feature 系统 v2 — feature 可贡献「包自有 defines」+ capability(provides/requires)能力绑定: 解决「
compat.eigen启用blas特性后,compile_commands.json里只有-DMCPP_FEATURE_BLAS、 没有上游真正读的-DEIGEN_USE_BLAS,特性形同未启用」这一类根因——旧版 feature 激活只能产出-DMCPP_FEATURE_<NAME>宏 + 门控源文件,无法表达任意宏、更无法做 backend 选择。本次按 「功能全覆盖 + 少即是多」收敛为两个原语(详见.agents/docs/2026-06-29-feature-capability-model-design.md):- Stage 1 — feature
defines:[features]条目可写成表形式name = { defines = ["EIGEN_USE_BLAS"], implies = [...] }(TOML 与 Lua 描述符两面均支持); 激活时每个裸名 define 脱糖为-D<x>加到该包编译标志,与自动的-DMCPP_FEATURE_<NAME>并存。按行业经验(vcpkg)刻意限制为「包自有命名空间宏」,feature 不注入自由cflags/ldflags,以保持 feature union 组合性。 - Stage 3 — capabilities:包/特性可
provides/requires一个抽象能力字符串(如blas), 解析器从依赖图中绑定唯一 provider——确定性:[capabilities]pin /--cap指定者胜出; 图中恰好一个 provider 自动绑定;零个或多个未指定均硬报错(绝不静默猜测)。 这把「静默用错/缺失后端」变成配置期显式报错。link/include 仍走既有依赖机制流动。
Stage 2(feature 触发的可选依赖自动拉取 + 全图 feature union 统一)作为下一阶段:它需要把 特性计算提前到依赖解析之前(解析阶段重排),风险更高,且 capability/Eigen 用例并不依赖它 (provider 以显式依赖声明)。本次先发坚实的 S1+S3,符合设计文档「各阶段独立可发」原则。
- Stage 1 — feature
- 带命名空间前缀的依赖解析失败
index entry not found in local clone(自定义 ns + 非规范文件名): 当一个包以「裸name+ 独立namespace字段」形态声明(如aimol.tensorvia-cpu:name="tensorvia-cpu"、namespace="aimol"),并以非规范文件名落盘在共享索引里 (pkgs/t/tensorvia-cpu.lua而非pkgs/a/aimol.tensorvia-cpu.lua)时,限定请求aimol.tensorvia-cpu报「索引条目缺失」,而裸名tensorvia-cpu却能解析。根因是 候选消歧selectDependencyCandidate用「规范文件名<ns>.<short>.lua是否存在」当身份 判据——描述符以非规范文件名落盘时,正确的 peer-root 候选(aimol, tensorvia-cpu)对消歧 器隐形,请求被钉死在错误的首选候选(mcpplibs.aimol, …)上并被身份门拒绝。修复:候选消歧 改为身份优先,经由加载路径同款的身份校验读取器(read_xpkg_lua*)按描述符声明的(ns, name)定位候选,文件名不再参与身份判定——选择层与加载层从此不可能对同一候选产生 分歧。详见.agents/docs/2026-06-26-identity-first-resolution-no-filename.md。
- LLVM 工具链产物运行期
libatomic.so.1: cannot open/ 真用原子时链接报undefined __atomic_*: 16 字节及超宽std::atomic会降级成__atomic_*外部调用,这些符号位于 libatomic (GCC 运行时库,LLVM 无对应物),而编译器驱动不会自动链接 libatomic。mcpp 现在在 Linux 链接行注入-Wl,--push-state,--as-needed -latomic -Wl,--pop-state:真正用到原子的 程序自动链上并保留依赖,未用到的程序经--as-needed自动丢弃、产物零额外依赖。注入是 自守卫的——仅当工具链链接目录里存在可解析的 libatomic(动态链接libatomic.so/.a, 静态链接libatomic.a)时才发出-latomic,因此对不附带 libatomic 的工具链零回归。 与之配套的 llvm 资源包需把 libatomic 打入lib/<triple>/(详见.agents/docs/2026-06-26-llvm22-libatomic-self-containment-design.md)。
mcpp add gtest+mcpp build报duplicate symbol: main/LNK2005(#168): gtest 作为常规依赖时,其gtest_main.cc(自带 main)被链进应用,与应用自身的 main 冲突。修复采用通用的「feature 门控源」机制:依赖描述符可声明[mcpp].features.<名>.sources,被某 feature 列出的源默认不编译/链接,仅在该 feature 被请求(dep = { version="…", features=["…"] })时纳入。gtest 描述符把gtest_main.cc归入mainfeature → 默认只链框架,不再撞 main;需要 gtest 提供 main 时gtest = { version="1.15.2", features=["main"] }显式开启。 门控仅作用于mcpp build;mcpp test保持既有的 dev 依赖 main 检测(0.0.64)不变。 详见.agents/docs/2026-06-25-gtest-main-feature-and-add-dev-design.md。
mcpp add --dev <pkg>:把依赖写入[dev-dependencies](测试专属,如 gtest; 由mcpp test消费,不链进mcpp build的应用)。
- 单元
SynthesizeFromXpkgLua.FeatureGatedSources(描述符 feature 门控源解析); e2e79_gtest_regular_dep_feature_main.sh(#168 哨兵 +features=["main"]opt-in +add --dev)。
- release workflow 默认 xlings 版本
0.4.58→0.4.60(缓存键同步更新)。
mcpp test在自带main()的测试 + gtest dev-dep 下duplicate symbol: main: gtest 的gtest_main.cc自带main(),而 mcpp 此前把依赖的全部对象内联进每个 测试二进制,于是测试自己的main()与gtest_main.o撞符号。修复:兑现依赖 描述符里已声明的kind="lib"——把这类依赖编译成静态归档lib<pkg>.a,链接在 测试对象之后;标准归档语义只在符号未定义时拉成员,故gtest_main.o的main只在测试不自带main时才被拉入。{自带/框架 main} × {用/不用 gtest}全部组合 皆正确,用户无感。纯模块依赖(如 mcpplibs.cmdline,无非模块对象)行为不变。 这是通用 link-model 改进、由既有描述符kind驱动,无 gtest 特例,未来 测试框架声明kind="lib"即自动适配。详见.agents/docs/2026-06-25-dependency-archive-linking-design.md。
- 新增单测
NinjaBackend.ArchiveInputsLinkedAfterObjects(归档须排在对象之后)与 跨平台 e2e78_test_main_combinations.sh(四种 main×gtest 组合mcpp test全绿)。
tests/目录无代码提示:clangd 在测试文件里对gtest::InitGoogleTest()、import std/import mcpplibs.*全无补全。根因:compile_commands.json是当次构建 plan 的镜像,mcpp build的 plan 不含tests/**/*.cpp与 dev-deps,而它与mcpp test写同一个 cdb——后写覆盖前写,日常「编辑→build」循环里测试条目几乎总被擦掉。修复:write_compile_commands由「全量覆盖」改为「合并保留」——保留当前 plan 未覆盖但 文件仍存在的旧条目(上次mcpp test写入的测试条目),剪除已删文件。mcpp build自身 零改动:不解析、不下载任何 dev-deps,build-only 用户与构建图均不受影响(offline-first)。 跑一次mcpp test后,测试补全在后续所有mcpp build中持久生效。 详见.agents/docs/2026-06-25-cdb-test-coverage-design.md。
- 新增单测
tests/unit/test_compile_commands.cpp(合并/剪除/去重/坏 JSON 回退)与跨平台 e2e77_cdb_preserves_test_entries.sh(mcpp test后真实重建mcpp build仍保留测试条目)。
- macOS 链接
library not found for -lSystem(#43):macOS 链接命令此前从不显式传 SDK, 链接侧靠 clang 隐式探测(xcrun/SDKROOT→ ld64-syslibroot)去找libSystem。干净的 CI Xcode runner 上探测正常、缺陷被掩盖;真机一旦xcode-select指向异常 / 只装 Command Line Tools / 新装 bundled clang,探测失效就ld64.lld: library not found for -lSystem+ 所有 libc 符号未定义。修复:f.ld显式追加-isysroot <SDK>,并给macos::sdk_path()加多级回退 (SDKROOT→xcrun→xcrun --sdk macosx→xcode-select -p推导 → 固定路径),即便 xcrun 返回空也能定位 SDK,把链接从「碰运气」变「确定」。(#162) - macOS 首跑需手动回车 / stdin 挂起:装 POSIX 工具链时进程等待 stdin,POSIX 路径也 seal
stdin(
</dev/null),不再要求交互按键。(#163)
- 新增跨平台 e2e
76_compile_commands_generated.sh:mcpp new+mcpp build一个最小工程, 在 Linux / macOS / Windows 三平台断言根目录生成合法compile_commands.json。因mcpp build含链接步骤,它同时是 macOS-lSystem链接缺陷的跨平台回归哨兵。(#165)
- 离线优先的索引刷新:
mcpp build不再因 TTL 过期就自动联网xlings update。改为 miss-triggered——依赖在本地索引里就直接用(零网络,消除弱网/Termux 首跑卡顿);依赖在 本地查不到时才刷新一次去拉它(打印Refreshing package index — \` not found locally`, 并有 120s 防重,避免一个 build 里多个缺包各跑一遍全量 git 同步)。 mcpp index status:只读、全程不联网,显示 xim/mcpplibs 两索引的 present/fresh/age/path; 缺索引时提示显式mcpp index update。- install.sh 多架构:新增
linux-aarch64(aarch64 / arm64),并支持 GitHub→GitCode(CN) 镜像回退(MCPP_MIRROR=CN强制 GitCode),让被墙网络下同一条安装命令可用。 - first-init 细粒度计时日志:
--verbose下首次初始化(sandbox 布局、patchelf/ninja bootstrap) 各步带时间戳 +ScopedTimer耗时([VERBOSE <ts>] … done (Δ=<ms>ms)),便于定位"卡很久"的步骤。
- e2e 套件拆为独立的
ci-linux-e2e.yml,与 build/单测/工具链矩阵并行,缩短每个 PR 的关键路径。 tests/e2e/run_all.sh每个用例输出耗时 + 末尾「最慢优先」汇总,便于后续分片/优化。
- 自托管清单改用 TOML 原生命名空间依赖写法
mcpplibs.cmdline = "0.0.1"(去掉遗留引号)。
- 包描述符解析改为 identity-first:不再「按候选文件名跨索引无序扫描、撞上第一个就返回」,
而是用描述符声明的规范
(ns, name)二元组校验命中文件的身份。修复compat.zlib在全新 CI 上偶发index entry has no mcpp field(外来xim-pkgindex/.../zlib.lua因目录遍历 顺序先被撞到而冒充compat.zlib)。索引目录改为排序后确定性遍历。
- 新增统一的
canonical_xpkg_identity()归一器(身份 = 二元组(ns, name);ns为可分层命名 空间路径,name为单一末段;点号名a.b本质(a, b))。归一三步:无声明 ns → 继承所属索引 默认 ns;求 FQN;按最后一个点切分。匹配 = 限定请求精确相等 / 非限定请求按默认搜索路径[mcpplibs, compat]。compat降级为搜索路径里的数据项(kCompatNamespace),不再是匹配 分支。[indices]路径索引的无命名空间描述符继承索引命名空间。
ci-{linux,macos,windows}.yml各加一步:用本次构建出的 mcppgit clone并mcpp build/mcpp run外部 C++ 工程 xlings(openxlings/xlings),验证自托管 mcpp 能构建真实外部项目。
mcpp run/test/build不再把目标的捆绑 glibcLD_LIBRARY_PATH注入到 mcpp 自身进程,因而泄漏进它启动的宿主/bin/sh。在 glibc 比捆绑版(2.39)更新的 发行版上,sh会被强制加载捆绑的旧 libc,无法满足宿主libtinfo的GLIBC_2.42符号而在目标运行前崩溃(报错形如sh: ... version 'GLIBC_2.42' not found)。新增platform::process::run_exec/capture_exec:直接 exec(不经 shell),额外环境 只作用于子进程;run / test / 快速路径 ninja / 整次构建 ninja 四个启动点全部改走它。
mcpp pack --mode模式更名,语义更清晰(旧名保留为永久别名,tarball 后缀冻结不变):bundle-project→vendored(默认)、bundle-all→self-contained;新增system模式(完全依赖宿主提供所有共享库,用于发行版打包 / 同发行版部署)。static不变。两轴模型:libc 由--target选(gnu/musl),--mode只选打包深度。
[targets.<name>]新增按目标的键defines/cxxflags/cflags,作用于该目标 独占的入口源(它的main)。用于二进制入口私有的标志(如-DBUILD_SERVER=1、 局部告警抑制),不影响共享模块/实现对象(compile-once 模型不变)。需要穿透共享代码的 差异请用 workspace member 或[features](#131)。[targets.<name>]新增required_features:仅当列出的 feature 全部激活时才构建该目标, 否则静默跳过。是构建选择门禁,不激活 feature。mcpp test现在接受--profile/--features/--strict,让被测代码与测试二进制 在所选 profile/feature 下编译(适合 sanitizer、契约求值语义等整次构建模式)。
[targets.<name>]下的不支持键不再被静默丢弃,而是产生 warning(--strict下为 error), 并指引到正确的机制(workspace / features / profile)。- 文档
docs/05-mcpp-toml.md(及docs/zh)新增"构建配置该放哪"的决策指引。 设计记录见.agents/docs/2026-06-18-per-target-build-config-design.md。
mcpp new <name> --template <pkg>:对声明了命名空间的模板包(如mcpplibs.llmapi以裸名llmapi引用)现在能从描述符派生出 (namespace, shortName) 坐标,正确完成 semver 解析与安装(#130)。
- 架构重构(零行为变更):
cli.cppm从 6192 行精简为约 480 行的纯命令 分发层;src/cli/cmd_*仅保留参数解析与路由,全部领域实现下沉到属主 子系统 ——mcpp.build.{prepare,execute}、mcpp.toolchain.{post_install, lifecycle}、mcpp.pm.index_management、mcpp.bmi_cache.maintenance、mcpp.scaffold.create、mcpp.publish.pipeline、mcpp.pack.pipeline、mcpp.doctor、mcpp.project、mcpp.fetcher.progress。 设计与迁移记录见.agents/docs/2026-06-10-cli-modularization.md。
- 库 / 组件下载现在与工具链下载一样显示实时进度条、字节进度与速度。自定义 /
项目索引依赖改经 xlings NDJSON
interface install_packages安装(仍落在项目 本地数据根,不改变安装位置与 install hook 顺序),不再静默卡住。
- 下载连接 / 预取大小阶段(
totalBytes尚未知)进度行不再"冻结"无反馈: 新增不确定态渲染,显示connecting…+ 已用时,流式无Content-Length时显示已下载字节,直到拿到总大小再切换为百分比进度条。
- 内置 xlings 版本上调至
0.4.51。 - 下载进度的状态机与渲染集中到
mcpp.ui(DownloadProgress),工具链 / 内置索引 / 自定义索引三条路径共用同一套 UI。
- 共享库 target 支持声明
soname,Linux 构建会传递-Wl,-soname,..., 并在运行产物目录生成 ABI 名称 alias,供下游DT_NEEDED/dlopen()以标准 SONAME 加载。
mcpp run/mcpp test会把工具链 runtime 目录加入进程库搜索环境。 这修复了 GLX/OpenGL driver 这类经由dlopen()加载的库无法找到自身DT_NEEDED闭包的问题。
- 修复裸依赖选择器无法 fallback 到独立 root 包的问题。现在
imgui = "0.0.1"会先尝试省略前缀的mcpplibs/imgui,若候选包身份不匹配, 会继续匹配独立 rootimgui,避免把非mcpplibs体系的包误解析为mcpplibs.imgui。 - 选择候选 xpkg 描述时校验
package.name/package.namespace,并在 lockfile 中保留独立 root 包的空 namespace 身份。
- 修复 git branch 依赖的缓存身份和 lockfile source 元数据。branch 依赖现在会先
解析到具体 commit,缓存 key 会随远端 branch 更新而变化,lockfile 也会记录
git+<url>#branch=<name>@<sha>而不是错误落到index+mcpplibs@。
- 支持在单个
[dependencies]/[dev-dependencies]/[build-dependencies]/[workspace.dependencies]表中使用多段 dotted dependency selector,例如imgui.core = "..."会先尝试mcpplibs.imgui/core,未命中时再尝试同级根imgui/core。 xpkg.lua的mcpp.deps支持同样的 dotted selector 规则,方便 compat、 imgui 等生态根和mcpplibs并列演进。
mcpp add默认保留用户写入的 dotted selector,显式 namespace 仍可使用ns:name写入[dependencies.<ns>]。
- 将
[package].standard打通为一等 C++ 标准配置,默认仍为c++23, 并支持c++26/c++2c等写法。
- 编译 flags、
compile_commands.json、fingerprint 与import std标准库 BMI 预构建命令现在使用同一个 active C++ 标准。 std.gcm/std.pcmcache 增加元数据校验,只有 compiler、stdlib、target、 standard、source 与 build command 匹配时才复用。build.cxxflags回归附加 C++ flags 语义,若写入-std=会提示迁移到[package].standard。
- 修复 Objective-C
.m源文件在 Ninja 后端被路由到 C++ 编译规则的问题。.m现在与.c一样使用 C/Objective-C 编译器与cflags,避免 macOS GLFW 等上游 Objective-C 源被错误附加-std=c++23。
- 修复 project-local index 包的 xpm hook 工具依赖无法解析官方
xim索引的问题。项目级 xlings 配置现在会在 custom/local index 旁边显式暴露 官方xim索引,让xim:python等 hook 工具依赖可用。
- 修复 project-local index 包安装时没有走项目 xlings 数据根的问题,本地 path 索引现在通过 xlings CLI 直接安装到项目数据目录,避免 hook 查找不到同索引包。
- 修复包 install hook 运行前
mcpp.deps尚未安装的问题,库/头文件依赖可以继续 留在mcpp.deps,只有 hook 执行工具需要放入 xpm deps。
- 支持包描述拥有自己的
ldflags,依赖包声明的链接参数会随包源码编译 一起进入最终链接命令,消费方项目不再需要手动补齐第三方 C/C++ 库的私有链接参数。
- 修复 xlings 项目构建时自动索引刷新泄漏 xlings 内部
[N/M] index::path输出的问题。mcpp 仍保留Updating package index (auto-refresh)状态行, 且该状态行走统一彩色 UI 输出;内部xlings update现在在自动刷新路径中 静默执行。 - 修复自动索引 freshness 依赖不稳定目录 mtime 的问题,改用 mcpp-owned
.mcpp-index-updatedmarker,避免 full prepare 时重复刷新索引。 - 修复命名空间依赖命中 BMI cache 后仍显示
Compiling mcpplibs.*的问题, cache key 与 UI 状态现在使用解析得到的 canonical dependency identity。 - 修复
xim:工具链自动安装时官方索引/目标包文件/.xlings-index-cache.json可能陈旧或指向临时 sandbox 路径导致package not found的问题。
- 修复默认
mcpplibs索引缺失时被其他 xlings 索引误判为 fresh 的问题。mcpp build/search现在会要求默认索引自身存在并处于 TTL 内,避免compat.*依赖在混合缓存状态下找不到。
- 支持包描述拥有自己的
cflags/cxxflags,依赖包源码编译时会继承所属包 的构建宏,消费方项目不再需要集中声明第三方 C 库的私有宏。 - 支持 Form B
mcpp.generated_files,官方索引包可以在包目录下生成少量配置头, 用于承载平台兼容宏或库私有配置。
- 修复本地
path索引读取命名空间包时没有匹配pkgs/<prefix>/<namespace>.<name>.lua的问题。 - 自定义索引首次同步时保留 mcpp 的
Fetching custom index repos状态提示,但静默 xlings update 的内部逐项输出。
- 将 legacy dotted dependency key 兼容解析移入
mcpp.pm.compat.legacy模块,保留mcpp.pm.compat作为 facade,并明确标注该兼容路径将在 mcpp 1.0.0 移除。
- 修复 project-local
.xlings.json生成时未转义 JSON 字符串的问题, 避免 Windows 本地 index 路径中的反斜杠导致 xlings 跳过项目索引。
- 修复 xlings 项目使用 mcpp 构建时 custom index 首次同步、project data root 查找和 local index 相对路径解析的问题。
- 支持 canonical nested dependency 写法:
[dependencies] capi.lua = "0.0.3"和[dependencies.mcpplibs] capi.lua = "0.0.3"。 - 将 legacy flat dotted dependency key 兼容解析集中到
mcpp.pm.compat, 并标注该兼容路径将在 mcpp 1.0.0 移除。
LLVM / Clang 工具链支持与 xlings 镜像配置完善。
- ✅ LLVM / Clang 工具链支持 —— 新增基于
clang++、clang-scan-deps、llvm-ar、lld的工具链探测与构建路径,支持 xlingsllvm包提供的 自包含 Linux LLVM 工具链。 - ✅
import std支持 —— LLVM libc++ 模块标准库可用时,自动发现std.cppm/std.compat.cppm,并接入标准库 BMI 预构建流程。 - ✅
mcpp self config --mirror—— 通过 xlings 抽象层配置 sandbox 镜像,默认初始化为CN,CI 可显式切换为GLOBAL。
- 🔧 工具链 provider 拆分 —— 将通用模型、探测逻辑、GCC、Clang、LLVM provider 与 registry 分离到独立模块,为后续更多工具链扩展预留入口。
- 🔧 xlings 索引兼容迁移 —— 自动将历史
mcpp-index索引名迁移到mcpplibs,避免旧 sandbox 状态影响新流程。
构建 / 环境体验优化三件套。
- ✅ Glob 排除模式 ——
[modules].sources(以及 Form B 的sources) 现在支持!前缀的排除模式(类似.gitignore):正向 glob 先展开、再减去sources = ["src/**/*.cpp", "!src/**/*_test.cpp", "!src/**/*_fuzzer.cpp"]
!-prefixed glob 命中的路径。解决了上游库 test/fuzzer 文件与源混放时不得不逐文件列举的问题(典型如 ftxui)。
-
🔧 xlings 布局调整 —— xlings 二进制从
<MCPP_HOME>/bin/xlings(与 mcpp 同目录)移至<MCPP_HOME>/registry/bin/xlings(=<XLINGS_HOME>/bin/xlings)。由于 xlings 的 shim-creation guard 恰好检查<XLINGS_HOME>/bin/xlings是否存在,新布局下ensure_sandbox_xlings_binary自然变成 no-op,省去了之前的 hardlink 步骤。 -
🔧 测试自动继承 sandbox PATH ——
mcpp test在调用测试二进制前, 自动把 sandbox 的subos/default/bin(含 patchelf、ninja 等 一次性自举工具)追加到$PATH,使 test 代码 shell-out 到这些工具时 不再报 "command not found"。
依赖解析体系的三步演进:0.0.2 release tag 之后合入 transitive walker, 这一版补齐 SemVer 合并(Level 2)+ 多版本 mangling 兜底(Level 1)。
-
✅ 依赖图传递性遍历 —— 直接依赖的子依赖(以及更深层)自动跟随入解析图, 消费者不必再在自己的
mcpp.toml里把 grandchild 也写一遍;子依赖的[build].include_dirs也会沿链路传播,让中间层在编译时看得到 grandchild 的头文件。冲突检测同时区分 path / git / version 三类来源,跨来源不允许 混用。 -
✅ SemVer 合并解析(Level 2) —— 同一个包在传递依赖图里被多个消费者 以不同版本约束声明时,resolver 会把两条原始约束 AND 合并(裸版本号视作
=X.Y.Z),向 index 重新查询,选出同时满足两侧的具体版本。若该版本与 此前已 pin 的不一致,旧的 manifest 与[build].include_dirs会被原地 替换为新版本的内容,孩子依赖也按新 manifest 重新入队。新增 e2e32_semver_merge.sh覆盖兼容合并 + 不可调和两条主链路。 -
✅ 多版本 mangling 兜底(Level 1) —— SemVer 合并失败时(典型如
=0.0.1⨯=0.0.2这种无重叠的 pin),resolver 不再硬报错,而是把次要 版本的源码 stage 到target/.mangled/<consumer>/...下,通过正则改写(export )?module X;/(export )?module X:Y;/(export )?import X;把模块名替换成<X>__v<M>_<m>_<p>__mcpp形式,让两个 BMI 在同一构建图 里以不同模块名共存(C++23 module attachment 帮我们做 ABI 隔离,无需额外 namespace mangle)。直接 consumer 的源码也一并 stage + 改写,让它的import指向 mangled 副本。MVP 范围:仅处理 dep-as-consumer + 叶子 secondary 两种情形,主包做 consumer 或 secondary 还有自己的 transitive deps 时报清晰错误并建议显式 pin。新增src/pm/mangle.cppm(纯改写 helper + 11 个单元测试)和 e2e33_multi_version_mangling.sh。
- 🔧 构建后端按需为多包做 obj 路径命名空间 ——
plan.cppm检测到 跨包同名源文件(多版本 mangling 后两个parse.cppm同时存在的常见情形) 时,自动把obj/<file>.o改为obj/<sanitized-pkg>/<file>.o,.ddi扫描产物随之放在 object 同目录下。无碰撞时仍是原始obj/<file>.o布局,不影响现有缓存命中。
第二个公开版本。新增 C 语言一等公民支持、xpkg 风格依赖命名空间、包管理子系统骨架重构,以及 lib-root 约定。
-
✅ C 语言源文件支持 —
mcpp.toml的[build]段新增cflags、cxxflags、c_standard三个字段;ninja 后端探测.c源文件后自动派 生兄弟 C 编译器(g++ → gcc、clang++ → clang、跨编译器前缀如x86_64-linux-musl-gcc同样适用),发出独立的c_object规则。 按文件扩展名分发:.cppm → cxx_module、.c → c_object、其它 →cxx_object;dyndep / 模块扫描自动跳过.c。实测可直接编译 mbedtls 3.6.1 全部 108 个.c源文件(SHA-256 测试向量与 FIPS 180-4 一致)。 -
✅ lib-root 约定 — 库项目(
kind = "lib"/shared)的 primary module interface 默认在src/<package-tail>.cppm,且必须export module <full-package-name>;(无:partition后缀);可用[lib].path = "src/foo.cppm"显式覆盖(cargolib.rs风格)。 违规组合(显式 path 但文件缺失 / 文件 export partition / module 名 不匹配 [package].name)报 error;约定文件缺失只报 warning,给已有 项目软迁移时间。纯 binary 项目跳过所有检查。 -
✅ xpkg 风格依赖命名空间 —
mcpp.toml现在原生支持三种依赖书写形式:- 平铺默认命名空间:
gtest = "1.15.2"⇒(mcpp, gtest),无引号 - TOML 子表命名空间:
[dependencies.mcpplibs] cmdline = "0.0.2"⇒(mcpplibs, cmdline),无引号 - 老式带点字符串(向后兼容):
"mcpplibs.cmdline" = "0.0.2"仍能解析 - CLI 同步:
mcpp add mcpplibs:cmdline@0.0.2接受<ns>:<name>冒号分隔形式,写出仍是子表写法 - 解析层在
DependencySpec增加namespace_+shortName结构化 字段,fetcher / lockfile / cache 等下层逻辑沿用现有完全限定 key。
- 平铺默认命名空间:
-
🛠
src/pm/包管理子系统(7 步重构,全部完成) — 包管理相关代码 从cli.cppm(3510→2900 行) /manifest.cppm/lockfile.cppm/fetcher.cppm/publish/xpkg_emit.cppm中抽出,集中到独立的src/pm/目录下,跟build//toolchain//pack/平级。 最终 8 个内部模块:pm/pm.cppm(子系统门面,re-export 数据类型)pm/dep_spec.cppm—DependencySpec+kDefaultNamespacepm/index_spec.cppm— 占位,等索引仓配置实现pm/lock_io.cppm—mcpp.lockIOpm/package_fetcher.cppm— xlings NDJSON 客户端pm/resolver.cppm—resolve_semver+is_version_constraintpm/commands.cppm—cmd_add/cmd_remove/cmd_updatepm/publisher.cppm—emit_xpkg+ tarball / sha256 / release helpers
整个重构严格保持零行为变更:每一步独立 PR、独立 CI 通过、独立可 回滚;旧模块名(
mcpp.lockfile/mcpp.fetcher/mcpp.publish.xpkg_emit) 保留薄 shim 透传到新模块,所有调用点零改动。规划与依赖图见.agents/docs/2026-05-08-pm-subsystem-architecture.md§3-§5。 -
📄 新增设计文档
.agents/docs/:2026-05-08-package-index-config.md— 多源包索引仓配置 +mcpp.lock索引 commit 锁定 + 两层不可变性 (L1 publish policy + L2 lock mechanism)2026-05-08-pm-subsystem-architecture.md— 包管理子系统目标布局 与 7 步落地计划
- 🐛 path 依赖的
[package].name比对支持 xpkg 标准name+ 旧式<ns>.<name>复合名两种形式,兼容当前 mcpp-index 描述符尚未迁移的 状态。 - 🐛 module 扫描器解析 partition import(
import :foo)时,不再把当前 TU 自己的 partition 后缀拼进 logical name。 之前export module M:bar;里的import :foo;被解析成M:bar:foo(没人 provide,产生 7 条 stale warning);现在正确解析为兄弟分区M:foo。GCC dyndep 实际能分辨,所以 build 不影响,但 mcpp 自己的 warning 噪音消失。在mcpplibs/tinyhttps上验证(7 条 warning → 0 条)。
向后兼容。老的 mcpp.toml / mcpp.lock 不需要任何改动即可在 0.0.2 下
继续工作。带引号的 "ns.name" 形式继续被解析,只是新写出的 mcpp add
会用无引号的子表形式。
mcpp 首个公开发版本。
- ✅ 基础工程命令:
mcpp new/build/run/clean/test - ✅ C++23 模块(
import std/import foo.bar)一等公民支持 - ✅ 跨项目依赖:mcpp-index 远程仓库、git、本地 path 三种来源
- ✅ SemVer 约束:
"foo" = "^0.0.1"/"~1.2.0"/">=1, <2" - ✅ P1689 编译器驱动模块扫描 + ninja
dyndep - ✅ 跨项目 BMI 持久缓存
- ✅ 私有 toolchain 沙盒(
mcpp toolchain install / default / list), 跟系统 PATH 完全隔离;首次使用自动装 musl-gcc 默认工具链 - ✅ 部分版本号支持(
mcpp toolchain install gcc 15自动选最高匹配) - ✅
mcpp pack三种自包含发布模式:static— musl 全静态,单文件可分发bundle-project(默认)— 只 bundle 项目第三方 .sobundle-all— 全自包含含 ld-linux + libc,附run.shwrapper
- ✅
mcpp self {doctor,env,version,explain}自诊断 - ✅ 下载 / 安装实时进度(速度、字节数、终端宽度自适应)
- ✅ 项目相对路径显示(
@mcpp/...、project-relative)
mcpp-0.0.1-linux-x86_64.tar.gz— bundled tarball(mcpp + 内置 xlings)mcpp-linux-x86_64.tar.gz—latest别名install.sh—curl | bash装机脚本SHA256SUMS+ 各资产 sha256 sidecar- 二进制为 musl 全静态 ELF,无 PT_INTERP / RUNPATH 依赖,任意 Linux x86_64 直接可跑
- 仅支持 Linux x86_64(glibc / musl 通用)
- macOS / Windows / aarch64 还在路上
- workspace、
mcpp publish --auto(自动 PR 到 mcpp-index)等功能未发版
接口、命令、产物形态可能在后续小版本调整。issue / 想法 / 协作意向都欢迎到 issues 来。