Releases: mcpp-community/mcpp
Release list
v2026.9.6.5
(no CHANGELOG entry found for 2026.9.6.5)
v2026.9.6.4
(no CHANGELOG entry found for 2026.9.6.4)
v2026.9.6.3
(no CHANGELOG entry found for 2026.9.6.3)
v2026.9.6.2
docs/20 增加「框架层是什么形状」
四条 lane 证明规则包能驱动四个编译器,不能证明这套机制扛得起一个真会被部署的东西。
第一个框架(llama.cpp 的 Vulkan 后端)量出来的五条,写进了 docs/20:框架自带生成器
时驱动它而不是替换它;到 134 条边这个规模「声明」就是全部差别;能力探测属于构建程序
且只做一次;可选后端的一切挂在 feature 下,判据是「CPU 构建的解析结果里不出现该后端的
任何包」;软件设备不自动是硬件的替身 —— ggml 只保留类型不为 eCpu 的 Vulkan 设备,
lavapipe 仅因类型被排除,而它声明了后端要求的每一项能力。
版本告警不再预言一个它看不见的失败
cmdline = "0.0.x" 会得到一句告警,理由是好的:那个形式解析不出来,而随后的失败只会
报出包名,让读者去查一个存在得好好的包。但那句话说的是「The fetch will fail」——
无条件的。
不是范围的字符串会被当作精确的索引键使用,所以失败与否取决于索引里有没有那个键,
而 manifest 解析器看不见索引。0.0.x 失败是因为没有这个键;b10069 成功是因为键就
在那里 —— 那是本生态里一个已发布包的版本方案(mcpp#363)。旧措辞对两者都断言失败,于是
那个包的每一次构建都打印一句预言了并未发生的失败的告警,而这会教会读者忽略这条通道。
新措辞陈述机制:「不是版本范围,所以当作精确索引键使用;索引必须逐字带有它。」
判据也跟着换了对象:三处断言原本钉住「not a requirement」这句措辞,现在钉住这条消息
必须携带的性质 —— 引用出问题的那个字符串(否则读者找不到)、出现 PACKAGE 这个词
(因为他们随后看到的失败正是报包名)、给出一个被接受的形式。钉措辞的断言,在改一次
措辞之后要么空转要么因为措辞而红。
示例目录与文档列表现在必须互相对得上
没有任何 CI 作业构建示例,所以课程表是这个仓库里改名可以静默弄坏的那一部分:文档
继续指着一个已经不存在的路径,而每个作业都是绿的。这一轮把四个设备示例整体搬了家,手工
改了 14 个文件 —— 正是该有一条判据的时候。
e2e 616 是双向的,而单向的那一半不值得跑:「列出的路径都存在」在一份什么都不列的文档上
通过,「盘上的示例都被列出」在一份把一半链接指错地方的文档上通过。两条合起来才是那条性质。
它当场抓到一个既有缺口:examples/04-workspace、05-lib-distribution、
06-openkal-cross、07-project-subos 都在盘上,而中英两份 docs/01-examples.md
一个都没列。现在列上了。
这是结构判据,它自己也这么说:它不构建任何东西,所以回答不了「这个示例还能不能跑」。
[feature-xlings] 声明的工具,构建程序找不到
[feature-xlings.<f>] 从诞生起就参与供给:在那里写下一个包,<f> 生效时它就会被下载
并安装。但构建程序的环境是由 [xlings.workspace] 单独填充的,于是载荷已经躺在
store 里,mcpp::xpkg_dir 仍然返回 ""。
这个缺陷的形状是「答案已解析,却没有接到决定上」:供给端读的是「声明 + 生效的 feature」,
查询端读的只是「声明」。它在 llama.cpp 的 Vulkan 后端上现形 —— 规则程序拿不到
xim:shaderc,而它唯一说得出口的话是「请声明 xim:shaderc」,指向一条作者早已写下的声明。
一条指错文件的诊断比没有诊断更坏。
修法是让查询端读同一个集合:fillXpkgDirs 现在把调用方已经算好的 feature 闭包
里的 [feature-xlings] 条目并进来。「是否安装」仍然是唯一的过滤器,所以
when = "dev" 的条目对消费者依然回答 ""。
判据是 e2e 614,它有对照:同一个工程构建两次,--features gpu 下拿到载荷路径,不带
feature 时拿到空串。只断言前者的用例,在一个「凡装过的包都作答」的 mcpp 上同样会通过。
用的包是 xim:ninja —— mcpp 自己 bootstrap 进沙箱的那个,所以判据不需要网络;版本从
store 里读出来而不是写死,否则 mcpp 升 ninja 会把这个 feature 的测试变红。
[feature-deps] 的共享库没上链接行
同一族的第二个缺陷,现形于同一次构建。[feature-deps] 在解析期就并进了 root 的
dependencies,所以那个依赖被解析、被下载、被编译 —— 每一行日志都说它在。但 plan
读 root 的边时读的是 packages[0],而它是在那次并入之前拍下的快照,于是依赖的共享库
既没进链接行也没进 implicit inputs。
它安静是因为形态:库工程的 mcpp build 产出静态档案,而档案不解析符号,所以构建成功。
失败出现在链接可执行文件的人那里 —— 报的是那个依赖自己的入口点未定义。llama.cpp 的
Vulkan 后端就是这样撞上的:libllama.a 建好了,vulkan_decode 链接时 vkGetInstanceProcAddr
未定义。
修法沿用本文件里已有的写法:root 的真相在 *m 而不在 packages[0](checkVersionFloors
早已为同一个理由这样分支)。
判据 e2e 615 的形态就是判据本身。同一个工程写成二进制时,有缺陷的引擎和修好的引擎
都通过 —— 实测过 —— 所以围绕 mcpp run 搭的夹具是一个不可能失败的测试。让缺陷现形的是
「库 root + 测试二进制去链接」,而这正是本生态里每一个库包的形态。
四个设备示例合并为 examples/09-heterogeneous
09-cuda-kernel / 10-vulkan-compute / 11-sycl-kernel / 12-hip-kernel 是同一课的
四种编程模型:同一个 kernel、同一道接缝、同一条收窄的 glob、同一个答案,差别只在规则包
驱动哪个编译器。四个连号在课程表里说的是「四课」,而再加一个模型就会说成五课。它们现在
是 examples/09-heterogeneous/{cuda,vulkan,sycl,hip},共享的那一课写在目录的 README 里。
同一次改动把落后于生态的钉子对齐了(它们改的是同几份 manifest):cuda 与 vulkan
从 mcpp:plugins 0.1.1 跟到 0.2.0(sycl 与 hip 早已是 0.2.0),cuda 与 hip
从 compat:cuda-runtime 改写为 compat:cuda-driver —— 那条目已冻结并改名,旧名仍可解析,
所以这是拼写修正而不是修复。
文档里的版本号没有跟着动。*(2026.9.5.2+)* 这类标记陈述的是某项能力何时落地,是
历史事实;把它跟到当前版本会让文档对自己的主题说谎。只有「当前应当写下什么」才跟随发布。
v2026.9.6.1
.sycl 进设备扩展名表:判据是编译器,不是方言
SourceKind::Device 自陈是一个图上的角色 —— 「不扫描、不产 BMI、由 mcpp 不驱动的
设备编译器编译」—— 而在此之前表里每一个扩展名都是普通 C++ 编译器会拒绝的方言。.sycl
不是:它的内容就是普通 C++。使它成为设备单元的是它交给谁 —— 一个带设备后端、且不接受
C++20 modules 的第二个编译器(icpx,或带 SYCL 前端的 clang)。
被否掉的替代是「让收窄的 glob 去承载 .cpp」。那会让同一个扩展名因为哪条 glob 先匹配
而指向两个不同的编译器,而设备构建赖以可读的接缝(设备代码只经 extern "C" 头进入程序)
正是因为文件名说明了一个单元在接缝的哪一侧。
这次加行对既有构建是惰性的,两个条件同时成立:设备扩展名不在 default_source_globs
里,所以没有任何 glob 变宽;而把这类扩展名写进 sources 在加行之前是硬错误,所以没有
任何文件悄悄换了角色。e2e 613 的第五段对整张默认 glob 列表做断言而不是对两个名字,
第三段则用同一份内容的 .cpp 拼法做对照 —— 只测 .sycl 的用例在一张「按内容分类」的
表上同样会通过。
配套的规则包在 mcpp:plugins 0.2.0:mcpp.rules.sycl 驱动 xim:dpcpp 载荷,
mcpp.rules.hip 在 NVIDIA 平台上把 HIP 当作 CUDA 运行时之上的一层头文件,
mcpp.rules.spirv 增加 glslc 路线(xim:shaderc 使它从一句声明变成一条路线)。
文档见 docs/20-heterogeneous-builds.md 的「lanes」一节。
mcpp clean --stale:只清 target/ 里已无构建使用的指纹目录 (#565)
每次配置指纹变化都会在 target/<三元组>/ 下新开一个目录,旧目录从不回收;mcpp clean 只有整删
一档,代价是全量重编,于是没人跑。mcpp clean --stale 以 target/.build_cache 记录的
(三元组, 指纹) 为当前,删掉记录过的三元组目录下其余兄弟目录并报告各自体积;未被记录但在
--older-than(默认 1d)之内写过的目录保留(mcpp test 的构建不写记录)。--dry-run 只列出。
没有构建记录时拒绝执行而不是猜;记录之外的目录(如 mcpp pack 的 dist/)不碰。
fingerprint changed 的警告末尾现在附带这条命令,让增长可见。
--stale、--dry-run、--older-than 三者任一都选中这一档:mcpp clean --older-than 3d
说的是一次有范围的清理,不该落进整删。负的时长被拒绝,0 表示不保留任何未记录目录。
mcpp search 显示包的可用版本,mcpp add 的建议同样携带 (#487)
search 的命中行追加该包描述符 per-OS 版本表的并集:semver 降序、按键去重,默认显示最新 3 个
并以 , ... 标记截断,--all-versions 显示全部。描述符不可读或未发布任何版本的包保持原两列
输出——富化是尽力而为的展示,不是新的失败路径。
$ mcpp search imgui
compat:imgui Dear ImGui immediate-mode GUI library core sources (1.92.8, 1.92.8-docking)
mcpplibs:imgui C++23 module package for Dear ImGui core and GLFW/OpenGL3 backends (0.0.6, 0.0.5, 0.0.4)
mcpp add 未命中时的跨命名空间建议从裸 FQN 升级为带版本:compat.eui-neo (0.5.6, 0.5.5, 0.5.3)。
数据是白捡的——did-you-mean 扫描本就要打开每个候选 .lua 读身份(#278),版本只是同一段文本
的再一次遍历;排序复用 SemVer 解析(version_req),不可解析的键保留原文排在最后(#363 的
教训:任意的索引键无法从解析形态复原)。build 失败路径的同款提示同步升级,两条路径不说两套话。
排序与扫描各有单测钉住;e2e 162 断言 build 与 add 两侧的建议都带版本。#324 的遗留半边。
v2026.9.5.4
构建程序声明的文件输入,快路径此前不比较
rerun_if_changed("data/table.csv") 声明的是「这个文件的内容变了就重跑」。工程级快路径
在没有任何源文件比 build.ninja 新时跳过 prepare_build,而构建程序缓存正是在
prepare_build 里被读取的;快路径自己只问过 glob 输入的路径集合(#359),没问过声明
文件的内容。数据文件既不在 src/ 下也没有 C++ 扩展名,mtime 扫描看不见它,于是改了数据、
mcpp build 打印 Finished dev in 0.00s、程序里编进去的还是上一次的字节。
快路径现在按缓存里记录的方式比较三类构建程序输入:glob 的路径集合、声明文件的内容哈希、
声明环境变量的值。e2e 612 用程序的输出做判据(陈旧的头文件与新的头文件让二进制打印
不同的字符串),并跑对照的另一半:输入没变时第二次构建仍然走快路径,否则「总是重建」也能
让第一条断言通过。发现它的是 mcpp.tools.embed——mcpp-plugins 0.1.1 里第一个非规则成员,
它把数据文件写成头文件,没有 action 可提交,所以完全落在这条路径上。
注释与文档里不再有装饰性符号
2026.9.5.3 的清理覆盖了 docs/、README.md、CHANGELOG、引擎源码、测试、示例与工作流
文件,没有覆盖 bench/、tools/、scripts/、mcpp.toml 与 README.zh-CN.md。程序
输出保留原样:用户在终端上读到的一行既不是文档也不是注释。README.zh-CN.md 同时补上英文
版已有的 Cortex-M 行,状态列改用与英文版相同的词。
v2026.9.5.3
官方构建插件集中为一个包:mcpp:plugins
规则包不再放在本仓库的 examples/ 下。它们现在集中维护于
mcpp-community/mcpp-plugins,以一个包 mcpp:plugins 发布,消费者用 feature 选择成员,
在 build.mcpp 里以成员自己声明的模块名 import:
[dependencies.mcpp]
plugins = { version = "0.1.0", features = ["rules-spirv"], host-module = true }命名收敛为两族:规则包 mcpp.rules.<x>,构建期工具 mcpp.tools.<x>;lib 根
mcpp.plugins 记录集合的版本。此前的 mcpp.build.<x> 撤回:那是引擎自己的模块族
(mcpp.build.plan、mcpp.build.prepare),插件不该与它同名。规则包规范曾以「模块名就是
裸包名,不能含点」为由撤回 mcpp.rules.*;I1(模块名由源码声明)落地后这个理由不再成立,
规范的 I8 与第 7 节按此修订。
引擎为此扩了一处:一个 host-module = true 的包,其解析后 [build] sources 里
—— 含 feature 加入的源文件 —— 的每一个模块接口单元都编成一个 host 模块,以各自声明的名字
注册,lib 根排在最前,feature 单元可以 import 它。只有清单里列出的源文件参与:未声明
sources 的包所推断出的 src/** 不被读取,所以此前发布的规则包暴露的仍是它当时暴露的那
一个模块。模块集合就是 feature 集合:未激活 feature 的单元不编译,import 它以未知模块失败。
e2e 610 分别断言这五条(含 mcpp 命名空间零告警、其他命名空间每个单元一条告警的对照);
tests/unit/test_provisions 覆盖接口单元的判定:实现单元、分区、全局模块片段与注释里引用的
声明都不算。
示例 09 与 10 像任何工程一样从索引消费 mcpp:plugins;mcpplibs:rules-cuda@0.1.0
在索引里保留并标注被取代。
设备源的判据是编译器,不是厂商
SourceKind::Device 的定义写着它陈述的是构建图里的角色 ——「由一个 mcpp 不驱动的
设备编译器编译」—— 并且明确说这样定义是为了让分类表不按厂商长出一行。而那张
扩展名表里恰好只有两行,都是 NVIDIA 的。
第二个设备 API 让这件事显形:一个写在带约束 glob 里的 shader 被拒绝为
'scale.comp' is listed in [build] sources, and mcpp has no role for the
extension '.comp'.
同一次运行里,规则包又被告知没有设备源并为此告警 —— 一个错误和一个警告在说同一个
文件的相反的话。
表因此扩到「由另一个编译器消费的语言」:CUDA 与 HIP,GLSL 的各个 stage 与无 stage 的
.glsl,HLSL,OpenCL C,Metal。共 18 个扩展名,完整清单见 docs/20-heterogeneous-builds.md。
这次扩表不能改变任何今天可用的构建,理由有两条且互相独立:设备扩展名本来就不在
默认 source glob 里,所以没有 glob 变宽;而这些扩展名今天在 sources 里是硬错误,
所以没有文件静默换了角色。两条都由 tests/unit/test_source_kind 断言,其中默认 glob
那条断言的是整张列表而不是曾经在场的两个名字。
mcpp.rules.spirv 与 examples/10-vulkan-compute
新的规则把「GLSL 如何变成 SPIR-V」陈述一次,与 mcpp.rules.cuda 同形:引擎拥有图 ——
加速器轴、把 shader 路由到规则包而非 C++ 编译器的带约束 glob、动作边、指纹 ——
并且不认识 "vulkan" 或 "glslang" 这两个词;规则包拥有拼法。
它产出的是头文件而不是目标文件。SPIR-V 是程序交给 vkCreateShaderModule 的数据,
所以动作以 role = "source" 提交 —— 这是引擎排在编译之前的那一个角色,正是生成的
头文件需要的;artifact 角色的产物排在链接之前,那已经晚于包含它的那个编译单元。
示例 10 用同一份产物在三处得到同一个结果:宿主 ICD 上的 RTX 4080、只给 VK_DRIVER_FILES
的软件驱动载荷(无 GPU 参与)、以及 mcpp build --no-accel 下接缝后的 CPU 实现。
修正
- 变体切换不再被快路径回放。 设备变体在指纹里,两次构建落在不同目录;而快路径在任何
计划存在之前运行,回放的是最后一次构建的目录。实测:mcpp build、
mcpp build --no-accel、mcpp build—— 第三次报Finished in 0.00s,随后的mcpp run
执行的是 CPU 变体。build.ninja 的头行现在记录选择来自清单还是来自--accel/--no-accel
(accel=default|override),两条快路径只回放前者;缺该字段的旧图按未命中处理并被重写。
e2e 611 与tests/unit/test_graph_shape断言。 MCPP_DEVICE_SOURCES以换行分隔。规则包若按;切分,对恰好一个设备源仍然正确,
对两个则拼出一条不存在的路径;e2e 609 第一段以整张扩展名表作分母,当场把它抓了出来。- glslang 若未链入 spirv-opt(
-Os not available; optimizer not linked),规则包降级
并告警一次,而不是让构建失败:可选的优化 pass 缺席不是错误,静默地不做优化才是。
文档
- 第 20 章由「加速器」更名为「异构硬件构建」(
docs/20-heterogeneous-builds.md),副题指明
GPU 与 AI 加速器目标以及宿主/设备混合编译;accel键不变。 - 第 5 章与第 7 章(中英)补入 feature 选择的规则集合与
mcpp.rules.*/mcpp.tools.*
命名;第 7 章中文版此前缺少命名一节,本次补齐。 - 文档、README、CHANGELOG 与代码注释里的装饰符号(告警、星标、勾叉等)全部移除;表格里
只以符号承载的取值改写为词(yes/no/partial/planned)。
v2026.9.5.2
⭐⭐ 在编译任何东西之前比较机器的下界
有些机器事实限定了能为它构建什么,而忽略它们时,失败到得很晚。本轮的样本:
设备运行时不得新于它将运行其上的驱动;当它更新时,构建与链接都干净通过,程序在
第一次分配处失败,消息里既不提工具包也不提驱动。
两个数字在编译任何东西之前都是可知的。mcpp 不去问厂商的工具要它们 ——
tests/unit/test_runtime_contract 禁止 src/ 里出现厂商探针,而且这条规则是对的:
一个学会跑一家厂商探针的引擎会学会跑四家。所以数字以声明抵达:
[[runtime.requirements]]
kind = "version-floor"
value = "cuda.driver >= 12.0"mcpp.build.version_floor 只做比较,这个文件里不出现任何厂商名字:
cuda.driver 是流经的数据。第二种后端不需要改动它。
⭐ 探针通道:mcpp::fact / mcpp::floor(协议 v7)
构建程序陈述它测得的事实与它需要的下界,引擎比较并在不满足时给出两侧取值
(version-floor-unmet)。这是让 CUDA 探针得以整体离开 src/ 的那条通道 ——
同样的读数现在由规则包产出,而它知道自己在跑哪个工具。
因此 mcpp self doctor 的设备一节与 mcpp.toolchain.devicehost 一并删除。
它读得对(载荷优先于宿主、crt/host_config.h 的宿主编译器上界、nvcc --dryrun
的不可达阶段),但它不属于引擎。新增 tests/unit/test_core_vendor_probes
在剥掉注释的源码上陈述这条性质,并自带分母:枚举到的文件太少即判失败。
逐 glob 的加速器约束
[build] sources 的条目可以带上它面向的加速器:
sources = [
"src/*.cppm",
{ glob = "src/kernels/**/*.cu", accel = "cuda12.9+{sm_89}" },
]构建按它收窄;--no-accel 整条排除;不覆盖它的 --accel 被拒并点名两侧
(accel-mismatch);匹配为空的约束被拒并点名该 glob —— 空匹配是笔误或搬走了的
目录,而不是空操作。
--accel / --no-accel 现在也挂在 run 与 test 上
此前只有 build 有,实测后果是一个工程的 CPU-only 变体能构建却不能运行。
两个动词接受同样的两个开关,给出任一个都绕开各自的快路径 —— 缓存的产物是按上一次
构建的轴产的。
第二个编译器需要的两个答案
MCPP_TOOLCHAIN_SYSROOT 与 MCPP_TOOLCHAIN_BINUTILS_DIR 陈述 mcpp 传给它自己
那个编译器的 --sysroot 与 -B。规则包驱动一个 mcpp 并未解析的编译器时,那个
编译器对环境一无所知:sub-OS 里 C 库不在 /usr/include,汇编器不在 /usr/bin,
于是它遇到的第一个 #include 就失败。hipcc、-fsycl-host-compiler、任何会编译
自己产物的生成器都有同一个缺口。
gcc::binutils_prefix_dir 把 -B 的守卫收敛到一处,此前有三份副本,其中一份的
注释写着它是另一份的镜像。
Linux 上的动态构建程序 helper 改用 DT_RPATH
RUNPATH 只对 helper 自己的 needed 生效,于是一个在运行期打开宿主库的构建程序
在下一跳失败:实测 dlopen("<sentinel>/lib/libcuda.so.1") 报
libdl.so.2: cannot open shared object file,而持有它的目录就在 helper 的 RUNPATH 里。
mcpp 链接的产物早就因为这个原因带 DT_RPATH。链接策略进 helper 的缓存身份,
旧 helper 会被重建而不是被重放。
--offline 跳过首次使用的沙箱引导
--offline 承诺不碰网络,而 load_or_init 在空 home 里克隆索引、经 xlings 装
ninja 与 patchelf。实测:空 home 下 26 秒 / 126 MB → 0.3 秒。
examples/09-cuda-kernel 走两条路线,并拒绝它不能配的对
主路线是 clang(-x cuda):工程自己的编译器编设备单元,没有第二个宿主编译器、
没有宿主编译器上界、没有 CUDA 的宿主头挡路。nvcc 是备用路线,并按名拒绝两对:
宿主编译器超出 crt/host_config.h 所述上界(实测 gcc 16 + nvcc 12.9 即便加了
-allow-unsupported-compiler 也死在 gcc 自己的 <type_traits> 里),以及工具包
旧于 C 库(12.9 的 crt/math_functions.h 为宿主重声明 C23 的
cospi/sinpi/rsqrt 不带 noexcept,而 glibc 2.41+ 带)。
同一个缝下还有一份 CPU 实现,由 cfg(not(accelerator = "cuda")) 选中,于是
mcpp build --no-accel 编它、mcpp build 编 .cu,两侧都不需要手写条件。
实测(RTX 4080,驱动 550.144.03 报 CUDA 12.4,LLVM 22.1.8):mcpp run 与
mcpp run --no-accel 都打印 12 24 36 48,来自不同的产物目录,后者不含
cudaMalloc。
v2026.9.5.1
⭐⭐ 加速器支持:设备编译单元、产物身份的加速器维、以及没人做的宿主编译器配对
一个为某个计算能力编译的库,被另一个计算能力的构建消费时,链接干净地完成,
程序在第一次 kernel 启动时失败,消息里既没有包名也没有任何一侧期望的架构。
C++ 构建生态里没有任何一个系统把「这个二进制是为哪个架构编的」记进它的身份 ——
这是整个品类的空白,不是 mcpp 特有的。
SourceKind::Device。 .cu 与 .hip 是设备编译单元:从不被扫描 import,
从不产出 BMI —— 没有任何设备编译器接受 C++20 modules。.cuh / .hiph 是头文件,
改动其一仍使快路径失效。设备扩展名刻意不进默认 source glob,理由与内置模块
扩展名表停在 .cppm 的理由相同:放宽它会让一个 vendored 了设备源码、在别处构建
它的已发布包在下次升级后突然开始编译它,而这是作者无法修复的破坏。
产物身份的加速器维。 携带设备代码的产物把它记在兼容性标签旁边,tag_check
比较它。成员判定按两条硬件里真实存在的机制放宽:家族目标覆盖同 major、minor 不低
于它的范围;内嵌的可移植形式覆盖下界之上的一切。AMD 两者都没有,靠 archs 一侧的
generic target 取得同样的覆盖,所以空的下界不放宽任何东西。
字段与标签并列而不是标签的一段,因为架构列表是集合,而标签是用 - 拼接、
其 triple 本身含数量不定 - 的字符串。一个比较器,两个存储位置。
accelerator 作为多值 cfg layer。 一次构建可以同时启用多个后端。比较是处处
成员判定,而不是只在 any(...) 里 —— 让组合子改变操作数含义会使
all(accelerator = "cuda", accelerator = "rocm") 变成不可满足,而不是「两者都启用」。
宿主编译器上界,读而不抄。 nvcc 拒绝比它在自己的 crt/host_config.h 里声明的
上界更新的宿主编译器,而 mcpp 的载荷常常更新。因为宿主编译器由 mcpp 提供,
它可以在任何编译发生之前作答。上界从工具包读出,所以一个 mcpp 从未见过的工具包
同样能作答;解析不了的头文件不产生上界,也就不产生断言。
[build] accel / --accel / --no-accel,以及 [package] accelerators。
前三者之间的关系与 [toolchain] 和 --target 相同。--no-accel 是显式请求
「不要加速器」,这是在一个同时发布了设备构建的包中选中 CPU-only 变体的方式。
[package] accelerators 与 platforms 同形,并刻意与产物的 accel 是不同字段:
声明由人手写,产物字段从构建测量。
真机验证(RTX 4080 / CUDA 12.0 / 驱动 550.144.03):examples/09-cuda-kernel
经规则包编出设备岛并运行,mcpp run 输出 12 24 36 48。
设计与调研:.agents/docs/2026-09-05-accelerator-support-design.md、
.agents/docs/2026-09-04-ai-accelerator-toolchain-ecosystem-survey.md。
新增手册章节 docs/20-accelerators.md(中英双份)。
v2026.9.4.3
⭐⭐ mcpp run 报告程序自己的退出码
在此之前所有非零退出码都被折成 1,为的是让 2 表示「起不来」以区别于「跑了但
失败」。区别值得保留,代价不值得:main 返回 3 的程序让 mcpp run 退 1,
qemu 报 3 的裸机镜像同样到达为 1。一条报不出退出码的命令没法写进脚本,而这
正是 mcpp run 的主要用途。
取值空间分三段,只有第一段属于程序:
中间那段是 env、timeout、nice 早已在用且被 shell 文档化的取值,所以 126
与 127 带着惯常含义到达。程序自己也可以退 125–127,mcpp 不靠数字区分 ——
启动失败一定向 stderr 写出原因,程序自己的退出码从不写。
mcpp test 不变,仍为 0/1:它聚合多个程序,没有单一退出码可透传。
兼容性:mcpp 自身的配置错误仍是 2,与其余所有命令一致 —— 变的只有「尝试
启动后被拒」这一种情况,而那时程序根本没运行。契约写在 docs/11 §6。
⚠️ ⚠️ 没有构建进程能比启动它的 mcpp 活得更久
实测:每一次被 timeout 终止的 mcpp run 都留下一个空转占满一个核的 ninja,
其中一个的工作目录已经是 (deleted)、比它所属的整个沙箱活得还久。任何用
timeout 包住 mcpp 的 CI,每超时一次泄漏一个忙核。
子进程现在进入自己的进程组(Windows 上是 job object),mcpp 在收到
SIGINT/SIGTERM/SIGHUP 时对该组发 SIGKILL。用 SIGKILL 而不是 SIGTERM 是必要
的:ninja 把信号记进标志位,只在等待子进程处才检查;一个没有命令在跑的 ninja
永远到不了那个检查点,礼貌的信号被记录且永不执行 —— 那正是那些孤儿所处的状态。
守卫从单槽改为多槽登记表:一个跨构建的 [hooks] 命令与构建自己的 ninja 会
同时被守卫,单槽会让后注册者解除前者的守卫。
MCPP_NINJA_DEBUG
设置后向 ninja 追加 -d <topics>(如 explain)。用于那个还没定因的 ninja 空转:
ptrace_scope=1 与 perf_event_paranoid=4 都取不到栈,而把 gdb 变成 ninja 的父
进程之后空转就不再发生 —— -d explain 是唯一能对这种进程取证的手段。
