| 项 | 值 |
|---|---|
| 规范编号 | SPEC-004 |
| 标题 | mcpp.toml 的平面划分、条件化形状、解析轴与命名规约 |
| 状态 | 草案(Draft) |
| 版本 | 1.10 |
| 最后修改 | 2026-09-28 |
| 最低实现版本 | 条件化形状:mcpp 2026.8.29.1([target.<selector>.build-dependencies] 起齐备);目标轴:mcpp 2026.9.6.4 |
| 作者/维护 | mcpp-community |
| 相关设计文档 | .agents/docs/2026-09-07-mcpp-toml-unified-semantics-design.md.agents/docs/2026-06-04-manifest-schema-ownership.md.agents/docs/2026-09-03-xlings-workspace-as-the-one-table.md.agents/docs/2026-09-25-issue-690-workspace-build-inheritance-consistency.md.agents/docs/2026-09-27-eight-reports-by-home-and-one-optimisation-plan.md |
| 相关使用文档 | docs/04 —— mcpp.toml 字段参考 |
按 RFC 2119:必须(MUST)/ 禁止(MUST NOT) 强制;应当(SHOULD) 强烈建议, 偏离需理由;可以(MAY) 可选。
| 标记 | 含义 |
|---|---|
| 已实现 | 当前实现与本条一致 |
| 部分实现 | 已有实现,语义或覆盖面有差异(差异已注明) |
| 未实现 | 本规范要求但尚未支持;当前行为已注明 |
本规范陈述 mcpp.toml 的结构语义:一个 section 属于哪个平面、条件写在哪里、
一个条目按什么解析、键怎么命名。它不列举字段——字段在 docs/05。
它回答的是一个新字段或新 section 该长什么样,以及一份 manifest 为什么这样组织。 §8 另陈述编译 flag 列表中一个元素代表哪些参数,§9 陈述工作空间继承与构建需求的作用域。
边界。 本规范不覆盖字段的准入条件,那由 docs/05 附录 A(Schema Ownership Principle)规定,本规范不重复它,只在 §6 引用并补充一条。
一份 manifest 的 section 必须落在下列平面之一。平面是"这段话在谈什么", 不是"它长什么样"。
| 平面 | section | 谈的是 |
|---|---|---|
| 身份 | [package] |
这个包是谁 |
| 产物 | [targets.<n>]、[lib] |
要产出什么 |
| 编译 | [build]、[profile.<n>]、[toolchain] |
怎么编 |
| 库依赖 | [dependencies]、[dev-dependencies]、[build-dependencies] |
需要哪些 mcpp 包 |
| 工具与环境 | [xlings] |
需要哪些载荷与工具 |
| 门 | [features]、[feature-deps.<f>]、[feature-xlings.<f>] |
什么条件下要 |
| 条件 | [target.<selector>.<section>] |
在哪个目标上要 |
| 产物元数据 | [runtime]、[resources] |
产出的东西是什么 |
| 生命周期 | [hooks] |
构建前后跑什么 |
状态:已实现。
库依赖与工具是两个平面,不是一个。 库有模块与 ABI,参与解析与链接;载荷是可执行
的工具或被编译对着的输入,不参与模块图。二者的解析规则不同(§4),因此禁止把工具
写进 [dependencies],也禁止把 mcpp 包写进 [xlings]。
条件化必须写成:
[target.<selector>.<section>]
<selector> 是目标三元组或 cfg(...) 谓词。禁止把条件写成尾部键
([xlings.workspace.linux])或值的兄弟键。
今天接受 <section> 为:build、dependencies、dev-dependencies、
build-dependencies、feature-deps.<f>、runtime、xlings、feature-xlings.<f>、
targets.<name>(mcpp 2026.9.14.2+),以及 abi、requires_abi、feature-requires-abi、
runners。实现不读取的 <section> 必须报出,不得静默忽略(mcpp 2026.9.14.2+)。
状态:已实现(上列 section)。
[build] 之下的 dialect_cxxflags 是图级联的方言开关,不是逐包可叠加的构建输入
(BuildConfig::dialectCxxflags;§9 第 10 条)。写在 [target.<selector>.build]
之下时,它接受与本节其它键相同的条件形状,但按图级联规则解析而不是按包解析:
只有一次构建的根(命令的包,或 -p 选中的成员)对这个列表贡献,依赖包自己声明
的这个键不到达任何命令。向量按 [workspace.build]、根的 [build]、再到每个
命中的 [target.<selector>.build](按 §3.1.1 的次序)追加,如同一个可叠加的构建输入。
状态:已实现(mcpp 2026.9.28.1)。
在 <selector> 命中的行上,[target.<selector>.dependencies] 中某个身份的声明
替换该身份在 [dependencies] 中的声明;身份按键规范化后的 (namespace, name)
比较,而非按键的字面。多个命中的 section 按选择器的具体程度生效:更具体的后生效,
因此替换较宽泛的;具体程度相同时,按选择器文本的字典序。同一规则适用于
dev-dependencies、build-dependencies 与 feature-deps.<f>。这与条件化标量
「最后一个命中者为准」是同一条规则;可叠加的构建输入(build)按同一次序追加。
选择器的具体程度是它固定的目标三元组成分的个数:三元组固定全部成分,高于任何
cfg(...);os = "…" 与 linux、macos、windows 固定操作系统及其族;
family = "…" 与 unix 固定族;arch = "…"、env = "…" 各固定该成分;all(…)
固定其各项的并集;any(…)、not(…)、层键与 accelerator 不固定任何成分。于是三元组
高于操作系统,操作系统高于族,cfg(all(os = "linux", arch = "aarch64")) 高于
cfg(os = "linux")。
清单顺序无法作为规则:TOML 的表不带键的顺序,解析器以有序映射保存键,实现因此只能 观察到选择器文本的字典序。按具体程度排序使结果只取决于选择器说了什么,而不取决于它 怎样拼写;字典序只用于打破平局。工作空间的条件表先于成员的条件表生效,各自按本条 排序(§9 第 2 条)。
只写选项而不写来源(path/version/git/workspace)的条件表不是对既有依赖的
修饰,按文法它声明的是另一个包;实现必须把这种写法报出,并给出补全来源后的声明。
[target.<selector>.targets.<name>] kind 是 [targets.<name>] kind 的按行形式:
只接受库目标,只在 lib 与 shared 之间选择,在命中的行上约束该包的链接形态,
与无条件的 kind = "shared" 相同。
linkage = "static" | "shared" 与 kind 并列,可写在 [targets.<name>] 与其按行形式中,
陈述库目标的默认链接形态:它不收窄可选形态的集合,只在消费者没有陈述时给出答案。
形态按以下顺序决定,前者优先:根工程依赖边上的 linkage,根工程写下的
dependency_linkage,包的 linkage,static。与包默认值不同的显式陈述必须被遵从,
且不得记为降级;实现必须输出一条同时点名两条陈述的信息。同一张表中 kind = "shared"
与 linkage 并存、一行同时陈述 kind 与 linkage、linkage 写在程序目标上,均必须
被拒绝;按行合并时后命中的陈述替换先前的陈述,无论两者各是 kind 还是 linkage。
状态:已实现(mcpp 2026.9.14.2;linkage 为 2026.9.15.2;多个命中的条件表按具体程度生效为 2026.9.28.2,mcpp#728)。
[target.<selector>.feature-deps.<f>] 合法:条件决定这个门拉进什么,不决定
这个门存不存在。feature 本身必须无条件注册,否则在不匹配的平台上请求它会
触发"未知 feature"诊断。
状态:已实现(mcpp-index#359)。
门必须拼成顶层的 <限定词>-<section>,与 dev-dependencies 同构:
dev-dependencies build-dependencies ← 限定词是用途
feature-deps feature-xlings ← 限定词是门
禁止为第二种门发明第二种语法。一个门拉进包和一个门拉进工具,是关于同一个门的 同一句话。
不采用 [features.<f>.deps],理由是 TOML 而非风格。 [features] 的值允许写成
内联表(rules-sycl = { sources = [...] }),而 TOML 禁止用子表扩展内联表——
[features.rules-sycl.deps] 在真实 manifest 里是语法错误。要让它合法必须禁掉
内联写法,而 [features] 是 Cargo 兼容面,其值还可以是数组(default = ["base"]),
数组开不了 section。
状态:已实现。
一个条目按宿主还是按目标解析,由它谈的是什么决定,不由它写在哪里决定。
| 轴 | 谈的是 | 例 |
|---|---|---|
| 宿主 | 在构建机上执行的东西 | xim:dpcpp、xim:shaderc、xim:ninja |
| 目标 | 被编译对着的东西 | xim:glibc、xim:linux-headers、目标 sysroot |
交叉构建(宿主与目标不同)时二者分叉。非交叉时二者恰好一致,因此这条差异在非交叉 构建上不可观测。
| 写法 | 轴 | 状态 |
|---|---|---|
顶层 [xlings],值写成平台键对象 { linux=…, macosx=…, windows=…, default=… } |
宿主 | 已实现 |
[target.<selector>.xlings…] |
目标 | 已实现(2026.9.6.4) |
[target.<triple>].sysroot |
目标 | 已实现 |
顶层平台键对象不是遗留拼法,它是宿主轴正确的写法:工具必须能在这台机器上
执行。[target.<triple>].sysroot 是目标轴 xpkg 引用的既有先例。
[target.<selector>.xlings.workspace] 与
[target.<selector>.feature-xlings.<f>] 被接受,并按目标解析:
[xlings.workspace]
"xim:dpcpp" = "7.1.0" # 宿主轴:它在这台机器上执行
[target.'cfg(os = "linux")'.xlings.workspace]
"xim:glibc" = "" # 目标轴:产物编译时对着它
"xim:linux-headers" = ""产物编译或链接时对着的东西应当写在目标轴上;在构建机上执行的工具应当写在
顶层 [xlings]。把目标事实写在顶层在非交叉构建上恰好正确(§4.1),在交叉构建上不
正确;既有写法保持原义,不被废弃。
两条轴同时命名一个包时,[target.<selector>] 一侧是更具体的陈述,必须是被采用
的那一条;实现应当报告这次覆盖。去重按包而非按地址进行:xim:glibc 与
xim:glibc@2.40 是同一次安装的两个地址,两条都保留会让环境取决于供给顺序。
[target.<selector>.xlings] 禁止接受 subos:一个工程只有一个环境,而不是每个
目标一个。实现必须报错而不是忽略。
工具的消费者看到的是两条轴的并集:xpkg_dir 与供给都读折叠后的一张表,所以一个
规则包不需要知道某个载荷是由哪条轴声明的。
由此产生一个必须说清的后果:物化出来的 .mcpp/.xlings.json 描述的是最后一次构建的
目标。 该文件是 mcpp 对"这次构建的环境"的物化,不是对"这个工程"的物化;同一个工程
先按 A 构建再按 B 构建,文件里是 B。读它的东西必须按这个含义读。
一个已发布的包,其诊断里给出的写法必须是它声明的引擎下界能接受的那一种。 诊断 文本是作者会逐字抄走的东西,推荐一种旧引擎会拒绝的写法就是把升级悬崖搬进了别人的 工程。目标轴要求 mcpp 2026.9.6.4,因此在下界低于该版本的包里,诊断应当继续给出 顶层写法。
状态:已实现(2026.9.6.4)。
[target.<selector>.xlings…] 的 <selector> 禁止命名由依赖解析回答的五个层
(c-abi、c++-abi、compiler、compiler-runtime、kernel-abi)。
理由是时序而非风格:这五个层由依赖解析回答,因此命名它们的谓词被推迟到第二遍合并, 而那一遍在工具供给之后、每个包的 build.mcpp 之后。在那里被接受的条目会被声明却永远 装不上,产生的失败形态是最坏的一种:构建成功,工具不在。
accelerator 被接受,且划分依据正是上面那条时序而不是这个键的主题。它不由任何东西
解析:它是 --accel 或 [build] accel,在第一个包被查找之前就已读入,因此以它为谓词的
条目在第一遍合并里就已折叠,与任何其他条目一样被安装。曾经把它一并拒绝的代价落在
每一个带设备孤岛的工程的每一次构建上——厂商工具包只能无条件声明或完全不声明,于是
一次 CPU-only 构建为它并不编译的设备下载数 GB。
实现必须在第一个载荷被取回之前拒绝被禁止的谓词,消息必须同时点出工具与谓词,
并指出两条出路:按目标条件化,或用 [feature-xlings.<f>] 做门——feature 在任何东西被
供给之前就已知。
状态:已实现(2026.9.6.4;accelerator 的接纳为 2026.9.6.5)。
已发布的 xim 描述符按平台分块(xpm.linux、xpm.macosx、xpm.windows),而 selector
不是平台。因此目标轴条目不产生描述符边;实现必须在发布时报告,而不是把它映射到
某一块上——映射需要为每个平台假定一个代表三元组,而谓词不满足该三元组的条目会消失在
同一种沉默里。
使用者装到的东西来自顶层 [xlings.workspace](即 §4.2 的宿主轴);目标轴对"本包
自己的构建对着什么"仍然正确。
状态:已实现(2026.9.6.4)。
[target.<selector>.xlings…] 之下的值禁止再写平台键对象:条件已经在外层,
里面再写一层就是同一个事实的两处陈述,而两处可以不一致。实现必须报错而不是
择一,并在消息里点出外层 selector——那是作者要删掉的一半,也是他没有在看的一半。
状态:已实现(2026.9.6.4)。
[xlings.workspace] / [xlings] deps / [feature-xlings.<f>] 里一条地址的身份是
(namespace, name)。版本永远是这个包上的约束,不是它名字的一部分。 命名空间缺省
为 xim,与 [<ns>:]<name>[@<version>] 的解析一致。
一次构建里同一身份只安装一个版本,由两步决定:
- 裁决——离产物更近的声明赢:工程 > 它依赖的包。同一份 manifest 内的两条轴仍按 §4.3 的「更具体的赢」。不带版本的声明弃权:它陈述了「要这个包」而没有陈述「要 哪一版」,因此不参与这个问题。全都不带版本时,结果就是那个裸地址。
- 校验——赢家必须满足每一条落败的要求。
>=/^/~/ 逗号组合是 要求;不带运算符的裸版本是选择,由裁决处理而不是校验。两条精确钉写得不同,是 两个选择,较近的那条赢并必须被报告;精确钉不满足某条要求时,实现必须拒绝 并同时点出两侧各自的声明与出路。
校验是一次比较,不是在索引里搜索。选哪一版由裁决决定,因此实现不需要「有哪些版本
可选」这个输入,也就不需要约束求解器。代价是明确的:一些求解器本可满足的组合会被拒绝
(工程写 >=8.0、规则写 8.5.0、索引最新 8.3),而拒绝消息里写着出路。
版本位接受范围表达式,并且必须在两个方向上都被求解:>=2026.1 装到满足它的最高
版本,>=2099.1 被拒绝。实现必须让 mcpp::xpkg_dir 回答范围——安装了却答「不
存在」,是让规则包无法声明下界的那个缺口。
xpkg_dir 对一条地址的回答必须是 xlings 为它安装的那个载荷:先取 xlings 报告的
解析结果,没有时按 xlings 的版本文法在已安装的版本目录中选择(SPEC-001 §10.1)。
libglvnd@1.7 因此回答 1.7.0.1。
状态:已实现(2026.9.6.6;按 xlings 文法回答自 2026.9.27.1,mcpp#712)。
不带 --target 的构建以宿主为目标。[target.<宿主三元组>] 对它的描述与对任何其他目标
的描述相同,必须被应用:toolchain、linkage、cxx_runtime 等键的效果与
--target <宿主三元组> 相同。行的查找与 --target 使用同一个与拼写无关的比较,
x86_64-unknown-linux-gnu 找到 [target.x86_64-linux-gnu]。命令行的 --toolchain
(MCPP_TOOLCHAIN)仍优先于行的 toolchain。
状态:已实现(mcpp 2026.9.27.1,mcpp#704)。
一条载荷声明陈述要哪个包;它从哪里来,以及什么时候装,是另外两个问题。
[xlings.overrides] 陈述来源。 键是 §4.5 的身份 [<ns>:]<name>,禁止带版本;
值是一个路径,或一个表,表中必须恰好写 program(一个文件,或一个在 PATH 上查找
的程序名)或 root(与载荷同布局的目录)之一,并可写 version。
- 被覆盖的包禁止被供给:它不进入安装列表,不参与离线判定。
- 它仍参与 §4.5 的校验:写了
version时,实现必须按该版本校验每一条落败的要求, 不满足则拒绝并点出两侧;没写时必须记一条 note,点出未被校验的要求。 mcpp::xpkg_dir必须回答覆盖所指的 root,mcpp::xpkg_program回答它所指的程序,mcpp::xpkg_source回答override。- 覆盖只由一次构建的根陈述:根清单、环境变量
MCPP_XLINGS_OVERRIDE_<NS>_<NAME>或config.toml,优先级依此顺序由高到低。依赖写它必须被拒绝,拒绝消息点出该包与 正确的位置。
provision = "on-request" 陈述供给时机。 条目表接受它与 version、when 并列;
缺省为 eager,即构建程序运行前供给。标为 on-request 的包:
- 在所有声明它的 manifest 都这样写时,构建程序运行前禁止被供给;
- 构建程序以
mcpp::xpkg_request请求它;实现必须把一次运行中的全部请求合为一次 安装,并只重跑请求过的那些程序,丢弃它们该次运行的其余陈述; - 规划(
mcpp emit build-database)禁止因此安装任何东西,必须记一条MCPP_BUILD_DATABASE_PAYLOAD_DEFERREDnote; - 请求一个没有任何 manifest 如此声明的包必须被拒绝。
状态:已实现(mcpp 2026.10.1.3,mcpp#755)。
| 面 | case | 例 |
|---|---|---|
| Cargo 继承面(依赖、feature、profile) | kebab | dev-dependencies、default-features、host-module |
| mcpp 自有构建面 | snake | include_dirs、cxx_runtime、module_extensions |
新键应当按它所在的面选 case。
状态:已实现(事实上一致,此前未成文)。
已进入已发布描述符的键禁止改名。不一致处应当写成规则并注明历史来源, 而不是通过改名消除。
由此保留的已知不一致:dev-dependencies 用全词 dependencies,而 feature-deps
用简写 deps。deps 是 dependencies 的既有简写;两种拼法都在已发布的描述符里。
状态:已实现。
除 docs/05 附录 A 的准入条件外,新的条件化需求必须先尝试用
[target.<selector>.<section>] 表达。表达不了才讨论新语法,并必须在设计文档里
说明为什么表达不了。
状态:本规范新增。
本规范的可检验推论:
[target.<selector>.<section>]之外不存在第二种条件写法(值的平台键对象 除外,它按 §4.2 是轴而非条件)。- 目标轴与宿主轴的差异只能在解析后的目标与宿主不同时观测。因此验证 §4.3 的
测试必须跨目标,非交叉的绿色对它零信息量。判据:同一份 manifest 按两个不同
的目标各解析一次,目标轴条目随之出现与消失,而宿主轴条目两次都在
(
tests/unit/test_target_xlings_axis.cpp)。 - §4.4 的"两处条件"必须报错,判据是一份同时写了外层 selector 与内层平台键的 manifest 被拒绝。
- §4.3 的"按包去重"判据:两条轴各写一次同一个包,
xlings.deps里该包只出现 一次,且是[target.<selector>]那条。 - §4.3.1 的判据:一份用被解析的层谓词声明工具的 manifest 被拒绝,且拒绝发生
在任何下载之前;而同一份 manifest 把谓词换成
accelerator时构建通过并装上工具。 两个方向都要跑:只跑拒绝那半,一个把所有层谓词都拒掉的实现同样通过。 - §4.5 的判据必须同时观察「装了什么」和「答了什么」。只断言 store 里有一个版本
目录,会在一个装 A 而
xpkg_dir答 B 的实现上通过;只断言答案,会在一个装两份的 实现上通过(tests/e2e/627_one_package_one_version.sh)。 - §4.5 的拒绝判据必须带反向腿:把钉抬到满足要求后同一份工程构建通过。否则一个
「凡工程与依赖同时声明同一个包就拒绝」的实现也会通过
(
tests/e2e/628_a_pin_below_a_stated_floor_is_refused.sh)。 - §3.1.1 的判据:同一身份在无条件表与命中的条件表中各声明一次、只有条件表写
linkage = "shared"时,命中的行链接共享库,不命中的行静态链接,解析记录给出 声明所在的表(tests/e2e/677_a_conditional_dependency_replaces_the_unconditional_one.sh); 按行kind在命中行上给出共享库与原因row-kind,不命中行为default(tests/e2e/678_a_row_states_a_library_form.sh)。 - §3.1.1
linkage的判据:包的默认值为shared时,不陈述的消费者得到共享库与原因package-default;边上的linkage = "static"与写下的dependency_linkage = "static"都得到静态形态与原因requested,且--strict下构建通过;按行linkage替换无条件的kind = "shared"(tests/e2e/692_a_package_states_its_default_link_form.sh)。 - §8 的判据取自三处并要求一致:程序打印每个宏收到的值,
compile_commands.json与mcpp emit build-database列出的词,三者都等于按 §8 读出的词;同一份断言在 Linux、 macOS 与 Windows 上不变(tests/e2e/736_compile_flag_words_reach_the_compiler_and_the_databases.sh)。 规则本身由读回性质陈述:任意词经实现的拼写读回为它自身,经宿主引号读回也为它自身, 且 POSIX 宿主上由/bin/sh实测(modules/manifest/tests/test_flag_words.cpp、tests/unit/test_compile_commands.cpp)。 - §8
defines集合语义与 §9 第 1 至 3 条的判据:同一成员分别作为命令构建的包与作为兄弟 成员的path依赖时,每个工作空间词在 C 与 C++ 编译单元中各恰好出现一次且先于成员 自己的词;成员重写的宏名只出现成员的值,!NAME移除的宏名不出现;作为依赖的成员 解析自己的x.workspace = true条目;通过git引用的仓库成员收到其仓库的[workspace.build](tests/e2e/770_workspace_member_as_dependency.sh)。第 3 条的 内部错误与第 4 条的键表由单元测试陈述(tests/unit/test_workspace_inheritance.cpp)。 - §9 第 6 条的判据:根包私有目录中的
limits.h不到达依赖的 C 与 C++ 编译单元;依赖 的编译命令在两个仅 include 设置不同的根包下相同;根包自己的编译单元中每个根包目录 恰好出现一次;依赖因此找不到头文件时,报错之后指出消费者目录 (tests/e2e/765_a_consumer_include_directory_stays_in_the_consumer.sh)。 - §9 第 1 条索引归档一种情况的判据:描述符指向归档内一个省略
version的成员时,该成员 取得归档工作空间的版本与defines(tests/e2e/774_an_index_member_inherits_its_archive_workspace.sh)。 第 5 条的判据:成员目录内的emit xpkg、publish --dry-run、toolchain list读取继承后的 清单(tests/e2e/773_commands_outside_the_build_read_the_effective_manifest.sh)。 - §9 第 7 条的判据:发布归档中的清单写出继承来的
version、license与cxxflags,兄弟边 以版本边出现且描述符的deps列出它,原清单以mcpp.toml.orig保留;归档的使用方在新旧两个 客户端上都能构建;两次发布的归档逐字节相同;缺少version的兄弟path边被拒绝且报错给出 应写的一行;无需修改的包的归档与此前逐字节相同 (tests/e2e/772_a_published_member_is_self_contained.sh)。 - §8 对链接 flag 的判据:
[build] ldflags与构建程序的mcpp::link_flag中写出的-Wl,-rpath,$ORIGIN/../lib原样到达程序的运行路径,不出现/../lib;依赖传播的同一 元素同样原样到达;含空格的link_search目录是一个参数 (tests/e2e/795_a_link_flag_reaches_the_linker_as_written.sh)。 - §4.5 按 xlings 文法回答的判据:xlings 发布的版本选择向量在 mcpp 的实现上逐条得到相同
结果(
modules/versioning/tests/data/semver-vectors.tsv,modules/versioning/tests/test_xpkg_version.cpp);libglvnd@1.7在只装有1.7.0.1时回答该目录(tests/unit/test_freestanding.cpp)。 - §4.6 的判据必须带对照腿:没有行时默认构建自包含,写了宿主行
cxx_runtime = "toolchain-coupled"后普通构建需要libstdc++.so.6,行以另一种拼写 书写时同样生效(tests/e2e/802_a_host_build_applies_its_host_row.sh)。 - §9 第 8 至 10 条的判据:成员得到根的条目与条件行,自己声明的同一个包保留自己的地址;
未解析的
workspace = true在三张依赖表中都被点名拒绝;成员工具的工具链取工作空间的[toolchain],自己声明时取自己的(tests/unit/test_workspace_inheritance.cpp)。 - §10.2 的判据必须两个方向都跑:只写
features = ["codegen"]的消费方得到工具并编译 它生成的源,不启用该特性的消费方什么都不构建;tools指名非bin目标时加载被拒绝 (tests/e2e/800_a_feature_provides_its_host_tools.sh)。 - §10.3 的判据:程序构建到
bin/并可运行,依赖的代码不在消费方中,占位符到达 action,mcpp pack的归档含该程序;有 musl 工具链时,--target x86_64-linux-musl下它为目标构建 (tests/e2e/801_a_dependency_program_is_shipped_with_the_consumer.sh)。
cflags、cxxflags、asmflags 与 ldflags 的一个元素是一段文本,代表零个或多个词;
编译器或链接器收到的参数就是这些词,按列表顺序排列。本节在元素的文本上陈述(TOML 或 Lua
先去掉自己的转义)。该读法对这四个键的每一个出现位置相同:[build]、[targets.<n>]、
flags 的 glob 条目、feature、[profile.<n>]、[target.<selector>.build]、xpkg 描述符,
以及构建程序的 mcpp:cflag=、mcpp:cxxflag= 与 mcpp:link-flag= 指令。
- 未加引号的空格与制表符分隔词,连续的分隔符等同于一个。
'开启一段单引号区域,区域内的字符按字面取到下一个'为止。"开启一段双引号区域,区域内\"代表"、\\代表\,其余字符按字面取到下一个 未转义的"为止。- 引号区域之外,
\后跟空格、制表符、"、'或\时代表该字符;其余\按字面。 - 相邻的区域与字面片段组成一个词;一个只含
''或""的词是空词。 - 未闭合的引号区域延伸到元素末尾。
- 除上述字符外,任何字符没有特殊含义;实现禁止对
$、*、?、;、|、&、<、>、`、~做展开或解释。 - 例外:以
-D或/D开头且含空格(U+0020)的元素代表一个词,即元素文本本身,规则 1 至 7 不适用。自 mcpp#234 起,每个版本在宿主读取之前都把这样的元素整体加引号,该例外保持其含义不变。
defines 的一个条目 X 是一个值:它代表一个词 -D 与 X 的拼接,禁止按上述规则
读取。
defines 是按宏名构成的集合(mcpp 2026.9.25.1 起)。条目的宏名是 = 之前的文本,
无 = 时为整个条目。条目按包接收它们的顺序读取:[workspace.build]、包自己的
[build]、各个命中的 [target.<selector>.build]。实现必须满足:同一宏名的后一个
条目在原位替换前一个;条目 !NAME 移除宏名 NAME;一个包的每个编译单元对每个宏名
至多收到一个 -D 词;defines 条目取代同一个包的 cflags、cxxflags 中读作单个词
且宏名相同的 -D 词。实现向这四个列表插入一个词 w 时,必须使用一个按上述规则读回恰为 w 的拼写;
构建程序的 mcpp:link-lib=、mcpp:link-search= 与 mcpp:link-script= 指令由名字或路径构成的
值属于这种插入。
实现必须把每个词原样交给编译器或链接器,与宿主的命令行读取规则(POSIX sh、MSVCRT)
无关;compile_commands.json 与构建数据库(SPEC-005 R3.7)列出的参数必须是这些词。
依赖的 ldflags 传播给消费者时按词传播,包内相对的搜索路径按词解析为绝对路径。
规则 7 对链接 flag 的一个推论:$ORIGIN 等加载器记号原样到达链接器。为 shell 或 ninja
手工转义的写法(\$ORIGIN、'$$ORIGIN')按上述规则读取,不再是转义。
dialect_cxxflags 与 std-module-flags 不在本节范围内。
状态:已实现(mcpp 2026.9.17.1;defines 的集合语义 mcpp 2026.9.25.1;ldflags 与链接指令
mcpp 2026.9.26.2,#703)。
- 工作空间成员必须恰好接收一次
[workspace.package]、[workspace.build]与x.workspace = true条目的继承,无论它是命令构建的包、带[package]的工作空间根 自己的path依赖所到达的成员、另一个成员的path依赖、通过git引用的托管在 git 上的工作空间的成员,还是索引包归档内的成员(描述符的mcpp字段指向该成员的 清单)。后两种情况按该成员所在仓库或归档的工作空间根继承,相对路径以该根为锚点; 在归档内查找工作空间根时禁止越出该版本的安装根。工作空间的上下文(它是哪个 工作空间、工作空间根在哪里)取决于清单在哪里,与命令走的是哪条分支无关——带[package]的工作空间根按自身构建时,同样要在解析任何依赖之前建立这一上下文。-p/--package首先按成员的包身份(限定名<namespace>.<name>,其次是裸包名) 为其命名,目录路径与目录名是回落拼法。 - 向量按工作空间、成员、命中的
[target.<selector>.build]的顺序追加,命中的条件表 之间按 §3.1.1 的具体程度排序;defines按 §8 的集合语义合并。标量仅在成员未 声明该键时取工作空间的值。 - 继承必须在
defines展开之前、在清单被固定进构建图之前完成。实现必须拒绝 把含有未展开defines的清单固定进构建图,并报告内部错误。 - 可继承的
[build]键集合只陈述一次。解析、已知键检查与报错文本必须取自同一 陈述。 - 读取成员清单的每一条命令必须读取继承后的有效清单。
- 一个包的私有构建需求,包括它的
include_dirs与include_dirs_after,禁止到达 另一个包的编译单元。使用需求只从依赖流向它的消费者,禁止从消费者流入依赖。 一个依赖的编译命令必须与构建它的工程无关;依赖缓存键必须包含到达该命令的 全部输入。 - 工作空间成员的发布形态必须自包含:发布的清单写出继承来的值,兄弟成员之间的
path边以版本边发布,无法以版本表达的path边必须被拒绝发布。 - 成员必须继承工作空间根的
[xlings.workspace]条目,包括[target.<selector>.xlings.workspace]的条件行(按行继承,合并时由选择器决定)。继承是 隐式的,与[toolchain]相同,因为载荷描述的是构建运行的环境,不是依赖图的边。成员自己 声明的同一个包(身份为(namespace, name))优先。[feature-xlings.<f>]不被继承:特性 属于声明它的包。 - 一条
x.workspace = true条目在继承之后仍未解析时,实现必须在它进入构建的每个位置 (根包、-p选中的成员、path与git依赖、索引依赖)拒绝它,并点名条目所在的表与 名称。带[package]的工作空间根按它自己的[workspace.dependencies]解析自己的workspace = true条目。 [toolchain]、[target.<triple>]与[indices]是根位置的键:它们为整个依赖图选择 编译器、目标行与索引,因此只在成员作为一次构建的根时继承。作为宿主工具构建的成员是其 子构建的根,同样继承这三项(§10.1)。[build] dialect_cxxflags(及其条件形式[target.<selector>.build] dialect_cxxflags)同样是根位置的键:它是 §3.1 所述的图级联方言开关,只在包作为一次构建的根时被渲染并到达命令。与前三项相同, 一个包声明它不被诊断——一个依赖包为自己将来作为根的构建合法地声明这些键,这一条 只是把已有行为写成明文规则。
状态:已实现(第 1 至 7 条 mcpp 2026.9.25.1;第 8 至 10 条 mcpp 2026.9.27.1,mcpp#713、
#714、#710;第 10 条的 dialect_cxxflags 为 mcpp 2026.9.28.1,#717)。
一条依赖边可以取得依赖包的 bin 目标,而不链接它的代码。取得的方式由边决定,因为程序
在哪台机器上运行决定了它为哪个目标构建。
x = { ..., tools = ["<bin>"] } 取得依赖为构建机器构建的程序:它在一次嵌套的子构建中构建,
发布到全局工具库,构建程序以 mcpp::dep_bin("<x>", "<bin>") 取得路径。
- 子构建的工具链由请求它的构建决定一次:
--toolchain(MCPP_TOOLCHAIN)优先;否则取 工具包自己的声明——应用它所在工作空间的根位置键(§9 第 10 条)之后,先宿主行的toolchain,再[toolchain];都没有时取请求方为构建程序使用的宿主工具链。决定的结果 传给子构建,并写入工具库的键,因此键与产物不会不一致。 - 工具库键中的源树摘要不包含带有自己
mcpp.toml的子目录:工作空间根作为工具包时,其成员 的改动不使工具重建。
状态:已实现(mcpp#355;工具链的决定与源树摘要自 mcpp 2026.9.27.1,mcpp#710、#705)。
[features.<f>] tools = ["<bin>"] 陈述启用特性 f 需要本包的程序 <bin> 在构建机器上
运行。启用该特性的依赖边,等同于在边上写了 tools = ["<bin>"];不启用时不构建。条目
必须指名本包的一个 bin 目标,否则清单在加载时被拒绝,消息列出本包的 bin 目标。
状态:已实现(mcpp 2026.9.27.1,mcpp#709)。
x = { ..., artifacts = ["<bin>"] } 取得依赖的 bin 目标,以消费方的目标与 profile
构建,作为消费方计划中的一个链接单元,输出到消费方的 bin/。
- 只经
artifacts边到达的包的代码不链接进消费方;同一个包另经普通边到达时照常链接。 - 构建程序的 action 以
${mcpp.artifact:<x>/<bin>}引用该程序的路径,可用于命令与输入; 名称不对应一个artifacts条目时,规划失败并点名该占位符。 mcpp run不选择该程序;mcpp pack把它放在消费方程序旁。- 交叉构建(
--target)中该程序为目标构建,与tools为构建机器构建相对。
状态:已实现(mcpp 2026.9.27.1,mcpp#711)。
| 版本 | 日期 | 变更 |
|---|---|---|
| 1.0 | 2026-09-07 | 首版。平面(§2)、条件化唯一形状(§3)、两条解析轴(§4)、命名规约(§5)、条件化准入(§6)。目标轴列为未实现。 |
| 1.1 | 2026-09-07 | 目标轴落地(mcpp 2026.9.6.4):§4.3.1 工具 selector 禁止命名目标侧层;[target.<selector>.xlings…] 与 [target.<selector>.feature-xlings.<f>] 转为已实现;§4.3 补两条轴同时命名一个包时的取舍与按包去重;§4.4 转为已实现;§7 补第 4 条判据。 |
| 1.2 | 2026-09-07 | 一个包一个版本(mcpp 2026.9.6.6):新增 §4.5(身份=(namespace, name),版本是约束;裁决与校验两步;范围必须双向可解且可被 xpkg_dir 回答);§4.3.1 改为「禁止命名被解析的层」,accelerator 明确被接受(2026.9.6.5);§7 补第 5 条的反向腿与第 6、7 条判据。 |
| 1.3 | 2026-09-14 | 条件依赖声明替换同一身份的无条件声明,targets.<name> 成为可条件化的 section,不读取的 section 必须报出(mcpp 2026.9.14.2):新增 §3.1.1 与 §7 第 8 条判据。 |
| 1.4 | 2026-09-15 | 库目标的默认链接形态 linkage(mcpp 2026.9.15.2):§3.1.1 补默认值的语义、优先顺序与拒绝条件;§7 补第 9 条判据。 |
| 1.5 | 2026-09-17 | 编译 flag 列表元素的读法(mcpp 2026.9.17.1,#655):新增 §8 与 §7 第 10 条判据。 |
| 1.6 | 2026-09-25 | 工作空间继承与构建需求的作用域(mcpp 2026.9.25.1,#690):§8 补 defines 的集合语义;新增 §9 与 §7 第 11 至 14 条判据。 |
| 1.7 | 2026-09-26 | §8 的读法扩展到 ldflags 与构建程序的链接指令(mcpp 2026.9.26.2,#703):$ORIGIN 原样到达链接器;§7 补第 15 条判据。 |
| 1.8 | 2026-09-27 | mcpp 2026.9.27.1:§4.5 的版本位按 xlings 文法回答(#712);新增 §4.6 宿主构建读取宿主三元组的行(#704);§9 补第 8 至 10 条(#713、#714、#710);新增 §10 依赖的程序:tools、特性的 tools、artifacts(#709、#711);§7 补第 16 至 20 条判据。 |
| 1.9 | 2026-09-28 | mcpp 2026.9.28.1:§9 第 1 条补上带 [package] 的工作空间根自己的 path 依赖所到达的成员,-p 先按包的身份解析(#725);§3.1 接受 [target.<selector>.build] dialect_cxxflags,§9 第 10 条把它列为根位置的键(#717);§3.1.1 的状态改为部分实现,多个命中的条件表的先后见 mcpp#728。 |
| 1.10 | 2026-09-28 | 多个命中的条件表按选择器的具体程度生效,三元组高于操作系统高于族,字典序只打破平局(mcpp 2026.9.28.2,mcpp#728,2026-09-28 设计 D7):§3.1.1 陈述规则与具体程度,§3.1 与 §9 第 2 条的「按清单顺序」随之更正;§3.1.1 转为已实现。 |