Releases: mcpp-community/mcpp
Release list
v2026.9.29.3
This release completes the runtime placement of a program that a workspace
member ships through artifacts. The validation project's post-release build
of 2026.9.29.2 found it.
Fixed
- A program shipped through
artifactsno longer waits for a runtime file
that a workspace plan never places. Its link edge depended on the plan's
own deploy set, which a workspace plan does not place (bin/holds products
only), so a workspace whose members declare runtime files stopped with
"missing and no known rule to make it" (e2e 833 G9). - The runtime files of such a program are beside it. A member's runtime
set includes the closure of every package whose program the member ships
throughartifacts, so the program finds its own runtime files in the
member's product directory, as it did beside a root's program inbin/
(e2e 833 G9).
v2026.9.29.2
This release corrects what a workspace plan reads from its members. The plan's
root is a virtual root that holds the values shared by the whole graph; five
statements that a member makes about itself were read from that root in
2026.9.29.1 and were therefore empty, and a member's product directory lacked
the shared libraries that its libraries need. The mcpp-index sweep of
2026.9.29.1 found the first two.
Fixed
- A member's own relative
[indices].pathis anchored at the member. It
was resolved against the workspace root, so a member that declares its own
index (47 members of mcpp-index do) found no package. Two members that name
one tree with different relative paths are now one configuration (e2e 120). - A member whose tests import
stdis tested with the std module built.
The decision read the entry files of the root's targets only, and the virtual
root has none; a member whose only sources are its tests failed to compile
them (e2e 836). - A member's
[resources]andwindows_code_pagereach its own images.
Each selected member's resources are compiled against the member's directory
and include directories, underres/<member>/, and embedded into the
member's programs and shared libraries only (e2e 837). A quoted#include
or resource file in a script is now found through the include directories as
the resource compiler finds it, in every build. - A member's product directory holds every graph-built shared library of its
closure. Only the libraries a member's own units link were placed, so a
library that another library needs (libffi under libwayland-client) was
missing and the program did not start (e2e 835 L3). [build] linkageis a value of the plan. It chooses the C runtime that
every object is compiled against, so members that differ in it are separate
configurations, and the plan takes it from its members.- A member's manifest is checked as a root's. An unknown capability under
the reservedmcpp:prefix is refused, and a cfg() predicate mcpp cannot
evaluate and the other schema warnings are reported, for each selected
member (e2e 836).
v2026.9.29.1
This release builds a workspace as one graph per configuration. The selected
members and everything they depend on are planned together under a virtual
root, so a member that several members use is compiled once; each member's
products are placed in its own product directory. It also fixes the planning
regression of 2026.9.28.3. The design, its readings and the task plan are in
.agents/docs/2026-09-29-workspace-build-graph-design.md. The xlings pin moves
to 2026.9.29.1.
Fixed
- Planning a workspace no longer grows with the length of its dependency
chains (2026.9.28.3 only). E1 planned a shared member as the root of a
nested build, and the nested build met the same condition again, so a chain
of n members cost 2^n plans: a nine-member workspace with nothing to build
took 79 s instead of 6.3 s, and a chain of five libraries and a program took
36.8 s for--workspace. E1 is removed. The same chain now plans once
(0.5 s), and with nothing changed--workspace,-p appand a build inside
the member are each answered by the fast path in a few milliseconds (e2e
834).
Behaviour changes
- A workspace is one graph per configuration. A command on a workspace
plans its selected members together:--workspace, and a virtual root
without-p, select every member (a rooted workspace's own package
included);-p X, and a command in X's directory, select X; a command at a
rooted workspace's root selects its own package. Members that share their
toolchain request, target, C++ standard and the other values that apply to a
whole graph are one plan, with onebuild.ninja; members that differ are
separate plans, built at the same time under a static share of the jobs. A
member used by several members is compiled once (e2e 833). - Build directories are at the workspace root. A member builds in
<workspace>/target/<triple>/<configuration>/instead of its owntarget/.
Its programs and shared libraries are in its product directory,
bin/<package name>/(bin/<namespace>.<name>/when two members share a
name; a rooted workspace's own package keepsbin/), with the shared
libraries and runtime files its programs load beside them. The first build
after the upgrade is a full build;mcpp clean --staleremoves the build
directories members held under their owntarget/. A member's build program
still writes to<member>/target/.build-mcpp/. -p Xplans X and what X reaches, in the shared build directory.
mcpp build --workspacefollowed bymcpp build -p Xcompiles nothing; a
package is compiled again only when its active features differ between the
two commands.- The directory name is the configuration. It hashes the toolchain, the
target, the standard library, the runtime contract, the C++ standard, the
dialect flags, the profile and the other values every node of one graph
shares. A package's owncflags,cxxflags,ldflags, defines, sources and
include directories reach its commands instead: editing them recompiles that
package and what imports it, in the same directory, rather than moving the
whole graph to a new one. This holds for single-package projects as well. - One lock and one compile database per workspace.
mcpp.lockis at the
workspace root, as it already was for a rooted workspace;--workspace
writes the whole record and-p Xupdates the entries of X's graph. Where
the workspace has no lock yet, a selected member's own lock supplies the
git commits. The workspace root'scompile_commands.jsoncovers every member
that has been built or configured. - A member keeps its duties as the project being developed. Its targets
are built, its[dev-dependencies]are loaded undermcpp test, its
[hooks]run around the build, its[xlings]entries withwhen = "dev"are
installed, and its build program runs after every dependency's program with
the graph document of what it reaches (in which it is theroot) and the
dependencies' link forms.--features factivatesfin each selected
member that declares it and is refused when none does. - Each member links its own closure. A member's programs link the flags of
the packages they reach and place the runtime files of those packages, never
another member's. - Shared libraries are placed by a hard link. A shared library placed
beside several programs is one file with several names where the file system
supports links, and a copy elsewhere. Other deployed files are copied, since
a program may write a file beside itself (e2e 835). - Files served from the global cache are staged by a ninja pass of their
own, before the build that reads them. ninja 1.12.1 crashed when a cached
dependency was staged again in a directory whose scans were current
(ninja-build/ninja#2662); a directory named by its configuration makes that
the ordinary case of a dependency upgrade. - The fast path records the request.
build.ninjastates the workspace
members and the features it was planned for, and a fast path replays it only
for the same request; one record is kept per selection and configuration. mcpp.graph. One module holds the engine's graph algorithms: the
topological orders the engine uses, dependency levels and the transitive
closure. Module order, host-module order, unit order by imports, the
dependency closure, the package-cycle check and the build-key fold use it,
each with the order it had, so no compile or link order changes; a cycle of
modules is now reported asa -> b -> a.
v2026.9.28.3
This release implements mcpp's part of the #734 design: the three layers of the
build-plugin architecture (mcpp.core, the official general library, plugins),
build information for build programs, workspace members built once, batched
placement, pack -p, phase 1 of the library-interface specification, and a
package's engine floor. The design, the measurements, the tasks and the
implementation record are in
.agents/docs/2026-09-28-build-cost-foreign-toolsets-and-library-surface-design.md;
the specifications are SPEC-007 §9 and the new SPEC-008. mcpp-plugins 0.17.0
states this release as its floor.
Behaviour changes
- A workspace member used as a path dependency is built once (E1). Before a
consumer builds, the member is built once in its own directory, as the root of
its own build, and its ninja decides what is stale (headers outside the
member's root included); consumers take its objects and BMIs through stage
edges that compare content. The condition is that the member's build-key
inputs in the consumer's graph equal its inputs as a root (package.index
excepted, which records only where it came from); otherwise it is compiled in
the consumer's graph as before, and-vnames the differing inputs. Two
consumers built at once take the member's directory in turn under a file
lock. The core library of a five-member Windows workspace was compiled three
times before. - One process places the files beside a program (E4). Two or more deploy
entries of a program become onestage_listedge reading the
placements.listthe plan writes;mcpp stage --listkeeps the single-file
semantics per destination. On a first Windows build 1270 single-file
placements took 4.5 s; one process copying the same files takes 0.5 s. - Phase 1 of the library interface (E6, SPEC-008).
mcpp packnames the
exported modules outside the shipped closure (W2), and its "Withheld" row lists
every unit not shipped (it read "(nothing)" for a package without an interface
root while two exported modules were neither shipped nor listed);mcpp build
warns when a package imports a non-public module of a dependency that is not a
member of its own workspace (W3); the warning for a missing interface root
states its consequence formcpp pack(W1). All three are warnings. - The fast path resumes after a confirmed edit. The build after an edit
confirmed the graph through the full path but did not rewrite an unchanged
build.ninja, whose time the fast path compares every source with; every
later build was declined until the graph's text changed. Confirming the graph
now moves build.ninja's time (e2e 832; 2026.9.28.2 shows the same on Linux). - The fast path sees a path dependency's whole tree. Only the dependency's
src/was swept, so an edit to a host module elsewhere (mcpp-plugins'
deps/vcpkg.cppm), which is compiled into the consumer's build program and
named by no ninja edge, was reported as "no work". The whole tree is swept
now, skipping hidden directories,targetand nested packages (e2e 831). - One line per download. Output that is not a terminal printed
Downloading <item> (<size>)at the start and... done, <size> in <time>at
the end; it now prints the completion line only, or the line saying the item
did not complete. On a terminal the bar is redrawn in place and ends with the
same completion line, which now states the size and the time. - clang on the MSVC ABI says when no toolset is found. With no Visual
Studio instance carrying the C++ tools, the defaultmsvc@systemcontinued
silently and the build failed at'cstdio' file not foundwhile precompiling
themcppmodule; the resolution now warns and names the commands that
install and select a managed toolset (measured on a runner whose Visual
Studio was masked). mcpp.corecarries noFILEinto its interface.mcpp::reportwrites
throughprintfonly; GCC rejected a build program that includes<cstdio>
afterimport mcpp;(e2e 651).
Features
mcpp.core(E8, protocol 14). The engine's interface is named
mcpp.core;mcppis its permanent equivalent.- Build information (E2, protocol 14).
mcpp::tool(role),abi_tool(role),
tool_env(),toolset_identity(),msvc_instance_dir(),ninja_program(),
cxx_runtime()andmsvc_crt_linkage()state the resolved toolchain and the
program's C++ runtime contract as facts, read from the producers the engine's
own command lines read. - Structured diagnostics (E11, protocol 14).
mcpp::report({severity, message, impact, hint})is rendered in the engine's
form, enters the JSON output, and is replayed on a cache hit. - A missing build-program module names its feature (E7). When a module is
provided only behind a feature the dependency edge does not enable, the error
names the package, the feature and the line to add. - Plugin module names (E10).
mcpp.<own namespace>.*draws no warning; the
reserved second segments (core,plugins,deps,rules,dist,
tools) belong to packages in namespacemcpp. mcpp pack -p <member>(E3). A member is packed from the workspace root,
with the result a pack in the member's directory gives.- A package's engine floor (E9).
[package] mcpp = ">=<release>"and
[workspace.package] mcpp; an engine below the floor stops before any other
work and names the upgrade command; only>=is accepted. [lib]reports unknown keys (E12). A misspeltpathwas accepted
silently.- The fast path on macOS, Windows and SDK-sysroot targets (E5). The runtime
validation holds ELF/glibc rules only and returned without writing its record
on these targets, so the fast path found no validated snapshot and every build
took the full path (read on macOS in e2e 645, 831 and 832: "no validated
artifact snapshot is recorded"). These targets now record the artifacts'
stamps with a Pass verdict (no rule applies); a relinked artifact still sends
the fast path to the full build. Under-veach refusal is stated as a
sentence.
Release process
- The release gate holds the ecosystem's own projects only. A downstream
project's canary is removed from.github/release-canaries.toml; a downstream
project (a user's application, or a fork of one) validates a release in its
own pull request after the release. xlings and mcppls remain. - Commit messages, pull requests, CHANGELOG entries and release notes are
written in English from this release on; release notes are the CHANGELOG
section of the release. Earlier entries remain as written.
Compatibility
- Protocol 14: a build program that uses the new interfaces of §9 fails to
compile on an older engine, naming the missing name; a package that states a
floor with[package] mcppreceives an "unsupported key" warning from an
older engine. - The first build after the upgrade pays once: every build program runs again,
because its context gains variables; a shared workspace member is staged into
its consumers and compiled once in its own directory; the placement edge
changes shape and runs once (a file with equal content is not rewritten).
v2026.9.28.2
本版本实施 2026-09-28 生态设计中 mcpp 的部分(WS1、WS2、WS3、WS7、WS8、WS10 与决定 D7),关闭
#728 与 #729,并完成 #718 中运行时放置的一半。设计、任务划分与实施记录见
.agents/docs/2026-09-28-ecosystem-design-and-optimisation-plan.md,其依据见
.agents/docs/2026-09-28-ecosystem-review-of-two-days-of-mcpp-and-xlings.md。配套的 xlings
2026.9.28.2 见 openxlings/xlings#628。
行为变化
- Windows 程序旁的文件由一个解析器决定(WS1,SPEC-006 §3.7.1)。 声明的来源(
[runtime] deploy)优先于工具链的来源,工具链的来源优先于推导的来源(运行时搜索目录中找到的 DLL)。MSVC C++
运行时作为一个有版本的集合决定:放置工具集的集合,除非某个搜索目录提供版本严格更新的完整集合,
此时放置该集合并说明一次;依赖包自带而未被放置的副本作为打包缺陷说明一次;声明的运行时文件比
工具集的旧时给出警告;读不出的版本不参与决定。版本取自 PE 文件的VERSIONINFO。契约决定是否
携带:toolchain-coupled携带;host-coupled不放置任何副本,并拒绝声明的副本(拒绝理由
crt-declared-under-host-coupled);self-contained只在依赖包带来运行时名字时放置。规划、
链接后的place-dlls(--crt、--toolset-crt)与mcpp pack读同一个答案,resolution.json
的runtime.placement、runtime.crt_set与runtime.placement_notes记录它。此前链接后的放置
按搜索次序取第一个提供vcruntime140.dll的目录,Qt 载荷中比工具集更旧的副本因此被放到程序旁。 - 每个 action 运行时,工具集的运行时目录位于
PATH最前(WS1,决定 D3)。 面向 MSVC ABI 的
构建经 action 包装器的--path-prepend设置它,与mcpp run、mcpp test相同;依赖包不带运行时
发布的宿主工具(Qt 的moc.exe)因此能够启动。系统目录在加载器的搜索次序中先于PATH,装有
VC++ redistributable 的机器仍使用系统的副本。 - 每个宿主上都跟踪头文件依赖(WS2)。 GNU 方言的编译器在每个宿主上写 depfile。Windows 上的
clang++(自 #718 起为 LLVM 行的默认)此前不写,编辑模块 purview 中#include的文件不会重新编译
导入者;Windows 上 GCC 的模块 depfile 由mcpp depfile-filter过滤,它运行编译命令本身,不需要
shell。emits no GNU depfile的降级提示随之删除。 - 每个事实每次运行陈述一次(WS3)。
mcpp.diag在一个进程中按(域、文本)只打印一次,工作空间
各成员共有的事实因此只打印一次;说明(note:)是独立的严重级别,--strict不提升它。继承自
[workspace.build]的冗余 CRT 参数指向[workspace.build],不再指向不含它的成员[build]。
构建边在成功时要陈述的事(放置比较出的差异,两个目录提供同名 DLL 时的选择)写入
.mcpp-advice/<该边的输出>.advice,构建成功后由完整路径与快速路径共用的一个函数报告一次
(SPEC-007 R4.5);此前这些内容只在构建失败或-v时可见,规划期对同一事实的第二次陈述随之删除。
emit build-database的信封按成员列出各自的诊断,代码由域得出(build/msvc-crt-word为
MCPP_BUILD_MSVC_CRT_WORD)。 - 命中同一目标的条件表按选择器的具体程度应用(决定 D7,#728)。 更具体的后应用,因而胜出:
三元组高于任何 cfg 表达式,OS 高于族,cfg(all(...))按其固定的三元组分量计数;具体程度相同时
按选择器文本排序。此前按选择器文本的字典序应用,aarch64-unknown-linux-gnu先于linux,其标量
被后者覆盖(SPEC-004 §3.1.1)。
特性
mcpp self env --format json报告defaultToolchain(WS8)。 该值由
pins::host_default_toolchain一个函数回答,即首次运行安装的工具链;docs/01 与 docs/20 的表格在
每个 CI 宿主上与之核对(.github/tools/check_default_toolchain_docs.py)。
内部
- CI 的步骤断言其名称所说的事(WS7,#729)。
.github/tools/check_workflow_assertions.py检查
工作流:经管道的构建须在pipefail下运行(W1),被丢弃的退出状态须被读取(W2),已知为红的任务须
标注其 issue(W3);它在origin/main的工作流上报告 7 处。LLVM 自构建改用 llvm@22.1.8,并以
set -o pipefail断言构建本身,函数规模门(check_function_sizes.sh)在其后运行。xcode-27 任务
标注 #669 并允许失败。 - 发布门(WS10)。
.github/release-canaries.toml列出真实工程,release-canaries.yml以候选
mcpp 构建它们,release.yml的打 tag 任务依赖其结果;tests/release/verify-published.sh在沙箱
中验证已发布的 mcpp 与 xlings,每个发布项一节,并保留此前各版本的小节;PR 模板要求列出每条新规则
所跨越的既有不变量与位于交点的测试。 - 测量任务。
measure-windows-tool-crt.yml在带 Visual Studio 与屏蔽 Visual Studio 的两个
Windows 行上隐藏系统的 C++ 运行时,测量 Qt 的宿主工具能否只经 action 的PATH启动(设计 §2.9),
它是从xim:qt-base中移除运行时副本的前提。 - xlings 固定版本为 2026.9.28.2。 interface 协议 1.3:
download_progress带stream且发送
频率有上限;home 以.xlings-home声明;update只构建一次索引(openxlings/xlings#628)。
兼容性
- 运行时搜索目录中带有较旧 MSVC C++ 运行时副本的 Windows 程序,现在得到工具集的副本;差异以一条
说明陈述,不再在每次链接时警告。 - 在
host-coupled下声明 MSVC C++ 运行时文件的 manifest 被拒绝。 - 若干条件表命中同一目标、且字典序与具体程度给出不同次序的 manifest,其标量取值与列表参数的次序
随之改变。 - Windows 上以 GNU 方言编译的工程,编译命令多出 depfile 参数,升级后第一次构建完整重建一次。
- 面向 MSVC ABI 的构建中,每个 action 的命令行多出工具集运行时目录(
__action --path-prepend),
升级后第一次构建中每个 action(包括check与prepare)重新运行一次。
v2026.9.28.1
本版本合入 #717、#718、#720、#722、#723、#724、#725 与 #726 的修复与特性。设计与实施记录见
.agents/docs/2026-09-27-eight-reports-by-home-and-one-optimisation-plan.md 与
.agents/docs/2026-09-27-eight-reports-implementation-plan.md。
缺陷修复
-
带
[package]的工作空间根的path依赖(#725)。 这样到达的成员此前不被识别为成员:
2026.9.26.1 忽略其[workspace.dependencies]中钉住的版本,2026.9.27.1 拒绝其
workspace = true。现在工作空间的上下文由清单所在的位置决定,这样的成员按 SPEC-004 §9 第 1 条
继承[workspace.package]、[workspace.build]与workspace = true;[toolchain]、
[target.<triple>]、[indices]仍只属于根。 -
-p, --package <NAME>按包的身份解析(#725)。 顺序为限定名、包名,然后是成员的路径或
目录名(此前唯一的写法,保留)。同一个包名在两个命名空间下出现时拒绝并给出两个限定名;一个值
既是某成员的包名又是另一成员的目录名时选择前者并警告。 -
宿主模块包的 lib root 参与本包单元的导入排序(#720)。 导入同包其他单元的 lib root 此前
先于被导入者编译而失败。不导入同包单元的包顺序不变。 -
规则认领的设备源不是编译单元(#724)。 它此前出现在 S1 文档与
mcpp build自己的
compile_commands.json中,带一条 C++ 编译命令,build.ninja也带一条无人引用的边。 -
构建程序失败时,其诊断得以保留(#724)。 以构建程序的指令为前提的检查(设备源的认领)不再
对构建程序已失败的包运行,规划失败路径也保留已记录的说明;此前报告的是「设备源无人编译」。 -
emit build-database不写入工程目录(#724)。 声明了[xlings]载荷的工程此前会得到
.mcpp/.xlings.json(SPEC-005 R2.1)。 -
一个放置目标一份内容、一个写入者(#723)。 同一目标的多个来源在放置时逐字节核对,相同则
放置一份,不同则失败并点名全部来源;此前在规划时即被拒绝,即使内容相同。链接后放置 DLL 的步骤
不覆盖另一写入者放在程序旁的文件;规划时在运行时搜索目录中找到的同名 DLL 让位于声明的放置,
内容不同时在规划时警告(SPEC-007 R4.2、R4.3)。PE 目标上目的地的比较不区分大小写。 -
只导入构建规则的
build.mcpp在 GCC 下可以编译。 它此前在工程根目录中编译,找不到位于
构建目录gcm.cache中的规则 BMI;现在导入任一模块的构建程序都在构建目录中编译。 -
要求更新 mcpp 的索引不再报告为错误。 读取处不再打印
error: ... [E0006];失败的运行在
使其停止的消息中给出 E0006;刷新了索引而遇到下限的运行在最后打印一行tip:,信封中为说明
MCPP_INDEX_REQUIRES_NEWER_MCPP;mcpp self doctor列出当前 mcpp 不满足其下限的索引。 -
Windows 上一次 xlings 调用只作用于 xlings 子进程(#726)。 此前的 Windows 实现在两处与
POSIX 不一致:- 每次调用把 registry 的
subos/default/bin加到进程PATH的最前面,并设置进程级的
XLINGS_HOME,调用后不恢复。同一次构建安装过载荷后,ninja 与每个动作都先找到xim:llvm
注册的cl、link、lib、rcshim,vcpkg 对宿主三元组的编译器检测因此失败。 - xlings 在 mcpp 的工作目录中运行,从那里向上找到工程的
.xlings.json而进入工程模式,把 mcpp
工具链与载荷的 shim 写进工程的 SubOS。
现在
ScopedInvocationEnv在调用期间应用XLINGS_HOME、作用域变量与PATH前缀并在调用后
全部恢复;命令以cd /d "<home>" &&开头,与 POSIX 前缀中的cd相同。 - 每次调用把 registry 的
特性
- 条件化的
dialect_cxxflags(#717)。[target.<selector>.build] dialect_cxxflags在命中的
目标上把参数加入全图的方言参数:标准库 BMI、扫描与每个编译单元。只读取构建的根包;依赖包自己的
全图键不进入其指纹。 - MSVC ABI 的 CRT 模型(#718)。 CRT 是目标 ABI 的性质:cl 以
/MD、/MT,clang++ 以
-fms-runtime-lib=dll、static表达同一模型,并到达编译、std BMI 与链接。MSVC ABI 的默认契约为
toolchain-coupled:动态 CRT,工具集的vcruntime140.dll、msvcp140.dll放到程序旁。
cxx_runtime与linkage之外不增加新键;手写的 CRT 参数与模型一致时提示冗余,矛盾时拒绝;
依赖包[build] cxxflags中矛盾的 CRT 参数同样拒绝;调试 CRT 参数(/MDd等)总被拒绝。依赖
的全局缓存键包含 CRT 模型。 - 构建数据库描述规则生成的文件(#724)。 S1 集合的
ide.generated列出生成的文件与目录、
同一组选择下mcpp build写入的路径以及生成它的步骤(S1 0.3.0)。命令仍不运行任何 action。 - 统一的下载进度。 工具链与载荷的安装、索引中的库包、
[xlings]载荷、索引刷新、git依赖的
克隆与沙箱的首次引导由同一个渲染器报告。标准输出不是终端时,每一项只打印开始与结束两行,不含
回车与擦除序列。索引刷新经xlings interface update_packages进行,需要 xlings 2026.9.28.1 的
进度事件才逐步显示。
内部
src/build/prepare/的阶段函数按其小节拆分(#722)。 代码逐字移动,不改变语句顺序;七个
夹具的resolution.json、build.ninja与构建数据库输出与拆分前逐字节相同。该目录下没有超过
400 行的函数:.github/tools/check_function_sizes.sh以 clang-tidy 的readability-function-size
在以 LLVM 构建得到的编译数据库上检查,拆分前报告 10 处;CI 中尚无能完整构建 mcpp 的 clang 任务,
接入见 #729。mcpp.lock与resolution.json的写入移入records.cpp。- xlings 固定版本为 2026.9.28.1。 该版本的 interface 协议为 1.2:
update_packages按阶段发出
进度事件,interface 能力运行期间写到标准输出的文本不再混入事件流(openxlings/xlings#625)。
兼容性
- 成员的编译命令可能改变。 经
path依赖到达、带[package]的工作空间根的成员,现在收到
[workspace.build]与[workspace.package]。 - Windows 上 LLVM 行的程序改用动态 CRT。 它们现在导入
vcruntime140.dll等,文件放在程序旁;
写cxx_runtime = "self-contained"可恢复静态 CRT。cl 行的编译参数不变,程序旁多出这些 DLL。 - 新增四种拒绝。 每一种都在消息中给出一行修法:
- 手写的 CRT 参数与解析出的模型矛盾;
- 在没有 redistributable 目录的行上显式写
toolchain-coupled; - 在两个命名空间下都有成员的包名上使用
-p; - 手写的调试 CRT 参数,以及依赖包中与模型矛盾的 CRT 参数。
-p的值同时是一个成员的包名与另一个成员的目录名时,选择前者并警告。 此前按目录名选择。- S1 profile 版本为 0.3.0。 0.2.0 的消费方忽略新字段。
v2026.9.27.1
缺陷修复(#704、#705、#710、#712 至 #716)
- 宿主构建读取宿主三元组的行(#704)。 不带
--target的构建此前不读取
[target.<宿主三元组>],其中的cxx_runtime没有效果。现在宿主构建应用这一行,与
--target <宿主三元组>相同,行的查找与拼写无关;--toolchain仍优先于行的toolchain
(SPEC-004 §4.6)。 xpkg_dir回答 xlings 装下的载荷(#712、#716)。 版本位按 xlings 的版本文法求值,
libglvnd@1.7回答1.7.0.1。xlings 在install_targets事件中报告每个请求解析到的载荷,
mcpp 按地址记录并优先读取。安装记录仍在而载荷已被删除时,联网构建重新安装,离线构建拒绝并
点名缺失的地址;构建缓存记录读取过的载荷目录,任一目录缺失时快路径不复用缓存。mcpp 以
xlings 发布的版本选择向量测试同一规则(SPEC-001 §10.1)。- 宿主工具的工具链由请求方决定一次(#710)。 顺序为
--toolchain、工具包自己的声明(工作
空间成员先继承根位置的键)、请求方的宿主工具链;结果传给子构建并写入工具库的键。工具包的源树
摘要跳过带自己mcpp.toml的子目录,并以 UTF-8 计算路径(#705)。 - 工作空间(#713、#714)。 成员隐式继承根的
[xlings.workspace]条目与条件行,自己声明的
同一个包优先。未解析的workspace = true在根包、-p成员与各类依赖处都被点名拒绝;带
[package]的工作空间根解析自己的条目。[build] sources = []不再推断库目标。 - 规则只经其声明的扩展名到达(#715)。 一个规则只在包含它声明的设备源扩展名时进入包的合成
构建程序;状态行只列出实际生效的规则。
规划不构建宿主工具(#707)
mcpp emit build-database 不再构建依赖提供的宿主工具。工具库中已有的工具照常使用;没有的工具被
推迟,报告为 note MCPP_BUILD_DATABASE_HOST_TOOL_DEFERRED,请求它的构建程序收到该工具将被发布的
路径。它取代 2026.9.26.2 的警告 MCPP_BUILD_DATABASE_HOST_TOOL_UNBUILT(SPEC-005 v1.4 R2.5)。
mcpp build 不受影响。
构建程序与依赖边(#708、#709、#711)
- action 的
env与cwd(协议 13,#708)。mcpp::action::env(name, value)与
cwd(dir)由引擎的 action 包装器应用;cwd按包根解析,声明的输入、输出与 stamp 不随它
移动;变量值改变时 action 重新运行。两者都未声明的 action 命令行与协议 12 逐字节相同。 - 特性的
tools(#709)。[features.<f>] tools = ["<bin>"]陈述特性需要本包的程序在
构建机器上运行;启用该特性的消费方得到该工具,与边上写tools相同。 artifacts依赖边(#711)。x = { path = "...", artifacts = ["<bin>"] }以消费方的
目标与 profile 构建依赖的程序,输出到消费方的bin/,不链接依赖的代码;action 以
${mcpp.artifact:<x>/<bin>}引用它;mcpp pack把它放在程序旁(SPEC-004 §10、
SPEC-007 R6.4)。
载荷的打包修订与自包含的 locale(xlings#620、#621)
- 依赖的索引条目的
revision进入依赖完整性判断:修订号不同的已安装依赖按未安装处理。修订号
大于 0 的运行时载荷进入运行时契约(revision=<n>),修订号变化使依赖它的产物重新链接;修订号
为 0 时契约文本不变(SPEC-001 §10.2)。 mcpp pack的 bundle-all 形态随 glibc 载荷复制lib/locale与lib/gconv,启动脚本设置
LOCPATH与GCONV_PATH(用户已设置时保留用户的值)。
兼容性
以下变化对已有工程可见:
- 未解析的
workspace = true此前静默成为空版本依赖,现在在加载时报错并点名条目(#714)。
修正方法是在工作空间根的[workspace.dependencies]中声明该包,或在条目上写明版本。 [build] sources = []不再推断库目标。依赖这一推断的包在[targets]中声明库目标。- note 代码
MCPP_BUILD_DATABASE_HOST_TOOL_UNBUILT由MCPP_BUILD_DATABASE_HOST_TOOL_DEFERRED
取代,不保留旧代码。
其他
- 测试:e2e 804 覆盖不属于任何工作空间的路径宿主工具包的工具链选择(#710);e2e 802 固定 gcc
工具链,使其断言不依赖机器的默认工具链;单元测试test_prepare_helpers覆盖各阶段共用的纯函数
(--features请求语法、宏名、git 远端是否本地等)。 - ELF 检查以一次读取载入文件。此前逐字节读入,
mcpp test的链接后检查在测试程序较多时耗时
数十分钟。 src/build/prepare.cppm的内部分解。 原文件 16,105 行,其中函数prepare_build占 85%。
它拆为按阶段划分的函数,跨阶段的状态集中在PrepareState(取代原来约 180 个共用一个栈帧的
局部变量)。主接口prepare.cppm只保留导出的类型、内联函数与声明;实现位于src/build/prepare/下的
实现单元与实现分区:state,各单元只导入自己用到的模块,每个文件不超过 2,500 行,由 CI 检查。
代码逐段移动,不重排语句;公开接口、导出符号与默认参数不变,七个场景的规划输出与分解前逐字节
一致。修改一个阶段只重新编译该实现单元:本机修改 P13 中的一处字符串,增量构建 12 s,分解前
71 s。模块组织受 GCC 16.1 的内部编译器错误约束,见 #721。- 规范:SPEC-001 v1.5、SPEC-004 v1.8、SPEC-005 v1.4、SPEC-007 v0.3。
v2026.9.26.2
编译数据库:一个配置一个数据库(#699 的报告;#397 C-1、#677 B1)
compile_commands.json 此前是一份跨命令、跨工具链合并的文件:删除后的下一次构建不再写回它,
另一个工具写入的条目与上一个工具链的条目都被保留,directory 写的是工程根目录而编译器在输出目录
中运行。现在:
- 每个配置一份数据库,位于
target/<triple>/<fingerprint>/compile_commands.json,只在本配置之内
合并,因此mcpp test之后的mcpp build保留测试单元的条目。工程根目录的文件是当前配置数据库
的副本,整体替换、从不合并,内容相同时不写;切换工具链或 profile 就切换整个文件,切回时恢复该
配置的条目。根目录的符号链接照旧按其指向写入。 - 根文件被删除后,下一次构建从配置数据库恢复它,快路径也是如此,不需要规划。
- 被替换的根文件含有 mcpp 没有写的条目时(条目的
output不在本工程的target/或 mcpp 主目录
之下),构建以一条警告说明其数量。 directory与 S1 的work-directory是编译器运行的输出目录,与 JSON Compilation Database 格式、
S1-8-2 以及 CMake、ninja 的写法一致;从directory重放 GCC 的条目不再在源码树中写出
gcm.cache/。- 提供模块的单元在
-c之前带上接口语言 flag(clang 为-x c++-module,GCC 为-x c++),
clangd 因此能处理.ixx接口;MSVC 的写法待 Windows 实测后再加。 - 导入
std的构建在compile_commands.json与emit --spec compile-commands中列出标准库单元
(S1-12-1),另一个版本的 clangd 因此能自己构建std。S1 文档中标准库单元的provides指向
共享 std 缓存中的 BMI,工具链带build-id,版本与构建身份完全一致的读者可以直接复用。 mcpp new写出的.gitignore包含compile_commands.json。
emit build-database 按成员规划(#699)
emit build-database --workspace 此前在第一个规划失败的成员处停止,已规划成员的集合全部丢失。
现在每个选中的成员独立规划:规划失败的成员不贡献集合,贡献一条 error 诊断,path 为它的
mcpp.toml;至少一个成员规划成功时文档带 data,watch 也列出失败成员的 mcpp.toml 与
build.mcpp;有任何错误时退出码为 1。在 emit 下,构建失败的宿主工具是警告
MCPP_BUILD_DATABASE_HOST_TOOL_UNBUILT,规划继续,构建程序得到该工具将被发布的路径;构建程序失败
的包不带该程序的指令地被描述,并得到一条 MCPP_BUILD_DATABASE_PROGRAM_FAILED 错误,path 为它的
build.mcpp。mcpp build 的行为不变。带错误诊断的 data 是 S2 0.3.0 的部分回答
(Sunrisepeak/mcpp-language-server#25)。
构建程序:runtime_search_dir、prepare 角色与 stamp 规则(#701、#702)
mcpp::runtime_search_dir(dir)(mcpp:runtime-search-dir=,协议 12)是[runtime] runtime_search_dirs的构建程序形态,并入同一个字段:ELF 与 Mach-O 的运行路径、mcpp run的加载
路径、mcpp pack的闭包搜索与运行时校验都照常读取它,依赖的声明到达消费方的可执行文件。目录在
构建程序运行时不必存在。保留字段[runtime] library_dirs不获得指令。- 新角色
prepare用于文件名在构建程序运行时未知的施工(安装一个前缀、解开一个 SDK):命令填充
用a.output_dir(dir)声明的目录,引擎写 stamp。命令成功而目录不存在或除 stamp 外不含文件时,
这条边失败并点名目录。声明包的每条编译边与计划中的每条链接边等待它,构建输出以PREPARE
标注。构建程序的重新运行依据落在某个prepare目录内时,构建给出警告。 - 角色以常量书写:
mcpp::roles::{source, check, object, artifact, prepare}。引擎拒绝这五个之外的
角色字符串并列出它们;此前未知的字符串被静默读作source。 check与prepare的命令成功后,引擎创建缺失的 stamp,并把已存在的 stamp 更新到当前时间;
此前已存在的 stamp 不被更新,一个输入改变一次之后,该 action 在此后每次构建中都会重新运行。
Windows 程序的 DLL 在链接后放到程序旁
PE 映像没有运行路径,运行时搜索目录中的 DLL 此前只服务于 mcpp run 与 mcpp pack,从构建目录直接
启动的程序找不到它。现在计划中带运行时搜索目录的 PE 程序在链接后多一条边 mcpp place-dlls:按
mcpp pack 的闭包求解与系统规则,把程序直接或间接导入、且在这些目录中解析到的 DLL 放到程序旁,
字节不同时才写;DLL 在其目录中被替换(包括被 prepare action 替换)后,同一次构建会再次放置它。
部署到程序旁的 DLL 从链接边的隐式输入改为 order-only 输入,DLL 变化不再使程序重新链接。
链接 flag 按词读取(#703)
[build] ldflags、mcpp::link_flag 与依赖传播的链接 flag 此前只为 ninja 转义,Linux 与 macOS 的
sh 展开其中的 $ORIGIN,程序的运行路径因此含有 /../lib,即宿主的 /lib。SPEC-004 §8 的词读法
现在同样适用于链接 flag:每个词原样到达链接器;依赖的链接 flag 按词传播;link_search、link_lib、
link_script 由路径构成的值是一个词,含空格的目录不再被拆开。为 shell 或 ninja 手工转义的写法
(\$ORIGIN、'$$ORIGIN')现在按写法读取,首次规划以 build/flag-words 指出这样的元素;
索引中没有这样的写法。
构建插件规范 SPEC-007
新增 docs/specs/build-plugins.md(草案 0.2):插件的配置、施工与校验各用一种机制,环境不完整时
警告而不失败,配置不依赖施工结果,运行时库以 runtime_search_dir 声明,构建期的网络访问在离线
构建中不发生。mcpp 只提供通用机制,某一个工具的知识只属于它的插件。
兼容性
- 构建程序缓存的 epoch 升到 3,升级后每个构建程序重新运行一次。
- 构建程序协议升到 12;使用新指令、新角色或角色常量的构建程序在旧引擎上编译失败并指出名字。
v2026.9.26.1
路径与文本统一为 UTF-8(#693)
mcpp 此前在 Windows 上以进程 ANSI 代码页持有路径,而它写出的 compile_commands.json 只能容纳
UTF-8。于是工程目录或 mcpp 主目录只要含非 ASCII 字符,每次构建都以
internal: unhandled exception: [json.exception.type_error.316] 失败,系统代码页能拼出的名字也
不例外(代码页 936 上的中文目录名、代码页 1252 上的 café);代码页拼不出的名字则以窄化异常失败。
Linux 上名字不是 UTF-8 的目录以同样的 JSON 异常失败。现在 mcpp 在所有平台上以 UTF-8 持有文本:
mcpp.exe以应用程序清单声明 UTF-8 代码页(res/mcpp.rc),在 Windows 10 1903 及以后的版本上
它与 Windows 交换的窄字符串都是 UTF-8。build.mcpp链接同一份清单;作为 host tool 构建的程序
默认也带它,除非其目标写windows_code_page = "legacy"或包自己嵌入了清单。- 新的目标键
windows_code_page("utf-8"或"legacy")为工程自己的可执行文件声明代码页;
不写时保持系统代码页。 - 路径没有 UTF-8 拼法的工程目录与
MCPP_HOME在构建之前被拒绝,消息以转义拼出该路径并给出
本机的原因;工程内部这样的文件名被跳过并按目录汇报一次;文本不是 UTF-8 的build.mcpp指令
按键名被拒绝。compile_commands.json的序列化失败报告为该文件的失败,不再成为内部异常。 build.ninja含非 ASCII 字节时,mcpp 以ninja -t wincodepage核对 Ninja 读取它的编码,
不是 UTF-8 就拒绝并点名该 Ninja。cl.exe、link.exe、lib.exe只在响应文件以字节顺序标记开头时
按 UTF-8 读取它(在 windows-latest 上实测),msvc 方言的响应文件因此以字节顺序标记开头。- 在忽略清单的 Windows(早于 1903)上,受影响路径的诊断会写出进程所在的 ANSI 代码页。
- Windows CI 新增回归任务:llvm、MSVC、MinGW 三行分别在 ASCII、
café与中文目录中构建并运行,
并核对build.ninja与compile_commands.json中该目录名的 UTF-8 字节。
报告中的静默 0xC0000409 来自 xlings 的 shim:它在代码页之外的工作目录中启动时抛出未捕获的异常。
xlings 2026.9.26.2 声明同样的 UTF-8 代码页并在 main 中设异常边界,mcpp 内置的 xlings 版本随之更新。
c_standard 作用于声明它的包(#695)
[build] c_standard 此前写在文件级 $cflags 上,图中每个 C 编译单元都读它:根包的值到达每个依赖,
依赖自己的声明被解析、被哈希进缓存键,却没有被施加。现在每个包的 C 单元以该包自己的标准编译,
未声明的包以 c11 编译,C++ 单元不接收 C 标准。cl.exe 以其默认模式编译 C,构建会用一行列出
声明未被施加的包。#690 设计记录 §3.1 中把 c_standard 列为「只读根包」的一行已附更正说明。
链接图提供的 C 库时不搜索宿主目录(#696)
C 库来自依赖图的链接此前没有 --sysroot,clang 会从宿主推导 /usr/lib 等库目录:
x86_64-linux-musl 上的 -lm 把宿主 glibc 的目标文件静默链接进 musl 镜像,aarch64-linux-musl
上则以一条不相干的报错失败。现在由 clang 链接的 ELF 目标带上指向构建目录内空目录的 --sysroot;
密封检查拒绝指向工具链存储、构建目录与图中各包之外目录的 -L([build] allow_host_libs = true
取消这一拒绝)。图中无人应答的 -l 以链接器自己的消息失败,mcpp 另附说明;缺失的名字全属于 musl 以
libc.a 应答的八个名字时,说明会点名带有这八个空归档的 openkal-musl 0.19.2。
musl 目标按声明的优化级别编译(#694)
引擎此前在 *-linux-musl 上把每个非零优化级别替换为 -Og(一个 musl-gcc 15.1.0 内部编译器错误的
变通,也作用于 clang 与 mcpp 自己的 Linux 发布二进制),而 Finished 一行按声明的级别报告
optimized。该分支已删除:编译与报告读取同一个值(realised_opt_level),空级别即 0。
原先的触发条件在任何可得的输入上都不再复现。
v2026.9.25.1
工作空间成员在每种位置上以相同方式编译(#690)
[workspace.build] 的继承此前在把清单固定进构建图的那一步执行,晚于 defines 的展开。
作为兄弟成员 path 依赖被编译的成员因此收不到工作空间的 defines(#690 报告的情形),
而被命令选中的根成员收到两份工作空间的 cflags、cxxflags 与 ldflags。现在成员在自己的
加载处继承,顺序与根包相同:先继承,再合并条件节,最后展开 defines;固定进构建图的一步只读取
结果,遇到未展开的 defines 时报告内部错误。
同一处还补上了两件同形的缺口:
- 作为依赖出现的成员解析自己的
x.workspace = true条目。此前该条目以既无版本也无路径的形式
进入解析,报出的是「no readable index entry」。 - 通过
git引用的、托管在 git 上的工作空间的成员,除[workspace.package]外,也继承所在仓库
的[workspace.build]与[workspace.dependencies],相对路径以仓库根为锚点。同一个提交在它
自己的检出中与在使用方的依赖图中以相同方式编译。 - 索引描述符以
mcpp = "<子目录>/mcpp.toml"指向归档内的成员时,该成员以同样方式取得归档中的
工作空间;在归档内查找工作空间根不越出该版本的安装根。本机已安装的 22 个带工作空间根的索引包
都没有声明可继承的表,这一变化不改变任何现有包。
[workspace.build] ios_deployment_target 此前被解析、被继承,却被已知键检查拒绝;可继承键现在
只在一张表里陈述,解析、已知键检查与报错文本都取自这张表。
defines 按宏名构成集合
defines 的条目按包接收它们的顺序合并:[workspace.build]、包自己的 [build]、命中的
[target.<selector>.build]。同一宏名的后一个条目在原位替换前一个,条目 !NAME 移除该宏名,
每个宏名只以一个 -D 词到达编译器。成员因此可以覆盖或移除继承来的宏,而不再产生重定义诊断
(-Werror 下是错误)。defines 条目同样取代同一个包的 cflags、cxxflags 中同名的 -D 词。
更早的 mcpp 会把 !NAME 作为 -D!NAME 交给编译器,使用它的包需要 mcpp 2026.9.25.1 或更新版本。
使用方的头文件目录不再进入依赖
根包的 include_dirs 与 include_dirs_after(包括 private_include_dirs)此前被写进
build.ninja 的文件级编译标志,图中每个编译单元都读到它们:根包目录中与系统头或依赖头同名的
文件会在依赖内部遮蔽它们。依赖缓存键不含这些目录,于是按一个工程的根包头文件编译出的依赖对象
会被另一个无关工程取用(实测:compat.cjson 的 cJSON_Compare(1.0, 1.2) 在未写任何头文件的
工程中答 1)。现在每个编译单元只收到自己的包的目录,以及它的依赖公开的目录;根包自己的编译单元
中每个目录只出现一次。
行为变化。 依赖若曾依靠使用方的头文件目录才能编译,现在报告缺少头文件;mcpp 在编译器报错
之后指出该头文件所在的使用方目录。依赖需要通过自己的 include_dirs 或它的某个依赖找到头文件。
依赖缓存的 epoch 由 3 升为 4,升级后首次构建重建一次依赖缓存,不需要任何操作。
作为 host tool 构建的依赖只合并一次条件节
依赖的 [target.<selector>.build] 条件节先按使用方的目标合并,host tool 子构建此前拿到的正是合并后的
清单,又按宿主合并一次,同时命中两者的条目因此到达工具两次。实测:条件节中的 -include once.h
(无 include guard)让单独构建正常的包作为工具时报重定义错误,2026.9.24.1 同样如此。清单现在记录
第一次合并之前的状态,子构建从该状态按宿主合并。
成员清单只有一种读法
mcpp publish、mcpp pack、mcpp emit xpkg、mcpp toolchain list、mcpp sbom、mcpp index list/update、
mcpp update 的索引刷新、快路径的身份判定与测试发现,此前直接读取成员目录中的原始清单:省略
version 的成员被拒绝,未写 [toolchain] 的成员在 toolchain list 中显示全局默认工具链,而同一
目录下的 mcpp build 用的是工作空间的工具链。现在它们与 prepare_build 经同一个函数读取继承后的
清单。
工作空间成员的发布形态自包含
在成员目录中执行 mcpp publish 或 mcpp emit xpkg 时,归档只包含成员自己的目录,此前其中的
mcpp.toml 原样照搬:继承的 version、[build] 标志不在其中,兄弟成员之间的 path 依赖原样保留
而使用方无法解析,描述符的 deps 静默略去这些依赖。现在发布的清单是归一化后的:继承来的值被写出,
x.workspace = true 条目取得解析后的来源,兄弟依赖以版本依赖发布,原清单以 mcpp.toml.orig 保留,
归一化后的文件同时写到 target/dist/<name>-<version>.mcpp.toml。归档由 git 对象按提交日期构建,
两次运行逐字节相同。不带 version 的兄弟 path 依赖、包外非成员的 path 依赖、位于成员目录之外的
继承头文件目录会被拒绝,报错给出应写的内容。不是工作空间成员的包与此前一样原样归档。
归一化后的清单只使用 2026.9.24.1 已接受的键,实测该版本的客户端可以构建它。
xlings 2026.9.20.1
内置的 xlings 由 2026.9.16.1 升为 2026.9.20.1(openxlings/xlings#610):项目 subos 中执行的命令
不再从项目的 subos 读取全局工作空间并据此删除全局 shim;下载失败的安装不再报告 installed。
测试基础设施
e2e 辅助脚本 _inherit_toolchain.sh 改为按版本链接开发者的载荷。此前它链接整个包目录,测试
新装的版本穿过链接写进开发者的 ~/.mcpp,链接脚本指向测试结束后即被删除的临时目录(#293 的
第二种形态;实测 31_transitive_deps.sh 写出了这样的 xim-x-glibc/2.44.3)。