Skip to content

Commit 00dd2e4

Browse files
committed
docs(plan): v8 — the four decisions, and the two publish orders that run opposite
§15 records what was settled, including that batch 1's engine interfaces become a compatibility contract on release, so their shape is frozen before it merges. §16 re-orders the batches with picolibc parallel rather than queued, and writes down a pair I had not stated together: * the board packages must wait for the ENGINE to publish, because the bare-name lookup they now rely on ships with it — the mirror of "consumers publish first"; * picolibc must publish before the cortex-m-rt version that names it, because a version reference cannot resolve to an unpublished package — the ordinary case of the same rule. ⭐ Which is what the libc feature buys: cortex-m-rt 0.1.0 ships on the zero-libc tier waiting for nobody, and gains the feature at 0.2.0. The near tier is not held behind the far one.
1 parent 98fa831 commit 00dd2e4

1 file changed

Lines changed: 297 additions & 7 deletions

File tree

.agents/docs/2026-09-04-named-runners-and-the-universal-command-surface.md

Lines changed: 297 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 具名 runner、通用命令面、部分后端,与生态闭环
22

3-
2026-09-04 · 生态级方案 **v6**(引擎与 openarch 已实施;§11 是实施回填)· 取代
3+
2026-09-04 · 生态级方案 **v8:四条已定,批次已排**(引擎与 openarch 已实施;§11 是实施回填)· 取代
44
`2026-09-04-commercial-grade-…-plan.md` §2 的槽表设计
55

66
前置:[`2026-09-04-commercial-grade-baremetal-embedded-plan.md`](2026-09-04-commercial-grade-baremetal-embedded-plan.md)
@@ -102,6 +102,11 @@ Cortex-M: provides = ["openarch-backend"]
102102

103103
**三处共用一个机制,不是三个机制。** 这正是「核心只放通用框架」。
104104

105+
⚠️ **而 §12 的工具分档不是第四处** —— 它对齐的是 mcpp 已有的**依赖种类**词汇
106+
(`dependencies` / `build-dependencies` / `dev-dependencies`),不是能力机制。复用既有
107+
词汇同样是好事,但把四条并列成「同一条纪律」是修辞上的合并,不准确。**两个既有机制,
108+
各用其所。**
109+
105110
### 1.5 ⚠️ 唯一的新风险:首次构建的墙钟
106111

107112
C 库改为**源码包**后,干净机器上第一次 `mcpp run` 要编一遍 picolibc。全局依赖缓存
@@ -170,17 +175,46 @@ publish pack emit toolchain cache index self
170175
| 5 | **板级 `mcpplibs/cortex-m-rt` + 模板** | 新建仓 || 用户自己写链接脚本与向量表 |
171176
| 6 | 真机工具 `xim:probe-rs` | xim-pkgindex || `hardware` feature 无从落地 |
172177

173-
### 4.1 ⭐ `[xlings]` 字段是闭环的接线点
178+
### 4.1 ⭐ `[xlings.workspace]` 是闭环的接线点
174179

175180
```toml
176181
# cortex-m-rt/mcpp.toml
177-
[xlings]
178-
deps = ["xim:qemu-arm@9.2.4-1", "xim:probe-rs@0.24.0"]
182+
[xlings.workspace]
183+
"xim:qemu-arm" = "9.2.4-1"
184+
"xim:probe-rs" = "0.24.0"
179185
```
180186

181-
⚠️ **声明 ≠ 安装。** `[xlings] deps``runner_lookup` 知道去哪个载荷的 `bin/`
182-
里找;**真正触发安装的是索引描述符的 `xpm.<平台>.deps`**。两处都要写,判据是
183-
「把 store 里的包改名藏起来,再 `mcpp add` + `mcpp run`,它被装了回来」。
187+
⚠️ **本文 v5 在这里写错了两处,已更正:**
188+
189+
| v5 写的 | 实际(2026.9.3 起) |
190+
|---|---|
191+
| `[xlings] deps = [...]` | **`deps` 已从清单里退休**,`[xlings.workspace]` 是作者写的那一张表。`deps` 仍被接受(根清单里报错、依赖清单里只提示),但不是该写的拼法 |
192+
| 「声明 ≠ 安装,真正触发安装的是 `xpm.<平台>.deps`| **对根工程已经不对了。** `prepare.cppm:3246` 的原话是 *"the contract being added is 'what you declared gets installed'"* —— 根/workspace 清单声明的东西由 mcpp 自动装 |
193+
194+
### 4.1.1 ⚠️⚠️ 但由此浮出一个真正的设计问题:自动安装只覆盖根
195+
196+
`prepare.cppm:3150``runtimeOwnerManifest = wsManifest ? *wsManifest : *m`,
197+
**workspace 或根工程的清单,永远不是依赖的**。于是:
198+
199+
| | 查找(`xlingsDepBinDirs`) | 安装(provisioning) |
200+
|---|---|---|
201+
| 根工程声明 || ✅ 自动装 |
202+
| **依赖(板级包)声明** |**本轮刚修** |**不装** |
203+
204+
⇒ 「板级包知道环境、消费者什么都不声明」这个故事,**查找那一半通了,安装那一半没通**
205+
干净机器上仍要靠**索引描述符的 `xpm.<平台>.deps`** 把工具随包装上。
206+
207+
**而这个不对称可能是对的,不是缺陷。** 两者的性质不同:
208+
209+
* **查找只读机器**。让它跨图是安全的 —— 依赖说「我要 qemu」,mcpp 去看看装没装。
210+
* **安装写机器**。让它跨图是一次**权限升级**:一个传递依赖可以让 mcpp 往你机器上
211+
装任意包。
212+
213+
⇒ 于是分工可以是:**清单里的声明是未经审查的通道,只用于查找;索引描述符的
214+
`xpm.<平台>.deps` 是发布时被审查过的通道,才有资格触发安装。** 板级包两处都写,
215+
而那不是重复 —— 它们回答的是两个不同的问题(「去哪找」与「谁有权装」)。
216+
217+
⚠️ **这一条我没有把握,列为待定** —— 见 §12。
184218

185219
### 4.2 为什么 C 库是源码包而不是 xim 预编译
186220

@@ -358,3 +392,259 @@ aarch64 改 `ELR_EL1`、x86_64 改中断帧)—— 这正是这一层存在所
358392
`mcpp new --template cortex-m-rt && mcpp run` 打印出东西」这条闭环判据的最后两块;
359393
`examples/preempt` 之所以能在没有它们的情况下跑通,是因为它是**零 libc**
360394
(`sysroot = ""`),自带启动与向量表。
395+
396+
---
397+
398+
## 12. 决定:全图安装 + 工具按用途分档
399+
400+
### 12.1 ⭐ 我的「权限升级」反对是错的
401+
402+
我曾主张「让依赖的声明触发安装是一次权限升级」。**那个权限今天已经存在** ——
403+
索引描述符的 `xpm.<平台>.deps` 就在做这件事:你依赖的包现在就能往你机器上装工具。
404+
405+
⇒ 全图安装**不增加任何权力**,它去掉的是一处重复拼写。我把它跟一个「描述符通道
406+
不存在」的世界比了,而那个世界不存在。**选 B。**
407+
408+
### 12.2 ⚠️ 但 B 单独做会装得更多,而工具本来就不必全装
409+
410+
一个板级包声明 `qemu-arm`(跑要用)与 `probe-rs`(上真板要用)。今天:
411+
412+
* 只想 `mcpp build` 的消费者 —— **两个都装**,一个都用不上;
413+
* 用模拟器的消费者 —— probe-rs 白装;
414+
* 而包依赖早就分了 `[dependencies]` / `[build-dependencies]` /
415+
`[dev-dependencies]`,工具却没有这个轴。
416+
417+
**B 与分档要一起做。合起来之后,常见情形装得比今天更少。**
418+
419+
### 12.3 形状:一张表不变,条目可以是标量或表
420+
421+
2026-09-03 那份文档为「`workspace`**唯一**一张表」辩护过,不应推翻。所以分档
422+
写在**条目**上,而不是新开几张表 —— 这与 mcpp 自己的依赖拼法同形
423+
(`dep = "1.0"``dep = { version = "1.0", features = [...] }`)。
424+
425+
```toml
426+
[xlings.workspace]
427+
"xim:qemu-arm" = "9.2.4-1" # 80%:不写就是今天的行为
428+
"xim:codegen" = { version = "1.0", when = "build" } # 20%
429+
"xim:probe-rs" = { version = "0.24.0", when = "run" }
430+
```
431+
432+
| `when` | 什么时候装 | 传播到消费者 |
433+
|---|---|---|
434+
| (不写) | **与今天完全一样**:构建时就装 ||
435+
| `build` | `mcpp build`||
436+
| `run` | 只在 `mcpp run` / `mcpp test`||
437+
| `dev` | 只在**声明它的那个包自己**被开发/测试时 | **** |
438+
439+
**不写 `when` 保持今天的行为,所以没有迁移。** 分档是把范围**收窄**的可选动作,
440+
不是必须回答的新问题 —— 这正是「默认覆盖 80%、其余可配置」。
441+
442+
### 12.4 feature 门控:沿用已有的 `[feature-deps]` 形状
443+
444+
真板工具只在 `hardware` feature 下才需要。包依赖已经有这个机制,工具沿用同一个
445+
拼法而不是发明新键:
446+
447+
```toml
448+
[feature-xlings.hardware]
449+
"xim:probe-rs" = "0.24.0"
450+
```
451+
452+
⇒ 用模拟器的人**永远不下载 probe-rs**
453+
454+
### 12.5 合起来的效果
455+
456+
| 情形 | 今天 | B + 分档 |
457+
|---|---|---|
458+
| 消费者 `mcpp build`(板级包依赖) | 描述符把 qemu + probe-rs 都装上 | **一个都不装** |
459+
| 消费者 `mcpp run`(默认 feature) | 同上 | 只装 qemu |
460+
| 消费者 `mcpp run`(`hardware`) | 同上 | 只装 probe-rs |
461+
| 板级包作者写几处 | 清单 + 描述符**两处** | **一处** |
462+
463+
### 12.6 ⚠️ 实施时会撞到的三条
464+
465+
1. **安装的 stamp 按「列表」计**(`prepare.cppm` 的注释说的)。分档后同一个工程会
466+
在不同命令下要求不同的子集,stamp 必须按 **(子集, 用途)** 计,否则
467+
`mcpp build` 之后的 `mcpp run` 会认为「装过了」而跳过 run 档。
468+
2. **交叉目标的 sysroot 是 mcpp 自己追加进 deps 列表的**,不是作者声明的 —— 它按
469+
性质属于 `build`,且必须不受 `when` 影响(现有注释已说明它不该被 provisioning
470+
改变行为)。
471+
3. **`xlingsDepBinDirs`(查找)必须包含 run 档**,否则 runner 找不到刚装的工具。
472+
本轮已把查找扩到全图,分档时要确认两者的集合定义一致。
473+
474+
### 12.7 与描述符的关系
475+
476+
`xpm.<平台>.deps` 仍然存在,但**不再是板级包作者必须记得的第二处**:清单声明即安装。
477+
描述符保留给「这个 xim 包自身的安装期依赖」这一层,那是 xim 的事,不是 mcpp 工程的事。
478+
479+
---
480+
481+
## 13. 仍待定
482+
483+
1. `when` 的取值是否要第四个(例如 `test``run` 分开)。我倾向不要 —— `mcpp test`
484+
要跑产物,与 `run` 是同一个需求。
485+
2. `dev` 档是否值得做。它是唯一一个**不传播**的档,语义最重而用例最少;可以先只做
486+
`build`/`run`,`dev` 留到有人要。
487+
3. `[feature-xlings.<feature>]` 这个拼法要不要与 `[feature-deps]` 完全对齐(后者的
488+
键是包名,这里的键是 xim 地址)。
489+
490+
---
491+
492+
## 14. 五个问题的解法
493+
494+
整体审查提出的五条,逐条给形状。⭐ 其中第一条的解法把一条「发现」变成了**接口的一次
495+
最小补全**,而不是一个待议事项。
496+
497+
### 14.1 ⭐⭐ openarch 缺的不是第五个组,是 trap 组里缺一个动作
498+
499+
我原先把它记成「可能需要第五个接口组,待议」。审查后这个判断太松:**抢占是 MCU 上用
500+
openarch 的主要理由**,表达不了它,openarch-on-Cortex-M 就只有一半用处。
501+
502+
看四台机器要做的事:
503+
504+
| | 抢占时改什么 |
505+
|---|---|
506+
| Cortex-M |`PSP`,以 `EXC_RETURN` 返回 |
507+
| riscv64 |`sepc`,换陷入桩将要恢复的寄存器区 |
508+
| aarch64 |`ELR_EL1``SP_EL0` |
509+
| x86_64 | 改中断帧的 `RIP`/`RSP` |
510+
511+
**同一个动作,四种写法** —— 这正是这一层存在的理由。而 openarch 的 trap 组今天只能
512+
**观察**陷入(`set_handler`)和**屏蔽**中断,不能**作用于**陷入。
513+
514+
⇒ 解法是一个函数,不是一个组:
515+
516+
```c
517+
/* 协作式:立刻切换,别人切回来时返回。 (已有) */
518+
void arch_context_switch(void* from, void* to);
519+
520+
/* 抢占式:让"这一次陷入"返回到 `to` 而不是被中断的那个上下文;
521+
被中断的存进 `from`。它正常返回给处理程序,切换在异常退出时发生。 */
522+
void arch_trap_switch(arch_trap_frame* f, void* from, void* to);
523+
```
524+
525+
⭐ **两者形状相同,差别只在"什么时候生效"。** 前者是「现在切」,后者是「返回时切」。
526+
527+
能力名 `openarch:preemption`,由后端声明。Cortex-M **能**实现它 —— `examples/preempt`
528+
里那段手写的 PendSV 汇编就是它的实现。⇒ **那段汇编从示例移进后端**,示例退回成
529+
四十行纯调度策略、零汇编,这正是这一层该有的样子。
530+
531+
⚠️ 仍然要**不止一台机器**才能定稿(riscv64/aarch64 各写一次才知道签名对不对),
532+
所以顺序是:先在 Cortex-M 与 aarch32 上各实现一次,再冻结签名。
533+
534+
### 14.2 ⭐ 生态闭环不是两批,是一个包多一个 feature
535+
536+
我原先把「零 libc 档」与「带 libc 档」排成前后两批,等于把一个**现在就能交**的东西
537+
压在一个大工程后面。
538+
539+
而 mcpp 早就有这个轴:`sysroot = ""` 是零 libc 档(`docs/13` 的原词)。所以:
540+
541+
```toml
542+
# cortex-m-rt
543+
[features]
544+
default = ["emulator"]
545+
emulator = {}
546+
hardware = {}
547+
libc = {} # ← 不选就是零 libc 档
548+
549+
[feature-deps.libc]
550+
picolibc = "1.8.12"
551+
```
552+
553+
**`cortex-m-rt` 现在就能发**(零 libc,自带启动与向量表 —— `examples/preempt`
554+
已证明这条路通);`picolibc` 落地后,用户加一个 feature,包加一条 `feature-deps`
555+
556+
**与模拟器/真机是同一个机制的第二次使用。** 不是新概念。
557+
558+
### 14.3 发现性:放进已有的两个地方,不新增噪声
559+
560+
「引擎不认识任何名字」的代价是新用户不知道 `--runner` 存在。三处补,零新概念:
561+
562+
1. **`mcpp explain`** —— 它已经是「告诉我解析出了什么」,加一节「本工程提供的 runner」
563+
是零新概念。`--list-runners` 与它共用一个读点。
564+
2. **`mcpp run --help`** 一行:*"see --list-runners for what this project supplies"*
565+
3. **模板的 README** —— 模板随包走,板级包最清楚自己有什么。
566+
567+
⚠️ **不做**「首次运行时提示一次」:它需要一个 stamp,而这个仓库记过
568+
「提示出现一次就消失」的教训 —— 缓存命中时不重跑,最需要它的那次反而不打印。
569+
570+
### 14.4 ⭐⭐ 工具分档的判据:测「要装什么」,不测「装成了没有」
571+
572+
我原先说这块判据最薄弱,因为验证需要真实 xim 安装,而 e2e 里没有干净环境。
573+
574+
**那是把判据施加在了错误的对象上。** 被断言的是**mcpp 请求安装的集合**,不是安装
575+
成功。而请求是可观察的:`MCPP_NO_AUTO_INSTALL`(或 `--offline`)下,provisioning
576+
不装并**点名它本来要装什么**(`prepare.cppm:3339`)。
577+
578+
⇒ 判据:
579+
580+
```
581+
MCPP_NO_AUTO_INSTALL=1 mcpp build 的输出里 不得 出现 run 档的工具
582+
MCPP_NO_AUTO_INSTALL=1 mcpp run 的输出里 必须 出现 run 档的工具
583+
```
584+
585+
**一次下载都不需要,而且判据带分母**(两条命令的差集正是被测的性质)。
586+
587+
### 14.5 顺序:aarch32 独立成批,并说明它为什么仍值得
588+
589+
我做的顺序与自己的建议相反(先 Cortex-M 后 aarch32),理由成立 —— Cortex-M 才有真实
590+
应用。但 aarch32 那条发现**还没拿到**:它是第一台 **32 位**机器,会挖出
591+
`arch_pte_make_leaf` 返回 `arch_u64` 这条从没被问过的宽度假设。
592+
593+
⇒ 它不因为被推后而失去价值,而且 §14.1 的 `arch_trap_switch` **恰好需要它** ——
594+
签名要在两台不同机器上各实现一次才能冻结。**两件事合成一批。**
595+
596+
---
597+
598+
## 15. 已定的四条(2026-09-04 review)
599+
600+
| # | 决定 | 出处 |
601+
|---|---|---|
602+
| 1 | **`arch_trap_switch`** —— openarch 缺的不是第五个组,是 trap 组缺一个动作 | §14.1 |
603+
| 2 | **`cortex-m-rt``libc` feature 分档**,零 libc 档现在就能发;**picolibc 并行开工**,不排在它后面 | §14.2 |
604+
| 3 | **分档判据用 `MCPP_NO_AUTO_INSTALL`** —— 测「要装什么」,零下载 | §14.4 |
605+
| 4 | **批 1" 的反向发布顺序** —— 板级包必须等引擎发布 | §15.1 |
606+
607+
⭐ 并且:**批 1 的引擎接口(`--runner``[target.X.runners]``mcpp:runner-named=`
608+
`mcpp::runner(name, tok)`)一经发布即成兼容契约。** 本轮之后改它们要付迁移代价,
609+
所以形状在批 1 合入前定稿 —— 已定稿。
610+
611+
---
612+
613+
## 16. 批次(每仓一个 PR,并行的用同一列)
614+
615+
```
616+
┌─ 批 1 mcpp #551 ──────────────────────────────────┐ 36/36 绿
617+
│ 具名 runner · emit sbom · --locked · 裸名跨全图 │ ← 解锁其余一切
618+
└────────────────────┬───────────────────────────────┘
619+
│ 必须先发布 2026.9.4.2
620+
┌───────────────────┼───────────────────┬────────────────────┐
621+
▼ ▼ ▼ ▼
622+
批 1' openarch 批 1" 两个板级包 批 2 mcpp+既有包 批 5 mcpp-index
623+
部分后端+preempt 裸名简化 [xlings] 工具分档 picolibc +
624+
(已提交未推) ⚠️ 等引擎发布 (when/feature-xlings) builtins
625+
│ │
626+
▼ │
627+
批 4 openarch 批 3 新仓 ◄────────────────┘
628+
arch_trap_switch + aarch32 cortex-m-rt
629+
两机各实现一次后冻结签名 零 libc 档先发,
630+
│ picolibc 到位后加 libc feature
631+
632+
批 6 xim-pkgindex probe-rs → 真机路径可落地
633+
```
634+
635+
### 16.1 ⚠️ 两条发布顺序,方向相反
636+
637+
1. **批 1"(板级包)必须等引擎发布。** 裸名写法依赖批 1 的「查找跨全图」;先发板级包
638+
会让老引擎上那两个包直接坏掉。—— 「消费者先发布」的**镜像**
639+
2. **批 5(picolibc)必须先于批 3 引用它的那一版发布。** `cortex-m-rt` 要写
640+
`[feature-deps.libc] picolibc = "…"`,而按版本引用一个未发布的包解析不到。
641+
—— 这是「消费者先发布」的**正例**
642+
643+
⇒ 所以批 3 分两版:**`0.1.0` 零 libc 档,不等任何人**;`0.2.0``libc` feature,
644+
等批 5。⭐ 这正是 §14.2 那个 feature 分档买到的东西:**近档不被远档挡住。**
645+
646+
### 16.2 并行关系
647+
648+
* 批 2、批 5 与批 1'/1" **互不依赖**,可同时进行。
649+
* 批 4 依赖批 1'(要有 Cortex-M 后端才能在上面实现 `arch_trap_switch`)。
650+
* 批 6 独立,任何时候都能做;它只在批 3 的 `hardware` feature 要真跑时才成为阻塞。

0 commit comments

Comments
 (0)