Skip to content

Latest commit

 

History

History
398 lines (278 loc) · 21.4 KB

target-side.md

File metadata and controls

398 lines (278 loc) · 21.4 KB

SPEC-002:目标侧模型与能力声明

项 值
规范编号 SPEC-002
标题 目标侧模型与能力声明(mcpp: 保留命名空间)
状态 评审中 v1.8
最后修改 2026-11-10
对应实现 mcpp >= 2026.8.24.2;§2.2 的对象形式与 §7 mcpp >= 2026.10.26.1
相关设计文档 .agents/docs/2026-08-24-target-side-design.md、.agents/docs/2026-10-23-target-base-and-execution-host-design.md、.agents/docs/2026-10-26-control-contract-runtime-and-ide-ecosystem-design.md §6
使用文档 docs/22 - 目标侧

本规范定义一次构建的目标侧由哪些层构成、每一层可以由谁供给、 供给者与需求者如何声明,以及引擎据此执行的规则。

用语按 RFC 2119:必须 / 禁止(强制)、应当(强烈建议)、可以(可选)。


1. 层

1.1 层的闭集 已实现

一次构建的目标侧必须由且仅由以下五层构成:

层名 内容
compiler 执行编译的程序
compiler-runtime 编译器自身的运行时:builtins 与展开器
kernel-abi 平台接口或其等价物
c-abi C 库
c++-abi C++ 库及其 ABI 运行时

层名是编译进引擎的闭集。实现名禁止出现在引擎代码中。

1.2 可供给性 已实现

除 compiler 外的每一层可以由包供给。compiler 禁止由包供给: 编译器是引擎安装并驱动的载荷,族与族之间的差异是引擎必须持有的事实。 包可以要求某个 compiler。

1.3 来源 已实现

每一层的来源必须是以下四者之一:

来源 报告用词 可知时刻
编译器载荷 payload 依赖解析之前
被点名、按内容识别的预制目录(目标基座,SPEC-006 §3.9) prebuilt 依赖解析之前
依赖图中的包 graph 依赖解析之后
无 — —

目标侧必须在依赖解析完成之后解析,且必须只解析一次。


2. 声明

2.1 provides 语法 已实现

mcpp:<层名>[=<实现名>]

<层名> 必须属于 §1.1 的闭集;不属于时的处置见 §5.1(根工程报错、依赖警告)。 不以 mcpp: 开头的条目属于特性系统,引擎必须原样透传。

省略 =<实现名> 时,该层的实现名取包名。

2.2 requires 语法 已实现

与 provides 同语法。语义为:被命名的层必须解析为被命名的实现, 否则引擎必须在编译开始之前拒绝该构建。

省略 =<实现名> 时,语义为该层必须有供给者。

requires 的条目可以是表 { layer = "mcpp:<层名>=<实现名>", visibility = "public" | "private" }。 字符串等价于 visibility = "public"。private 声明该需求只对本包自己的代码成立、 不越过本包的边界(C++ 运行时孤岛,§7.2);它只允许用于 c++-abi 层,用于其他层时 清单必须以 requires-private-layer 报错 —— 一个映像中的第二个 C 库不是孤岛,而是两个 C 库。

requires 可以写在 [target.<选择器>] 下。生效的需求是 [package] requires 与所有匹配 选择器的 requires 的并集,合并规则与 requires_abi 相同。选择器引用层(compiler、 compiler-runtime、kernel-abi、c-abi、c++-abi)时,需求必须在层解析之后、任何编译开始 之前按规则二检查;引擎禁止忽略这样的需求。

2.3 标准库模块源 已实现

供给 c++-abi 的包可以在 [build] 下声明:

键 类型 含义
std-module 路径 std 模块源,相对包根
std-compat-module 路径 std.compat 模块源
std-module-flags 字符串数组 编译该模块源所需的 flag

三者的 [package] 写法必须继续被接受。[build] 写法应当优先, 因为只有它可以按 [target.'cfg(...)'.build] 条件化。

声明了 std-module 而没有相应 provides 条目的包,引擎必须报错。

部分实现:std-module 与 std-compat-module 是单一路径,不可条件化; 仅 std-module-flags 参与条件合并。

2.4 预制目录的声明 已实现

一个预制目录供给哪一层、实现与版本,必须由测量回答(SPEC-006 §3.9.3): 驱动预处理探针翻译单元得到 C 库的实现与版本。测量回答不了时, [target.<三元组>].sysroot = { path = "…", provides = ["mcpp:c-abi=<实现>"] } 的 provides 作为声明;测量回答了时,provides 用于核对。 由包名点名的预制载荷,接口名仍取包名。

2.5 基座包 已实现(mcpp >= 2026.11.10.1)

在 sysroot 的位置被指名(工程、本机配置、平台或提供者的 sysroot)、且根目录带 mcpp.toml 的预制目录是基座包。 它按普通包读取,不带角色标记:

  • 一种读法(mcpp >= 2026.11.12.1)。 清单陈述包根即 sysroot,所以基座包必须按 sysroot 渲染,不论其目录怎样 命名;目录布局(include/features.h 与 lib64,即生态自身的 glibc 包布局)只对不带清单的目录探测。产物不由本机 加载时的链接规则见 SPEC-016 §6。

  • [package] provides 回答它供给的层(mcpp:c-abi=…、mcpp:c++-abi=…、mcpp:compiler-runtime=…)与 hosted-standard-library;这些层的来源为 prebuilt,报告附基座包的 <名>@<版本>。

  • [build] 的键与依赖图中供给者的含义相同,路径相对包根,包根即 sysroot:

键 含义
include_dirs C++ 库的头文件目录(规则二:-nostdinc++ 之后加入)
ldflags 链接的 C++ 库;供给 c++-abi 时引擎加 -nostdlib++,不再由驱动选择工具链的 C++ 库
std-module、std-compat-module、std-module-flags §2.3
compiler_runtime_dir 编译器运行时所在的目录;clang 读作资源目录(-resource-dir),gcc 经 -B 读取
  • 一个基座包是一个身份的 sysroot。驱动能在一个根内区分架构的系统(MinGW-w64),一个包可以服务该系统的全部身份; 否则每个身份一个包,平台以各提供者的 sysroot 为各身份指名(SPEC-016 §3)。
  • 基座声明 c++-abi 时,C++ 头文件、std 模块与链接的 C++ 库必须取自基座;工具链载荷的 C++ 运行时及为其宿主 构建的归档禁止进入该构建。基座未给出 std-module 时,该构建没有 std 模块。
  • provides 是声明(§2.4):测量回答不了 C 库时,mcpp:c-abi 回答它。
  • driver-internal 的 MinGW-w64 行上,基座包是 clang 的 sysroot;静态链接取基座的归档而非其导入库(SPEC-016 §3)。

3. 规则

3.1 规则一:每层恰好一个供给者 已实现

同一层出现两个供给者时,引擎必须在解析期报错, 并必须同时指出两个包及各自进入依赖图的路径(直接依赖或传递依赖)。

同一个包在 provides 中为同一层给出多条条目时,视为一个供给者; 其中命名了实现名的一条必须优先。

目标基座是一个供给者:它供给 c-abi 时,依赖图中再有 c-abi 的供给者,必须按本规则报错。 一个目录作为 --sysroot 寻址,当且仅当它供给 c-abi(c-abi 独占律)。

3.2 规则二:为其下方的层配置过 部分实现

一个实现必须在它曾被配置的层之上使用。引擎必须执行:

  • 编译器载荷的 c++-abi,仅在 c-abi 同样来自该载荷,或来自一个被测量为同一实现的目标基座时可用;
  • 后一种情形下,编译器运行库所需的 C 库版本(其 version need 中最高的 GLIBC_x.y)禁止高于基座的版本, 违反时必须在编译前拒绝并说出两个版本(可测量的下限);
  • §2.2 声明的每一条 requires。

「compiler-runtime 必须与 compiler 同族」当前依赖供给者自行声明 requires,引擎不独立推断 —— 因为推断需要一张实现名到族的映射表, 而 §1.1 禁止引擎持有实现名。

3.3 规则三:跨来源接线 部分实现

两层来自同一来源时,引擎禁止介入它们之间的关系。 两层来自不同来源时,只有引擎同时知道两边的地址,因此必须由引擎接线。

当前实现覆盖「两层均来自图」与「两层均来自载荷」。 「一层预制、一层来自图」的接线尚不完整。

可叠加的层(内核头文件、第三方头文件与库)由引擎以 -isystem / -L 接线;本机的目标基座缺少 内核头文件时,引擎必须补上生态的内核头文件,而禁止写入基座。

c-abi 层来自图时,链接的库搜索同样属于图:引擎禁止让驱动器从宿主推导库目录, 这样的链接上每一个 -L 必须位于工具链存储、构建目录或图中某个包之内; [build] allow_host_libs = true 解除这一约束。由 clang 链接的 ELF 目标已实现 (mcpp 2026.9.26.1,链接带指向构建目录内空目录的 --sysroot,mcpp#696);经 MinGW 驱动器的 PE 链接尚未实现,待其从宿主 MinGW 解析的导入库清点完成。

3.4 规则四:三元组是请求 已实现

三元组的 env 段必须被当作对 c-abi 的请求,而非其答案。

  • 段缺席 ⇒ 未作陈述,任何供给者都不与之矛盾;
  • 段存在且与解析出的 c-abi 不同,且后者来自图 ⇒ 引擎必须报出该不一致, 并必须给出不含该段的目标拼写;禁止据此使构建失败。

段命名的是 C 库或工具集时,请求按其 C 库比较:gnu 在 Windows 上请求 ucrt,在其他系统上请求 glibc;msvc 请求 ucrt。编译器载荷供给 c-abi 时,该层的实现名同样是 C 库的名字 (x86_64-windows-msvc 与 x86_64-windows-gnu 的载荷均回答 ucrt),因此 requires = ["mcpp:c-abi=ucrt"] 被 Windows 上每一条带 Win32 C 运行时的路线满足(mcpp >= 2026.11.10.2)。

拒绝曾被实现并被实测否掉:它打破了每一个把宿主目标拼作 x86_64-linux-gnu 的工程与 CI 配置 —— 而那正是 mcpp toolchain list 打印的拼写。 判据是该请求不改变任何东西:图两种写法下都供给同一个 C 库, 因此该段是被忽略而非被违反。

规范化会把 x86_64-linux 写成 x86_64-linux-gnu,因为身份必须是全的。 请求必须在规范化之前捕获;报告应当显示工程书写的拼写。

段缺席而 c-abi 来自图时,身份取实际 C 库的行(x86_64-linux 依赖 musl 时为 x86_64-linux-musl),输出目录随之(SPEC-016 §4.5;mcpp >= 2026.11.9.1)。

3.6 能力的缺省提供者 已实现(mcpp >= 2026.11.9.1)

层的机制推广到普通包:包以 requires 声明所需能力,以 [defaults."<能力>"] <选择器> = <包> 声明缺省 提供者。解析顺序为依赖图 > 平台 > 缺省:图中已有包提供该能力(使用者的选择,或平台为其层加入的包)时 禁止加入缺省;否则取与目标匹配的选择器的缺省,* 在其余选择器之后。缺省提供者是声明它的包的依赖。

3.7 C 环境的声明 已实现(mcpp >= 2026.11.9.1)

hosted 与否是 C 环境的一个声明:提供 hosted-standard-library 的 sysroot、c-abi 供给者或 hosted = true 的提供者使裸机行不加 -ffreestanding,main 不按 freestanding 处理。

3.8 层供给者的需求不成环 已实现(mcpp >= 2026.11.10.1)

依赖图供给的 compiler-runtime、kernel-abi、c-abi、c++-abi 各层,其供给者的 requires 指向另一层时形成一条边。 这些边禁止成环:成环时引擎必须在层解析之后、任何编译之前拒绝(layer-cycle),写出环上每个包、它供给的层与 它需要的层。例:写在 C 库之上的 kernel-abi 实现(需要 mcpp:c-abi)与写在 kernel-abi 之上的 C 库(需要 mcpp:kernel-abi)不能同处一图;二者相互调用的递归在运行时才出现。

3.5 目标表的约定何时生效 已实现

目标行的 pin 命名的是供给该目标 C 库的载荷,不是偏好的编译器。 它必须仅在两个条件同时成立时生效:清单对该目标未作陈述, 且依赖图中无人供给 kernel-abi 或 c-abi。

第二个条件在依赖解析之后才可知,因此工具链必须在其之后解析。 过早决定被双向实测否掉:无条件应用会替换用户用 mcpp toolchain default 设下的工具链;不应用会让一个零依赖的交叉构建从可用变为不可用。


4. 报告

4.1 默认输出 已实现

构建必须报告解析出的结果而非清单中的意图。 默认应当只列出来源不是编译器载荷的层;五层均来自载荷时应当只输出目标行。

MCPP_VERBOSE=1 时必须列出全部五层。

4.2 诊断 已实现

诊断必须列出该判断所依据的每一层,包含来源为编译器载荷的层。

4.3 标识 已实现

报告应当使用与 [dependencies] 中一致的包名拼写。 全限定名应当仅在以下三种情形出现:MCPP_VERBOSE、 同一次构建中两个短名相同、以及诊断信息。


5. 兼容性

5.1 未知名字 已实现

谁的清单决定答案。

  • 出现在根工程清单中的未知层名,引擎必须报错。 这是作者自己的拼写,而他正看着这次构建。
  • 出现在依赖清单中的未知层名,引擎禁止据此使构建失败; 必须警告并忽略该层。该清单是对着一个更新的引擎写的。

清单中其它位置的未知键,引擎禁止据此使整份清单加载失败;应当警告并忽略。

第二条来自一次实测。在其落地之前,一个声明了新层名的包 在该层被命名之前发布的每一个引擎上都无法加载,因此层名词表对已发布的包 永远不可扩展:

error: dependency 'openkal-llvm-runtime': mcpp.toml: error:
       `provides = ["mcpp:compiler-runtime=compiler-rt"]` names no
       capability mcpp knows.

这条规定只在未来的引擎上生效。一个包若要声明某个层名, 其使用者的引擎仍须不早于该层名被引入的版本。

5.2 既有拼写 已实现

  • 能力名 hosted-standard-library 必须继续表示 c++-abi 层;
  • 工具链族拼写 openkal-llvm 必须继续解析,归一为 llvm;
  • [package] 下的三个 std-module* 键必须继续被接受。

6. 目标侧执行 已实现

6.1 组合

一个产物必须按 <run_under…> <runner…> <产物> <参数> 执行。runner 说明用什么执行(模拟器、 运行器包),run_under 说明经过什么到达执行宿主(Bazel --run_under 的语义)。run_under 的值按 覆盖(MCPP_TARGET_<三元组>_RUN_UNDER,空值视为未设置,none 表示清除)、工程([target.<三元组>].run_under)、 机器默认(config.toml 的 [target.<三元组>].run_under)取第一个;没有引擎默认。--no-runner 表示该三元组 在本机是原生的,runner 与 run_under 都必须去掉。两者的程序都必须先在声明的载荷 bin/ 中查找, 再在 PATH 上查找。

6.2 运行器契约

经过 runner 或 run_under 执行时,引擎必须设置:

变量 含义
MCPP_RUN_TRIPLE 目标,mcpp 的规范拼写
MCPP_RUN_KIND exe;产物是目录时为 bundle
MCPP_RUN_MODE run 或 test
MCPP_RUN_ID 每次执行唯一;运行器可能被并发调用
MCPP_RUN_CLOSURE 运行闭包清单的路径:带 schema 版本的 JSON,含产物、它从自身目录加载或读取的文件、基座的根
MCPP_SYSROOT 被陈述的目标基座的根,没有时不设置
MCPP_RUNTIME_FILES 既有的文件清单(TSV),保留

运行器可以包装运行器,argv 必须原样传递;运行器的退出码即程序的退出码。工具专属的变量 (例如 QEMU_LD_PREFIX)禁止由引擎设置,由对应运行器的包从 MCPP_SYSROOT 转换(SPEC-006 §3.11)。

6.3 物理边界

本机没有任何路径能执行的产物,必须在执行前被拒绝,并给出 run_under 的写法。当前的判据: 非 macOS 宿主上的 Apple 产物,且没有 run_under。mcpp test 把这种情形报告为未运行。

7. 一个进程一套 C++ 运行时 已实现

7.1 原理

两套 Itanium C++ ABI 运行时(libstdc++、libc++/libc++abi)共用同样的 ABI 符号名:__cxa_*、 __gxx_personality_v0、std::exception 一族的类型信息与虚表、operator new/delete。同一进程的 全局符号空间里,它们要么各用一半(mcpp#838:抛出用一边、捕获用另一边,带类型的 catch 失效), 要么互相串用。std::runtime_error 等类型在两边的内存布局不同。因此不存在让"多个映像 + 两套 运行时 + 异常跨映像"可靠的链接写法。

7.2 三种模式

模式 成立条件 引擎的义务
U 统一(默认) 一个进程只有一套 C++ 运行时,由 §3.1 的唯一供给者决定:编译器载荷、目标基座或依赖图 requires 不满足时必须在编译前拒绝,并给出补救
I 孤岛 某个组件的 C++ 运行时只存在于它的 C 接口之后,且由包以 visibility = "private" 声明 必须机械验证 §7.3 的全部条件,不满足即拒绝
H 宿主决定 Python 扩展、JNI、宿主插件:c++-abi 由目标基座陈述 包服从基座

能统一就统一:依赖图的 c++-abi 恰为所需实现时,孤岛声明不生效。

7.3 孤岛的条件

  1. 我们一侧只有一个 C++ 映像:按根包声明的全部目标判定,没有含 C++ 的共享库目标。不满足时 必须在规划期以 [cxx-runtime-island] 拒绝。自身运行时静态嵌入并隐藏。
  2. 一套展开器:--unwindlib=libgcc。
  3. 链接顺序:外来运行时由引擎放在自身运行时之后(链接行的 foreignCxxRuntime 位置),规则包 禁止把它写进 $ldflags 之前的位置。
  4. 布局相关的冲突集:链进映像的孤岛目标文件(孤岛包的 role=Object 动作产出)禁止以未定义 符号引用布局相关的冲突集(std::exception 一族中除 std::exception 自身外的构造、析构与 what)。
  5. ABI 层整体绑定到自身运行时:链接后的映像禁止以 GLIBCXX_*、CXXABI_* 版本导入 ABI 层 冲突集中的任何符号。
  6. 不跨运行时按类型捕获:映像引用或定义的 typeinfo,禁止同时由一个建立在外来运行时上的 共享库导出。

条件 4–6 在链接后检查,违反时必须以 [cxx-runtime-collision] 拒绝并列出符号。

各目标格式:上述条件按 ELF 的全局符号作用域推导。PE 按导入表绑定:条件 1 放宽为"我们一侧的 所有映像共用同一个运行时映像",条件 3、5 照常成立。Apple 平台只有 libc++,MSVC ABI 只有一个运行时 家族,两者都不存在孤岛。

7.4 外来运行时的识别

外来运行时必须按类型识别,禁止在链接参数中搜索子串:

  • 链接行上被点名的运行时库(-l:libstdc++.so.6、-lstdc++ 等,按词读取);
  • 链接输入中的共享库,其 DT_NEEDED 含外来运行时的 SONAME(libstdc++.so.*、libc++.so.*、 libc++abi.so.*;只看直接输入,不递归)。

识别出、但没有任何包以 private 声明的外来运行时,按孤岛条件校验:全部满足时构建并警告 (兼容读法 undeclared-cxx-runtime,docs/51),不满足时拒绝。

7.5 依赖图供给的 libstdc++

c++-abi 来自依赖图中的 libstdc++ 供给包、而编译器为 clang 时,引擎必须:以 -nostdinc++ 加供给包的头文件目录编译;以供给包的 std-module 预编译 std(源文件不是模块扩展名时以 -x c++-module 编译);链接带 -stdlib=libstdc++,运行库与展开器仍为 --rtlib=compiler-rt --unwindlib=libgcc(compiler-runtime 层不变,§3.2);向构建程序暴露 MCPP_CXX_STDLIB 与 MCPP_CXX_ABI_ROOT。


变更记录

版本 日期 变更
v1.0 2026-08-24 初版。五层闭集、provides/requires 语法、三条规则、报告与兼容性条款。
v1.1 2026-09-26 规则三增补:c-abi 来自图时,链接不搜索宿主库目录(ELF 已实现,mcpp#696)。
v1.3 2026-10-26 随 mcpp 2026.10.26.1:§2.2 增加 requires 的对象形式与 visibility、[target.<选择器>] requires;新增 §7 一个进程一套 C++ 运行时(U/I/H、孤岛条件、外来运行时按类型识别、依赖图供给的 libstdc++)(mcpp#838)。
v1.4 2026-11-03 随 mcpp 2026.11.3.1:§2.2 选择器引用层的 requires 在层解析之后检查(此前未检查即被丢弃)。
v1.5 2026-11-09 随 mcpp 2026.11.9.1:§3.4 身份随 C 库;新增 §3.6 能力的缺省提供者、§3.7 C 环境的声明(mcpp#911 G3、G7)。
v1.6 2026-11-10 随 mcpp 2026.11.10.1:新增 §2.5 基座包(按普通包读取:provides 回答层,[build] 陈述层,包根即 sysroot;新键 compiler_runtime_dir)与 §3.8 层供给者的需求不成环(layer-cycle)。
v1.7 2026-11-10 随 mcpp 2026.11.10.2:§3.4 写明段与 C 库名的对应,MSVC 载荷的 c-abi 回答 ucrt。
v1.2 2026-10-23 随 mcpp 2026.10.24.1:§1.3 prebuilt 放宽为被点名、按内容识别的预制目录;§2.4 改为由测量回答、provides 声明或核对,标为已实现;规则一增加基座是一个供给者与 c-abi 独占律;规则二增加基座上的编译器运行库与可测量的下限;规则三增加可叠加层;新增 §6 目标侧执行(组合、运行器契约、物理边界)。
v1.8 2026-11-12 随 mcpp 2026.11.12.1:§2.5 基座包只有一种读法(带清单即包根 sysroot,布局只对无清单的目录探测)。