|
1 | | -# mcpp 模板系统(package-based templates)— 设计 + TODO |
| 1 | +# mcpp 模板系统(package-based templates)— 设计 v2 |
2 | 2 |
|
3 | | -> 2026-06-03 · 状态:builtin 模板已实现;**package 模板为设计/TODO(本轮未实现)** |
| 3 | +> 2026-06-04 · 状态:**设计定稿(v2,多级解析)**;builtin `bin|gui` 已实现,package 模板待实现(T1–T8) |
4 | 4 | > 关联:agentdocs/2026-06-03-capability-architecture-rfc.md §9 |
| 5 | +> .agents/docs/2026-06-04-manifest-schema-ownership.md(语法封闭/词汇开放——本设计是该原则在"脚手架"维度的应用) |
5 | 6 |
|
6 | | -## 目标 |
| 7 | +## 0. 目标与现状问题 |
7 | 8 |
|
8 | | -`mcpp new` 的模板不应只有内置几种,而应**复用"库模型":一个库同时携带实现 + 示例 + |
9 | | -可实例化模板**。让库作者把"上手骨架"和库一起分发,消费者一条命令拉起。 |
| 9 | +`mcpp new` 的模板应**复用库模型:一个库同时携带实现 + 示例 + 可实例化模板**, |
| 10 | +让库作者把"上手骨架"随库分发,消费者一条命令拉起。 |
| 11 | + |
| 12 | +现状(v0.0.48)的 builtin `--template gui` 是**反例教材**:imgui 的包名、版本、 |
| 13 | +示例代码硬编码在 `cmd_new` 里 —— 领域词汇泄漏进机制层,且版本已经腐化 |
| 14 | +(builtin 钉 `0.0.2`,生态已到 `0.0.5`,docking/viewports 模板全用不上)。 |
| 15 | +**库每发一版,mcpp 核心就过期一次** —— 这正是要根治的。 |
| 16 | + |
| 17 | +## 1. 设计原则 |
| 18 | + |
| 19 | +> 机制归 mcpp(解析文法、下载、渲染、注入);词汇归库(模板名、内容、默认选择)。 |
| 20 | +> —— 语法封闭 / 词汇开放在脚手架维度的投影。 |
| 21 | +
|
| 22 | +- **架构通用**:mcpp 不认识任何具体库;任何 index 包放个 `templates/` 即获得模板能力。 |
| 23 | +- **使用方便**:常用路径一个词(`--template imgui`),精确路径全可表达(`pkg@ver:tmpl`)。 |
| 24 | +- **版本对齐**:模板随包 tarball 分发 ⇒ 模板版本 == 包版本,`{{self.version}}` |
| 25 | + 注入 ⇒ **版本腐化在结构上不可能发生**。 |
| 26 | +- **纯数据信任边界**:模板 = 渲染 + 拷贝,**无钩子、无脚本执行**(信任面与普通包源码一致, |
| 27 | + 不引入 install-script 攻击面)。 |
| 28 | + |
| 29 | +## 2. 解析文法(多级,核心 UX) |
10 | 30 |
|
11 | 31 | ``` |
12 | | -mcpp new myapp # builtin: bin(默认) |
13 | | -mcpp new myapp --template gui # builtin: imgui.app 窗口骨架(已实现) |
14 | | -mcpp new myapp --template imgui@0.0.2:window # package 模板(本设计) |
15 | | -mcpp new myapp --template imgui:window # 省略版本 = 最新 |
| 32 | +SPEC := NAME # L0 裸名(最方便) |
| 33 | + | PKG ':' TMPL # L1 指定库的某个模板 |
| 34 | + | PKG '@' VER # L2 库默认模板 + 钉版本 |
| 35 | + | PKG '@' VER ':' TMPL # L3 全显式(最精确) |
16 | 36 | ``` |
17 | 37 |
|
18 | | -## 两层模型 |
| 38 | +| 写法 | 语义 | |
| 39 | +|---|---| |
| 40 | +| `--template imgui` | imgui **默认模板**,版本 = 解析到的最新 | |
| 41 | +| `--template imgui:docking` | imgui 的 `docking` 模板,最新 | |
| 42 | +| `--template imgui@0.0.5` | 默认模板,钉 0.0.5 | |
| 43 | +| `--template imgui@0.0.5:docking` | 全显式 | |
| 44 | +| `--template imgui:`(空模板名) | 列出该库全部模板(= `--list-templates imgui`) | |
| 45 | + |
| 46 | +**裸名解析顺序**(L0,类似 PATH 查找): |
| 47 | +1. **builtin 注册表**(离线标准库,词表冻结:`bin`、`lib` —— 真正零网络可用的两种); |
| 48 | +2. miss → 当作**包名**,经 index 解析,取其**默认模板**。 |
19 | 49 |
|
20 | | -### 1) builtin 模板(已实现) |
21 | | -- `--template bin|gui`,硬编码在 `src/cli.cppm cmd_new`。 |
22 | | -- 用途:无网络/零依赖即可起步;`gui` 给出 imgui.app Tier-0 骨架。 |
23 | | -- 这是 fallback,也是 package 模板的"标准库"等价物。 |
| 50 | +冲突治理:builtin 词表冻结(不再新增,杜绝未来遮蔽包名);`gui` 保留为 |
| 51 | +**过渡 alias**(打印 deprecation 提示指向 `--template imgui`,一个 minor 周期后移除) |
| 52 | +—— 这是对现存领域泄漏的退场路径,不是新模式。 |
24 | 53 |
|
25 | | -### 2) package 模板(设计 / TODO) |
26 | | -语法:`--template <pkg>[@<ver>]:<templatename>`。 |
| 54 | +## 3. 库侧约定(词汇层,全部归库) |
27 | 55 |
|
28 | | -**库侧目录约定**(库仓库里新增 `templates/`): |
29 | 56 | ``` |
30 | 57 | imgui-m/ |
31 | | -├── src/ # 库实现 |
32 | | -├── examples/ # 可运行示例 |
| 58 | +├── src/ # 库实现 |
| 59 | +├── examples/ # 可运行示例 |
33 | 60 | └── templates/ |
34 | | - └── window/ # 模板名 = 目录名 |
35 | | - ├── template.toml # 模板元数据(见下) |
36 | | - ├── mcpp.toml.in # 带占位符的清单 |
37 | | - └── src/main.cpp.in # 带占位符的源码 |
| 61 | + ├── window/ # 模板名 = 目录名 |
| 62 | + │ ├── template.toml # 元数据(下) |
| 63 | + │ ├── mcpp.toml.in # .in = 渲染;非 .in = 原样拷贝 |
| 64 | + │ └── src/main.cpp.in |
| 65 | + └── docking/ |
| 66 | + ├── template.toml |
| 67 | + ├── mcpp.toml.in |
| 68 | + └── src/main.cpp.in |
38 | 69 | ``` |
39 | 70 |
|
40 | | -**template.toml**: |
| 71 | +**template.toml**(模板元数据): |
| 72 | + |
41 | 73 | ```toml |
42 | 74 | [template] |
43 | | -name = "window" |
44 | | -description = "Minimal imgui.app window app" |
45 | | -# 占位符 → 取值来源 |
46 | | -[template.vars] |
47 | | -PROJECT = "{{name}}" # mcpp new 的 name |
48 | | -IMGUI_VER = "{{self.version}}" # 该模板所属包的版本(自动) |
49 | | -# 生成后提示 |
50 | | -post_message = "Edit src/main.cpp, then `mcpp run`." |
| 75 | +description = "Minimal imgui.app window" |
| 76 | +default = true # 库的默认模板(0 或 1 个;多于 1 个 → 解析报错) |
| 77 | +post_message = "cd 进项目后 `mcpp run` 即出窗口。" |
| 78 | + |
| 79 | +# 依赖注入:生成的项目自动获得对所属库的依赖(含 features 形态)。 |
| 80 | +# `self` = 模板所属包,版本自动 = 解析到的包版本 —— 根治版本漂移。 |
| 81 | +[template.inject] |
| 82 | +self = { features = [] } # window 模板:无 feature |
| 83 | +# docking 模板则是:self = { features = ["docking-full"] } |
51 | 84 | ``` |
52 | 85 |
|
53 | | -**占位符渲染**:`{{name}}`、`{{self.version}}`、`{{self.name}}` 等;`.in` 后缀文件渲染后去掉 `.in`;非 `.in` 文件原样拷贝。 |
54 | | - |
55 | | -### 解析与执行流程(core) |
56 | | -1. 解析 `--template` 值: |
57 | | - - 不含 `:` → builtin(`bin`/`gui`)。 |
58 | | - - 含 `:` → `pkg[@ver]:tmpl`。 |
59 | | -2. 经现有 fetcher/index 解析并下载该 `pkg@ver`(复用 `mcpp.pm` / `fetcher.cppm`)。 |
60 | | -3. 读取包内 `templates/<tmpl>/template.toml`;若缺失 → 报错并列出该包可用模板(`templates/*/`)。 |
61 | | -4. 渲染:对模板目录递归拷贝,`.in` 文件做占位符替换,写入新项目目录。 |
62 | | -5. 若模板 mcpp.toml 未声明对该库的依赖,自动注入 `[dependencies] <pkg> = "<ver>"`(让模板默认依赖它所属的库)。 |
63 | | -6. 打印 `template.post_message`。 |
64 | | - |
65 | | -### 代码定位(实现时) |
66 | | -- `src/cli.cppm cmd_new`:解析 `--template`,分流 builtin vs package。 |
67 | | -- 新增 `src/scaffold/template.cppm`:模板下载 + 渲染引擎(占位符、`.in` 处理)。 |
68 | | -- 复用:`src/fetcher.cppm` / `mcpp.pm.*`(下载包)、`src/manifest.cppm`(注入依赖)。 |
69 | | -- index:无需改 schema(模板随源码 tarball 分发,已在 `templates/`)。 |
70 | | - |
71 | | -### 发现/列举 |
72 | | -- `mcpp new --list-templates <pkg>[@ver]`:下载并列出 `templates/*/` 及其 description。 |
73 | | -- `mcpp new --template <pkg>:`(空模板名)→ 同上列举提示。 |
74 | | - |
75 | | -## 为什么这样设计(契合架构不变量) |
76 | | -- I5 复杂度下沉:模板由库作者写一次,消费者一条命令继承。 |
77 | | -- I1/I4:`--template gui` builtin 保零配置;package 模板可被 `--list-templates` 解释。 |
78 | | -- 与 capability 模型正交:模板只是"起点物料",不改变解析/能力体系。 |
79 | | - |
80 | | -## TODO(实现顺序) |
81 | | -- [ ] T1 模板字符串解析 `pkg@ver:tmpl`(+ builtin 分流)。 |
82 | | -- [ ] T2 `template.cppm` 渲染引擎(`.in` + `{{var}}`)。 |
83 | | -- [ ] T3 接 fetcher 下载模板包 + 读取 `templates/<tmpl>/`。 |
84 | | -- [ ] T4 自动注入依赖 + post_message。 |
85 | | -- [ ] T5 `--list-templates`。 |
86 | | -- [ ] T6 imgui-m 仓增 `templates/window/`、`templates/headless/` 作为首批样例。 |
87 | | -- [ ] T7 文档 + `mcpp new --help` 更新。 |
88 | | - |
89 | | -## 现状(本轮已落地) |
90 | | -- builtin `--template bin|gui` 已实现并验证(`mcpp new x --template gui` → imgui.app 窗口骨架 → 直接出窗口)。 |
91 | | -- package 模板:本文件为设计与 TODO,留待后续实现。 |
| 86 | +**占位符集**(机制层固定,渲染引擎唯一识别的词表): |
| 87 | + |
| 88 | +| 占位符 | 取值 | |
| 89 | +|---|---| |
| 90 | +| `{{project.name}}` | `mcpp new <name>` 的 name | |
| 91 | +| `{{self.name}}` | 模板所属包名(如 `imgui`) | |
| 92 | +| `{{self.version}}` | 解析到的包版本(如 `0.0.5`) | |
| 93 | + |
| 94 | +(刻意最小;模板内容的可变性靠库出多个模板,而非把渲染引擎做成编程语言。) |
| 95 | + |
| 96 | +## 4. 解析与执行流程(机制层,归 mcpp) |
| 97 | + |
| 98 | +1. `cmd_new` 解析 SPEC(§2 文法 + 裸名两级回退); |
| 99 | +2. builtin 命中 → 原有路径(离线); |
| 100 | +3. 包路径:经现有 fetcher/index 解析下载 `pkg@ver`(**复用包缓存**,与依赖同一套); |
| 101 | +4. 读 `templates/`: |
| 102 | + - 指定 TMPL → 取该目录;缺失 → 报错并列出可用模板; |
| 103 | + - 未指定 → 取 `default = true` 的模板;没有 default → 报错并列出; |
| 104 | +5. 渲染:递归拷贝,`.in` 文件做占位符替换(去掉 `.in` 后缀),其余原样; |
| 105 | +6. 依赖注入:按 `[template.inject]` 把 `self`(以及未来允许的兄弟依赖)写入生成的 |
| 106 | + `mcpp.toml`(若模板清单已显式声明则模板优先,不重复注入); |
| 107 | +7. 打印 `post_message`。 |
| 108 | + |
| 109 | +**发现**:`mcpp new --list-templates <pkg>[@ver]` → 下载(缓存命中则免)并列出 |
| 110 | +`templates/*/`:名字、description、是否 default。 |
| 111 | + |
| 112 | +### 代码定位 |
| 113 | +- `src/cli.cppm cmd_new`:SPEC 解析 + 分流 + gui deprecation; |
| 114 | +- 新增 `src/scaffold/template.cppm`:渲染引擎(`.in` + 占位符)+ template.toml 解析 + 注入; |
| 115 | +- 复用 `src/fetcher.cppm`(下载/缓存)、`src/manifest.cppm`(注入写回); |
| 116 | +- index:**无 schema 改动**(模板随源码 tarball 分发)。 |
| 117 | + |
| 118 | +## 5. 与既有机制的协同 |
| 119 | + |
| 120 | +- **features**:`[template.inject] self.features` 把模板与 feature 体系打通 |
| 121 | + (docking 模板 ⇒ 注入 `imgui = { version = "<self>", features = ["docking-full"] }`); |
| 122 | +- **why/doctor**:生成的项目是普通项目,解析全程可解释(I4),模板不引入任何特殊态; |
| 123 | +- **schema 所有权**:占位符词表、文法、注入键归 mcpp(机制);模板名/内容/默认选择/ |
| 124 | + 注入的 feature 词汇归库 —— 通过 §1 判定法审视,无泄漏。 |
| 125 | + |
| 126 | +## 6. 实施计划(每步:本地验证 → PR → 三平台 CI 绿 → 合入) |
| 127 | + |
| 128 | +- [ ] **T1** SPEC 解析(四级文法 + 裸名 builtin→package-default 回退)+ `gui` deprecation 提示 |
| 129 | +- [ ] **T2** `src/scaffold/template.cppm` 渲染引擎(`.in`、占位符、template.toml) |
| 130 | +- [ ] **T3** fetcher 集成:下载/缓存 `pkg@ver` → 读 `templates/`、default 选择、错误列举 |
| 131 | +- [ ] **T4** `[template.inject]` 依赖注入(self + features)+ post_message |
| 132 | +- [ ] **T5** `--list-templates <pkg>[@ver]`(+ `pkg:` 空名列举) |
| 133 | +- [ ] **T6** imgui-m:`templates/window/`(default)+ `templates/docking/`(inject docking-full) |
| 134 | + → 发版(模板首次随包分发)+ index 收录 |
| 135 | +- [ ] **T7** 文档(00-getting-started、mcpp new --help、imgui-m README)+ e2e |
| 136 | + `69_package_templates.sh`(L0–L3 四级 + 注入 + default + 列举 + 错误路径) |
| 137 | +- [ ] **T8** 闭环:fresh 机器 `mcpp new app --template imgui && cd app && mcpp run` 出窗口; |
| 138 | + `--template imgui:docking` 出四分屏 + 分离窗口;一个 minor 后移除 builtin `gui` |
| 139 | + |
| 140 | +## 7. 验收标准 |
| 141 | + |
| 142 | +- [ ] `--template imgui` 一词出窗口(默认模板,版本自动对齐最新) |
| 143 | +- [ ] `--template imgui@0.0.5:docking` 全显式可复现 |
| 144 | +- [ ] 注入的依赖带正确版本与 features(版本漂移结构性消除) |
| 145 | +- [ ] `--list-templates imgui` 列出 window(default)/docking 及描述 |
| 146 | +- [ ] builtin `bin|lib` 离线可用;`gui` 出 deprecation 提示 |
| 147 | +- [ ] 模板路径零脚本执行(纯渲染拷贝) |
| 148 | +- [ ] e2e 69 全过,三平台 CI 绿 |
0 commit comments