|
1 | 1 | # 具名 runner、通用命令面、部分后端,与生态闭环 |
2 | 2 |
|
3 | | -2026-09-04 · 生态级方案 **v6**(引擎与 openarch 已实施;§11 是实施回填)· 取代 |
| 3 | +2026-09-04 · 生态级方案 **v8:四条已定,批次已排**(引擎与 openarch 已实施;§11 是实施回填)· 取代 |
4 | 4 | `2026-09-04-commercial-grade-…-plan.md` §2 的槽表设计 |
5 | 5 |
|
6 | 6 | 前置:[`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"] |
102 | 102 |
|
103 | 103 | **三处共用一个机制,不是三个机制。** 这正是「核心只放通用框架」。 |
104 | 104 |
|
| 105 | +⚠️ **而 §12 的工具分档不是第四处** —— 它对齐的是 mcpp 已有的**依赖种类**词汇 |
| 106 | +(`dependencies` / `build-dependencies` / `dev-dependencies`),不是能力机制。复用既有 |
| 107 | +词汇同样是好事,但把四条并列成「同一条纪律」是修辞上的合并,不准确。**两个既有机制, |
| 108 | +各用其所。** |
| 109 | + |
105 | 110 | ### 1.5 ⚠️ 唯一的新风险:首次构建的墙钟 |
106 | 111 |
|
107 | 112 | C 库改为**源码包**后,干净机器上第一次 `mcpp run` 要编一遍 picolibc。全局依赖缓存 |
@@ -170,17 +175,46 @@ publish pack emit toolchain cache index self |
170 | 175 | | 5 | **板级 `mcpplibs/cortex-m-rt` + 模板** | 新建仓 | ❌ | 用户自己写链接脚本与向量表 | |
171 | 176 | | 6 | 真机工具 `xim:probe-rs` | xim-pkgindex | ❌ | `hardware` feature 无从落地 | |
172 | 177 |
|
173 | | -### 4.1 ⭐ `[xlings]` 字段是闭环的接线点 |
| 178 | +### 4.1 ⭐ `[xlings.workspace]` 是闭环的接线点 |
174 | 179 |
|
175 | 180 | ```toml |
176 | 181 | # 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" |
179 | 185 | ``` |
180 | 186 |
|
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。 |
184 | 218 |
|
185 | 219 | ### 4.2 为什么 C 库是源码包而不是 xim 预编译 |
186 | 220 |
|
@@ -358,3 +392,259 @@ aarch64 改 `ELR_EL1`、x86_64 改中断帧)—— 这正是这一层存在所 |
358 | 392 | `mcpp new --template cortex-m-rt && mcpp run` 打印出东西」这条闭环判据的最后两块; |
359 | 393 | `examples/preempt` 之所以能在没有它们的情况下跑通,是因为它是**零 libc** 的 |
360 | 394 | (`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