Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions .agents/docs/2026-06-04-manifest-schema-ownership.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# mcpp.toml Schema 所有权设计:语法封闭 · 词汇开放

> 2026-06-04 · 状态: 设计定稿待实施 · 代码锚点基于 main@3a8a3d4 (v0.0.47)
> 关联: agentdocs/2026-06-03-capability-architecture-rfc.md(能力架构 RFC)
> mcpp-index/.agents/docs/2026-06-03-capability-runtime-metadata.md(index 侧 schema)

## 0. 问题

v0.0.47 引入了一批"三档旋钮"字段(`backend=`、`[runtime.<cap>] provider=`、
`[package] platforms`、`[features]`、`[profile.*]`),但**字段归属是临时混合的**:
`backend` 把 imgui 域词汇泄漏进 mcpp 语法层;provider 校验语义偏松("声明了该
capability 的包都算 provider");platforms/profiles 缺校验与逃生口;且全部
**未写进用户文档**(docs/05-mcpp-toml.md 截至 2.7 节)。本设计统一定型。

## 1. 设计原则(判定法)

> **语法封闭,词汇开放(closed grammar, open vocabulary):
> 谁拥有"解析语义"谁定义键;谁拥有"领域知识"谁定义值。**

三条铁律:

- **A. mcpp 只定义机制,不枚举领域词汇。** features 的并集/闭包、capability 的
require/provide/override、profile→编译器旗标、platform→triple,这些解析语义
归 mcpp,键与形状固定。feature 名、capability 名、后端名是领域知识——只出现在
**值**里,绝不出现在 mcpp 代码中(与 doctor 去 #ifdef 同一原则在 schema 层的体现)。
- **B. 不允许包自定义 toml 键。** 键合法性若取决于"先解析目标包",manifest 即丧失
静态可解析性,lockfile/LSP/审计/why 全部层次倒置(Cargo 十年验证的取舍)。
包的扩展点 = **固定机制内的开放值域**,不是新键。
- **C. 包级旋钮统一收敛进 features;糖键入核仅当:① 领域中立(跨生态通用模式)
② 1:1 脱糖、零新增解析语义。**

## 2. 现状代码盘点(main@3a8a3d4)

| 机制 | 代码锚点 | 现状归属 |
|---|---|---|
| dep-spec 键白名单(含 `backend`) | `src/manifest.cppm:593-597 is_dep_spec_key` | 键 mcpp 固定 |
| `backend=` 脱糖 → `backend-<x>` feature | `src/manifest.cppm:631` | 纯糖,值开放 ✅ |
| `DependencySpec.features` | `src/pm/dep_spec.cppm`(features 字段) | 机制固定,值开放 ✅ |
| `[features]` 解析 | `src/manifest.cppm:213 featuresMap` / `:521` | 机制固定,值开放 ✅ |
| feature 激活(default∪requested,闭包,`-DMCPP_FEATURE_*`) | `src/cli.cppm:2969` | mcpp 固定 ✅ |
| `[runtime.<cap>] provider=` | `src/manifest.cppm:121 providerOverrides` / `:897`;应用+校验 `src/cli.cppm:3294-3310` | 形状固定,cap 名开放 ✅;**provider 语义偏松 ⚠️** |
| `[package] platforms` | `src/manifest.cppm:40`;`why` 展示 | 键固定;**值未校验 ⚠️** |
| `[profile.<name>]` | `src/manifest.cppm:188 Profile`;`src/build/flags.cppm` 应用 | 键固定(编译器域);**无 passthrough ⚠️** |
| capability 聚合(`CapabilityProvider`) | `src/build/plan.cppm:72-76` | mcpp 固定 ✅ |
| index 侧能力声明 | mcpp-index `compat.glfw.lua` `runtime.capabilities`(含 `abi:glibc`) | 值由生态定义 ✅;**无 provides= ⚠️** |

## 3. 设计决策(逐项定型)

### D1. `backend=` —— 定型为"通用约定糖",保留
- 理由:「库的多个可替换实现」是跨生态通用模式(GUI 后端 / DB driver / logger sink),
满足铁律 C 两条件(领域中立 + 已是 1:1 脱糖)。
- 定型内容:
1. 键名维持 `backend`(评估过 `impl`,`backend` 语感更明确;不再增设别名)。
2. **约定写入文档**:库支持该旋钮 ⇔ 声明 `[features] backend-<name>` 系列
(mcpp 不校验后端名本身,名字归库)。
3. **strict 校验**(新增):若目标包的 `featuresMap` 已声明任何 `backend-*`
feature,而请求的 `backend-<x>` 不在其中 → warning(`--strict` 下 error)。
目标包零声明时不校验(允许纯 define 用法)。
- 不做:包注册自定义键(违反铁律 B)。

### D2. `provider=` —— 收紧为显式 `provides=` 语义
- index/包侧新增 `provides = ["opengl.glx.driver", ...]`(lua `mcpp` 段 +
mcpp.toml `[runtime] provides`),声明"我兑现这些能力"。
- core 解析进 `RuntimeConfig.provides`;`plan.runtimeProviders` 改为:
capability→provider 的映射**优先取 provides 声明者**;无任何 provides 时
回退现状(声明 capabilities 者视为弱 provider,向后兼容)。
- `provider=` 覆盖校验升级:目标包必须 provides(或弱提供)该能力,否则 warning 照旧。
- capability 命名规范(文档化,不进代码):分层小写 `domain.sub.role`
(`opengl.glx.driver`、`x11.display`)+ 前缀类 `abi:<name>`;mcpp-index 维护
"知名能力名注册表"文档供 doctor/why 提示,**mcpp 代码不枚举**。

### D3. `platforms` —— 固定词表 + 校验
- 值域归 mcpp(它拥有 triple 体系):`linux | macos | windows`(后续随 target
支持扩展)。解析时非法值 → warning(`--strict` 下 error)。

### D4. `[features]` —— strict 校验 + 传递传播(follow-up)
- strict 校验:dep spec 请求的 feature 不在目标包 `featuresMap` 且目标包**有**
`[features]` 表时 → warning(`--strict` error);无表时不校验(纯 define 用法)。
- dep→dep 传递 feature 请求:已知缺口,独立 follow-up(影响解析器,本设计不展开)。

### D5. `[profile.<name>]` —— 固定键 + passthrough 逃生口
- 新增开放值域(固定形状内):`cflags = [...]`、`cxxflags = [...]`、`ldflags = [...]`,
追加在 profile 解析后(I6 完备性;铁律 A 不破——键仍是 mcpp 的,值是用户的)。

### D6. Schema 所有权规范成文
- 本文件 §1 的判定法写入用户文档(见 P5),作为后续任何新字段的准入标准:
新键须回答"解析语义归谁?能否用 features/capability 表达?是否领域中立纯糖?"

## 4. 实施计划(每阶段遵循:本地 build+test+e2e → 小步 commit → 分支 PR → 三平台 CI 绿 → squash 合入)

### P1 `provides=`(D2,跨 mcpp + mcpp-index)
- mcpp:`src/manifest.cppm` RuntimeConfig 增 `provides` + TOML/Lua 双解析
(TOML `[runtime] provides`;Lua `mcpp.runtime.provides`);
`src/build/plan.cppm` 聚合优先级(provides > capabilities 弱提供);
`src/cli.cppm` provider 覆盖校验/why/doctor/resolution.json 同步展示 provides 来源。
- mcpp-index:`compat.glx-runtime.lua` 增 `provides = {"opengl.glx.driver"}`(PR);
schema 文档同步。
- 验证:imgui 消费链 why/doctor 显示 provider=compat.glx-runtime(由 provides 而非弱提供);
e2e 新增 `66_runtime_provides.sh`。

### P2 `backend=` 定型 + features strict(D1+D4)
- `src/cli.cppm` feature 激活处增 strict 校验(warning;`--strict` 全局 flag → error);
`mcpp build --strict` 选项接入 BuildOverrides。
- 验证:imgui 0.0.3(声明 backend-* 前后)+ 故意写错 backend 名 → warning/error;
e2e `67_features_strict.sh`。

### P3 `[profile.*]` passthrough(D5)
- `src/manifest.cppm` Profile 增三个 list 字段 + 解析;`src/build/flags.cppm`
在 profile 应用处追加。
- 验证:`[profile.dist] cflags=["-fno-plt"]` 经 `--verbose` 可见;e2e `68_profile_passthrough.sh`。

### P4 `platforms` 校验(D3)
- `src/manifest.cppm` 解析处校验词表 → warning/strict-error。
- 验证:非法值 warning;e2e 并入 67。

### P5 **文档定型(必做收尾)** —— `docs/05-mcpp-toml.md`
- 新增章节(承接现有 2.7 编号):
- **2.8 `[features]`**:语法、default 集、隐含闭包、`--features`、dep `features=[]`、
`-DMCPP_FEATURE_<NAME>` 约定、strict 行为;
- **2.9 `[profile.<name>]`**:内置 release/dev/dist、opt/debug/lto/strip、
passthrough cflags/cxxflags/ldflags、`--profile`;
- **2.10 `[runtime]`**:library_dirs/dlopen_libs/capabilities/**provides**、
`[runtime.<capability>] provider=` 覆盖语义与校验;
- **2.1 增补**:`[package] platforms`(词表 + CI 矩阵提示 + `why` 展示);
- **2.5 增补**:dep spec `backend = "<impl>"` 糖(脱糖规则 + `backend-*` feature 约定);
- **新增附录「Schema 所有权原则」**:§1 判定法 + 字段归属总表(给后续贡献者的准入标准)。
- 同步:`docs/03-toolchains.md` 提及 abi 能力强制;`docs/00-getting-started.md`
提及 `new --template gui` 与 `why/doctor`。
- 验证:文档内示例逐个真实跑通后才提交(文档中的每段 toml 必须可构建)。

### P6 e2e 收口
- 上述 66/67/68 纳入 `tests/e2e/`(`run_all.sh` 自动发现);linux self-host CI 全量跑。

## 5. 验收
- [ ] provides= 端到端(index 声明 → why/doctor/resolution.json 显示 → 覆盖校验收紧)
- [ ] backend=/features strict 行为(warning 与 --strict error)
- [ ] profiles passthrough 旗标可观察
- [ ] platforms 非法值告警
- [ ] docs/05-mcpp-toml.md 含 2.8–2.10、2.1/2.5 增补、所有权附录,且所有示例可构建
- [ ] 新 e2e 全过,三平台 CI 绿
7 changes: 7 additions & 0 deletions docs/00-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,10 @@ mcpp pack --mode bundle-all # 全自包含,含 libc 与 ld-linux
- [02 — 发布打包](02-pack-and-release.md) — 构建可分发产物
- [03 — 工具链管理](03-toolchains.md) — 切换编译器与多版本管理
- 任意命令的完整选项可通过 `mcpp <cmd> --help` 查阅


## 更多入口

- GUI 起步:`mcpp new myapp --template gui`(imgui.app 窗口骨架,构建后 `mcpp run` 直接出窗口)。
- 解释默认决策:`mcpp why [toolchain|runtime|deps]`;主机能力体检:`mcpp self doctor`;
机器可读解析清单:构建产物 `target/<triple>/<fp>/resolution.json`。
8 changes: 8 additions & 0 deletions docs/03-toolchains.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,11 @@ mcpp 的运行行为可通过以下环境变量调整:
未显式设置 `MCPP_HOME` 时,mcpp 将基于二进制所在目录的上一级路径
自动定位沙盒位置(release tarball 解压至 `~/.mcpp/` 后,`~/.mcpp/`
即为 home),因此 release 版本无需任何环境变量配置即可运行。


## ABI 能力强制

依赖可声明 `abi:<name>` 能力(如 `compat.glfw` 声明 `abi:glibc`)。解析出的
工具链 ABI 不满足任一依赖的 abi 要求时,构建会**尽早失败**并给出修复建议
(例如 musl-static 工具链遇到 abi:glibc 依赖),取代深层的链接/头文件报错。
查看:`mcpp why toolchain`。
92 changes: 92 additions & 0 deletions docs/05-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,18 @@ mylib = { path = "../mylib" }
# Git 依赖
[dependencies]
mylib = { git = "https://github.com/user/mylib.git", tag = "v1.0.0" }

# 长式 dep spec:features 与 backend 旋钮
[dependencies]
imgui = { version = "0.0.3", features = ["docking"] } # 请求该依赖的 feature
widget = { version = "1.0", backend = "glfw_opengl3" } # 糖:= features=["backend-glfw_opengl3"]
```

`backend = "<impl>"` 是**通用约定糖**:1:1 脱糖为请求该依赖的 `backend-<impl>`
feature(库若支持该旋钮,应在自己的 `[features]` 中声明 `backend-*` 系列)。
若目标包声明了 `[features]` 但不含所请求的 feature(含 backend 脱糖结果),
默认给出 warning,`mcpp build --strict` 下报错。

**SemVer 约束**:

```toml
Expand Down Expand Up @@ -188,6 +198,88 @@ toolchain = "gcc@15.1.0-musl"
linkage = "static"
```

### 2.8 `[features]` — 特性(Cargo 式,加性)

```toml
[features]
default = ["base"] # 默认激活集
base = []
docking = ["extra"] # 激活 docking 时隐含激活 extra(传递闭包)
extra = []
```

- 激活来源:包自身 `default` 集 ∪ 显式请求(根包 `mcpp build --features a,b`;
依赖经长式 dep spec `features = [...]` / `backend = "..."` 糖)。
- 每个激活的 feature 在该包的编译中得到宏 `-DMCPP_FEATURE_<NAME>`
(名字转大写,非字母数字转 `_`,如 `backend-a` → `MCPP_FEATURE_BACKEND_A`)。
- **strict 校验**:目标包声明了 `[features]` 表时,请求未声明的 feature 给出
warning;`--strict` 下报错。未声明 `[features]` 的包接受任意请求(纯宏用法)。

### 2.9 `[profile.<name>]` — 构建档案

```toml
[profile.dist]
opt = 3 # -O 级别(数字或 "s"/"z" 字符串)
debug = false # -g
lto = true # -flto(注意:部分打包 gcc 未启用 LTO 插件)
strip = true # 链接期 -s
# passthrough 逃生口(固定键、开放值):
cflags = ["-fno-plt"]
cxxflags = ["-fno-plt"]
ldflags = []
```

- 选择:`mcpp build --profile <name>`,默认 `release`。
- 内置档案:`release`(-O2)/ `dev`、`debug`(-O0 -g)/ `dist`(-O3 + strip;
**不默认开 lto**)。`[profile.<内置名>]` 可整体覆盖内置定义。

### 2.10 `[runtime]` — 主机运行时能力

```toml
[runtime]
library_dirs = ["vendor/lib"] # 烤进产物 RUNPATH 的目录(相对包根)
dlopen_libs = ["libGL.so.1"] # 运行期 dlopen 的 soname(doctor 校验)
capabilities = ["opengl.glx.driver"] # 需要的主机能力(开放命名空间)
provides = ["opengl.glx.driver"] # 显式声明本包兑现的能力(强 provider)

# 显式 provider 覆盖(三档旋钮的"显式"档)
[runtime."opengl.glx.driver"]
provider = "compat.glx-runtime"
```

- **provider 选择**:声明 `provides` 的包(强)优先于仅在 `capabilities` 列出
能力的包(弱,向后兼容);`[runtime.<cap>] provider=` 显式覆盖最优先,
指向依赖图中不存在的 provider 时给出 warning。
- 解析结果可经 `mcpp why runtime`、`mcpp self doctor` 与构建产物
`target/<triple>/<fp>/resolution.json` 查看(默认不是魔法)。
- 能力命名约定:分层小写 `domain.sub.role`(如 `opengl.glx.driver`、
`x11.display`)与前缀类 `abi:<name>`(如 `abi:glibc`,参与工具链 ABI 强制)。

### 2.11 `[package] platforms` — 平台声明

```toml
[package]
platforms = ["linux", "macos", "windows"]
```

声明包支持的平台(CI 矩阵提示,经 `mcpp why` 展示)。词表由 mcpp 固定
(它拥有 target/triple 体系):`linux | macos | windows`;未知值 warning,
`--strict` 下报错。

## 附录 A. Schema 所有权原则(新字段准入标准)

> **语法封闭,词汇开放**:谁拥有解析语义谁定义键;谁拥有领域知识谁定义值。

- mcpp 只定义**机制**(features 并集/闭包、capability require/provide/override、
profile→编译器旗标、platform→triple),键与形状固定;feature 名、能力名、
后端名等**领域词汇只出现在值里**,不进 mcpp 代码。
- **不支持包自定义 toml 键**:键合法性不得依赖"先解析目标包",否则 manifest
失去静态可解析性(lockfile/LSP/审计的前提)。包的扩展点 = 固定机制内的开放值域。
- 包级旋钮统一收敛进 features;糖键(如 `backend=`)进入核心语法须满足:
① 领域中立(跨生态通用模式)② 1:1 脱糖、零新增解析语义。
- 字段归属总表与定型决策见
`.agents/docs/2026-06-04-manifest-schema-ownership.md`。

## 3. 实战示例

### 3.1 简单 Hello World
Expand Down
16 changes: 16 additions & 0 deletions src/build/plan.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,23 @@ BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
for (auto const& cap : package.manifest.runtimeConfig.capabilities) {
if (std::ranges::find(plan.runtimeCapabilities, cap) == plan.runtimeCapabilities.end())
plan.runtimeCapabilities.push_back(cap);
}
}
// Provider mapping (capability -> package), strongest first: packages
// that explicitly `provides` a capability win over packages that merely
// list it in `capabilities` (weak/back-compat providers). Downstream
// lookups take the first match.
for (auto const& package : packages) {
for (auto const& cap : package.manifest.runtimeConfig.provides)
plan.runtimeProviders.push_back({cap, package.manifest.package.name});
}
for (auto const& package : packages) {
for (auto const& cap : package.manifest.runtimeConfig.capabilities) {
bool dup = false;
for (auto& pr : plan.runtimeProviders)
if (pr.capability == cap
&& pr.provider == package.manifest.package.name) { dup = true; break; }
if (!dup) plan.runtimeProviders.push_back({cap, package.manifest.package.name});
}
}
// The same private runtime directories embedded as executable RUNPATH are
Expand Down
Loading
Loading