Skip to content

Commit 0e89ee2

Browse files
committed
Merge branch 'feat/622-t7-docs' into feat/622-ui-framework-on-three-rows
2 parents ef59f41 + bb40b91 commit 0e89ee2

14 files changed

Lines changed: 752 additions & 44 deletions

‎.agents/docs/2026-09-12-622-a-ui-framework-on-android-ios-and-web.md‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ status: active
55

66
# A UI framework on Android, iOS and Web: where each item of #622 lands, and the three it does not list
77

8-
**Status:** proposal, for review. Nothing here is implemented. Every "measured"
9-
statement was checked against `main` at `85df514a` (mcpp 2026.9.12.2),
8+
**Status:** implemented in mcpp (engine items A1 to A7, A10, A11; the version
9+
is assigned at release); ecosystem changes follow. Every "measured" statement
10+
below was checked against `main` at `85df514a` (mcpp 2026.9.12.2),
1011
`mcpp-plugins` at `9064107` (0.6.0) and `openxlings/xim-pkgindex` at `571f845`,
11-
not against the issue's own citations.
12+
not against the issue's own citations, and reflects the state at that commit
13+
rather than the engine that landed afterward.
1214

1315
## 0. Scope, and the ledger it starts from
1416

@@ -474,6 +476,18 @@ mt = { threads = true }
474476
(ignored)` and loses the requirement, which is the behaviour it had before
475477
2026.9.12.2.
476478

479+
**Correction (2026-09-12, T4).** This does not hold. `mcpp 2026.9.12.2`
480+
reads `[target.'cfg(linux)'] requires_abi = { threads = true }` with no
481+
diagnostic at all — measured by running it against that exact binary. The
482+
key is a direct, inline-table-valued key of the selector table, not a
483+
sub-section, and the schema sweep that would warn on an unsupported
484+
`[target.<sel>]` key skips every table-valued key on the assumption that a
485+
table is the conditional channel; an inline table is the same TOML value
486+
shape and falls through the same sweep unreported. So an older engine
487+
**silently ignores** the requirement rather than warning about it — a
488+
package that relies on the refusal to protect an unconditional switch must
489+
state its own engine floor.
490+
477491
**Criteria.** A library with the Linux table and a root with
478492
`cfg(not(os = "emscripten"))` threads builds for both Linux and Web; remove the
479493
root table and the Linux build is refused naming `cfg(linux)`; the Web build is

‎CHANGELOG.md‎

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,165 @@
55
66
## [Unreleased]
77

8+
### wasm 产物契约:启动器改名为 `.js`,`.wasm` 是隐式输出(#622 A5)
9+
10+
`wasm32-emscripten` 行此前用的是宿主借来的裸名 —— `bin/<name>`(Linux 宿主)或
11+
`bin/<name>.exe`(Windows 宿主),因为 `artifact_naming` 对这一行落到了「未知 OS
12+
就套用宿主拼法」的兜底分支。改为 `bin/<name>.js`:这是 runner 执行的文件,而
13+
Emscripten 自己的 CMake 工具链(`CMAKE_EXECUTABLE_SUFFIX ".js"`)与 Rust 的
14+
`wasm32-unknown-emscripten` target spec(`exe_suffix: ".js"`)各自用一行钉死了
15+
同一个后缀。
16+
17+
- `bin/<name>.wasm` 是同一条链接边的**隐式输出**,而不是靠命名约定推断出来的
18+
旁路文件:`ninja -t clean`、增量检查与 `mcpp pack` 都经由图看到它。
19+
- `mcpp pack` 暂存「词干家族」——启动器旁边每一个 `<name>.<任意>` 的文件,包括
20+
`--preload-file` 产生的 `<name>.data`——而不是一份写死的扩展名表;`.wasm`
21+
缺席时 `mcpp pack` 拒绝,而不是把一个没有模块的启动器发出去。
22+
- `kind = "shared"` 在这一行上被拒绝,点名 `-sSIDE_MODULE`:side module 需要
23+
一种 mcpp 不渲染的链接契约,让它落到兜底命名只会产出一个存在却打不开的
24+
`.so` 形状的文件。
25+
- **这是对 2026-09-11 才发布的那一行的一次可见改名**:2026.9.11.3 把
26+
`wasm32-emscripten` 发布为 `verified` 时用的正是那个裸名。已知的唯一消费者
27+
与每一个尚未写出的 `${mcpp.target_file:}` 用法都会在这次改名后编码新名字,
28+
所以选择现在改而不是等更多消费者出现。
29+
- `LinkIntentFlavor::Wasm` 不再落到 `Elf` 的兜底分支上:它是 ELF 的拼法本身
30+
(`-l<name>`,emcc 接受它),只是 `frameworks` 与 `link_library_dirs` 在这一行
31+
上不产生任何标志。
32+
- 判据:`tests/e2e/650`。
33+
34+
### `kind = "app"`:一个平台的事实,不是一处 cfg 门(#622 A3)
35+
36+
`[targets.<name>] kind = "app"` 是继 `bin` / `lib` / `shared` 之后第四个取值,
37+
意思是「用户启动的那个东西」。它的链接形态完全由**行**决定:在 ELF、PE、Mach-O
38+
各行与 `wasm32-emscripten` 上与 `bin` 相同;在 `*-linux-android` 上与 `shared`
39+
相同,产出 `libmyapp.so`,即 `System.loadLibrary` 与 manifest 里
40+
`android:name` 命名的那个文件。`main` 在每一行上都还是指出一个翻译单元;
41+
`windows_subsystem` / `windows_entry` 接受 `app` 与接受 `bin` 完全相同。
42+
43+
- `mcpp pack` 把一个 `app` 当作程序 target 处理,不论这一行把它链接成什么文件。
44+
在形态是共享库的行上,不带 `--format` 运行 `mcpp run` 会被拒绝,点名 `--format
45+
apk` 与提供它的成员。
46+
- **兼容性**:早于本版本的 mcpp(2026.9.12.2 及更早)按名字拒绝这个取值——
47+
`targets.<name>.kind must be 'bin', 'lib' or 'shared'; got 'app'`,三种
48+
已知取值。这是正确的:一份要求引擎产出不出来的形态的 manifest 不应该构建。
49+
- 判据:`tests/e2e/652`(ELF 行)、`tests/e2e/652b`(Android 行,`# requires:
50+
android-ndk`)。
51+
52+
### `mcpp::deploy`:构建程序部署自己生成的文件,协议升至 11(#622 A4)
53+
54+
`[runtime] deploy` 只能点名包里已经存在、按包根解析的文件,而一个 action 一步
55+
之后才写出的文件通常是 `MCPP_OUT_DIR` 下的绝对路径,manifest 键够不到它。新指令
56+
`mcpp:deploy=<from>\t<to>`(typed API:`mcpp::deploy(from, to)`)补上这个缺口:
57+
`<from>` 可以是绝对路径或按包根解析,`<to>` 遵守与 manifest 键相同的规则
58+
(`/` 分隔、禁止 `..`、`"."` 表示可执行文件自己所在的目录)。
59+
60+
- 传入一个 action 自己声明的输出,让拷贝边天然依赖上那个 action——边的输入
61+
就是那份输出,ninja 据此排序。
62+
- 并入被 `link-lib`/`link-search`/`link-flag` 喂入的同一个 `LinkIntent`,因此
63+
到达消费者的 `bin/`,并被 `mcpp pack` 按格式各自的布局暂存(`.apk` 落进
64+
`assets/`,`.app` 落进 bundle 的可执行文件目录,web 落进静态目录)。
65+
- 像 `runner` 与 `warning` 一样持久化进构建缓存:一次缓存命中的重放,即使
66+
`bin/` 被手工删除,也仍然写出被部署的文件。
67+
- **兼容性**:协议版本升至 11。早于本版本的 mcpp 调用 `mcpp::deploy()` 在
68+
build.mcpp **编译期**就失败,因为那个引擎自带的 `mcpp` module 里没有这个
69+
函数——与 v5 起历次协议升级同一个代价。
70+
- 判据:`tests/e2e/651`。
71+
72+
### `MCPP_TARGET_MIN_PLATFORM_VERSION`:平台部署下限交给构建程序(#622 A11)
73+
74+
此前 `dist-apple` 一类成员写着「mcpp 不把编译期部署目标暴露给构建程序」,只能由
75+
项目在成员自己的选项里重复一遍这个值,而这个副本会与 manifest 的真实答案悄悄
76+
走样。新增环境变量 `MCPP_TARGET_MIN_PLATFORM_VERSION`,typed reader
77+
`mcpp::min_platform_version()`:macOS 上是 `macos_deployment_target` 或引擎
78+
自带的默认值 `14.0`;iOS 上是 `ios_deployment_target` 原样给出,项目未声明时
79+
为空;`*-linux-android` 上是 `min_api_level`,或已解析 NDK 载荷给出的回落值;
80+
其余每一行为空。取的是有效三元组携带的那个值,并进入重跑键——与既有的
81+
`min_platform_version()`(链接与标准库预构建已经在用的那个函数)是同一个答案,
82+
只是多了一个读者。
83+
84+
- 判据:`tests/e2e/651` 断言 Linux 上该值为空。
85+
86+
### `abi.exceptions`:`abi` 表的第二个成员(#622 A1)
87+
88+
`[target.<selector>.abi]` 新增 `exceptions`(布尔),只在 `os = "emscripten"`
89+
上渲染 `-fexceptions`——经方言 flag 进入编译行,也进入链接行;在其余每个目标上
90+
什么都不产生,因为那里异常本来就是默认开启的。clang 把异常模型记进 BMI 并拒绝
91+
一个与之不一致的导入者,这与 `threads` 要求整个产物一致的理由相同,所以它进的
92+
是同一张表,而不是 `cxxflags` 里的一条 flag。
93+
94+
- 不带这个成员时,失败发生在**运行时**而不是链接时:一个跨 `import std` 边界
95+
抛出异常的 Web 程序照常编译链接,只在 `throw` 真正执行时以
96+
`Aborted(Assertion failed: Exception thrown, but exception catching is not
97+
enabled. ...)` 中止。
98+
- 接受的成员集合变为 `threads | exceptions`;未知成员被拒绝,同时列出两者。
99+
- `requires_abi = { exceptions = true }` 是依赖声明需求的形式,与 `threads`
100+
同构;根包未满足时的拒绝信息指出包(或 feature)与成员名。
101+
- **兼容性**:`abi` 表的成员是兼容性规则里唯一的例外——早于本版本的 mcpp
102+
按名字拒绝一个它不认识的成员,这是正确的:一份要求引擎渲染不出来的开关的
103+
根 manifest 不应该构建。
104+
- 判据:`tests/e2e/653`(`# requires: elf`,emsdk 载荷缺席时打印 SKIP 并
105+
仍以 PASS 退出)。
106+
107+
### `requires_abi` 落到 target 轴上(#622 A6)
108+
109+
`[package] requires_abi` 与 `[features.<f>] requires_abi` 是无条件的一份需求;
110+
一个需求局限于某个平台的依赖此前无法表达它。现在可以直接写在承载对应
111+
`sources` 的选择器上:`[target.<sel>] requires_abi = { ... }` 以及
112+
`[target.<sel>.feature-requires-abi] <feature> = { ... }`(命名沿用
113+
`feature-deps`、`feature-xlings` 的既有形式)。需求集合是包级、feature 级与
114+
每一个命中的选择器级的并集,只对命中已解析目标的选择器生效;根包未满足时的
115+
拒绝按原样点名那个选择器,例如 `[target.'cfg(linux)']`。
116+
117+
- **兼容性(实测,纠正了设计记录最初的推断)**:`mcpp 2026.9.12.2` 读到
118+
`[target.<sel>] requires_abi` 时既不警告也不报错,**静默忽略**这项需求。
119+
它是选择器表下一个取值为表的键,而旧引擎的 schema 清扫把每一个取值为表的
120+
键都当作条件通道跳过;`requires_abi` 恰好是同一种 TOML 形状(内联表),
121+
于是不受任何报告地漏过同一次清扫。依赖这份拒绝来保护一次无条件构建的包,
122+
因此要自己声明引擎下限,而不能指望旧客户端替它发现这个缺口。
123+
- 判据:`tests/e2e/654`。
124+
125+
### `frameworks` 成为按 target 的键(#622 A2)
126+
127+
`[target.<sel>.runtime]` 的词汇表从 `libraries`、`link_library_dirs` 扩到
128+
`frameworks`。语义与 `libraries` 相同:追加在顶层 `[runtime] frameworks` 之后,
129+
只在 Mach-O 各行渲染为 `-framework <name>`,其余各行不产生任何标志——`AppKit`
130+
不存在于 iOS SDK,`UIKit` 不存在于 macOS 的,这是 `[runtime]` 里第一个必须
131+
按 target 区分的键。
132+
133+
- **兼容性**:与既有的 `[target.<sel>.runtime]` 键相同——早于本版本的 mcpp
134+
报出并忽略 `[target.macos.runtime] has unsupported key 'frameworks'
135+
(ignored)`,manifest 在旧引擎上仍能构建,只是拿不到这个键的效果。
136+
- 判据:`tests/unit/test_target_runtime_frameworks.cpp`(本批未新增覆盖它的
137+
e2e)。
138+
139+
### `[package] platforms` 的词表扩到六个平台(#622 A7)
140+
141+
词表从 `linux | macos | windows` 扩到
142+
`linux | macos | windows | ios | android | emscripten`。一个平台名是三元组的
143+
`os`,除非某个 `env` 自己命名了一个平台——Android 各行 `os = "linux"`、
144+
`env = "android"`,因此 `linux` 不覆盖它们;Web 行用 `emscripten` 而不是
145+
`web`,这是 `cfg(...)` 选择器语法已经在用的词。`mcpp pack` 的覆盖检查、
146+
`mcpp doctor` 的「已声明平台」一行与校验器三处一起改,校验器不再是写死的
147+
三条候选三元组,而是逐一询问引擎认识的每一行。
148+
149+
- **兼容性**:不是一次收紧——旧词表之外的值此前就只产生警告(`--strict`
150+
下报错),新词表只是让 `ios`、`android`、`emscripten` 不再落进那条警告。
151+
- 判据:`tests/e2e/67_features_strict.sh`。
152+
153+
### `mcpp run --format <name>`:运行一个不是链接产物本身的产物(#622 A10)
154+
155+
一个 Android **应用程序**是一个 `.apk`,一个 iOS 应用程序是一个已安装的
156+
`.app`,两者都不是 `mcpp run` 默认执行的链接产物。`--format` 复用 `mcpp pack`
157+
的同一个 flag:`mcpp run --format <name>` 先按 `<name>` 打包(与
158+
`mcpp pack --format <name>` 相同的两遍与暂存树),再运行打包报出的那个产物,
159+
经由为一个程序解析出的 runner——项目的 `runner`、依赖的 `mcpp::runner(...)`、
160+
载荷描述文件的,顺序不变。
161+
162+
- `--format` 与 `--no-runner` 同时给出会被拒绝;在 `kind = "app"` 的形态是
163+
共享库的行上不带 `--format` 运行同样被拒绝,两处都点名 `--format` 与已解析
164+
图提供的格式集合。`mcpp test` 不受影响。
165+
- 判据:`tests/e2e/656`。
166+
8167
## [2026.9.12.2] - 2026-09-12
9168

10169
2026.9.12.1 未单独发布,其条目并入本版本。

‎docs/04-mcpp-toml.md‎

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,50 @@ the loader opens and the import library the linker consumes, with the export
156156
list generated from the objects on the MSVC ABI (which exports nothing without
157157
`__declspec(dllexport)` or a `.def`). See `tests/e2e/08`, `257` and `259`.
158158

159+
#### `kind = "app"` — the thing a user launches (mcpp 2026.9.12.3+)
160+
161+
```toml
162+
[targets.myapp]
163+
kind = "app"
164+
main = "src/main.cpp"
165+
```
166+
167+
`app` names the same fact on every row — the program a user launches — and
168+
each row supplies its own file for it:
169+
170+
| Row | Form of `app` | File |
171+
|---|---|---|
172+
| ELF, PE, Mach-O rows, `wasm32-emscripten` | identical to `bin` | `myapp`, `myapp.exe`, `myapp.js` |
173+
| `*-linux-android` | identical to `shared` | `libmyapp.so` |
174+
175+
On `*-linux-android` the platform loads an application as a shared library
176+
into a Java process (`System.loadLibrary("myapp")`, `android:name` in the
177+
manifest); there is no executable form of an application on that row. On
178+
every other row `app` links exactly as `bin` does, and the produced file is
179+
byte-identical to a `bin` target's.
180+
181+
`main` keeps its meaning on every row: it names the translation unit that
182+
defines the entry point. Where `app` is an executable, that entry is `main`
183+
itself. On `*-linux-android` the file is compiled as a translation unit of
184+
the shared library instead, and the platform's own entry
185+
(`ANativeActivity_onCreate`, or the JNI exports it declares) is the
186+
platform's contract, not a name mcpp assigns. `exports` (above) applies to an
187+
`app` target exactly as it does to a `shared` one, and `windows_subsystem` /
188+
`windows_entry` (below) accept `app` exactly as they accept `bin`.
189+
190+
`mcpp run` of an `app` target, on a row where its form is a shared library,
191+
refuses without `--format`, naming the flag and the formats the resolved
192+
graph provides. `mcpp pack --format apk` stages the library where the
193+
closure already places a shared object. See `mcpp run --format` in
194+
[10 — Pack and Release](10-pack-and-release.md).
195+
196+
An engine older than 2026.9.12.3 does not know the value and refuses it,
197+
naming the three kinds it does know:
198+
199+
```
200+
targets.myapp.kind must be 'bin', 'lib' or 'shared'; got 'app'
201+
```
202+
159203
#### `exports` — the artifact’s published symbol set (mcpp 2026.9.6.5+)
160204

161205
```toml
@@ -1116,8 +1160,23 @@ provider = "acme.widget-runtime@2.0.0"
11161160
An unsupported key in this table is **reported and ignored**, and the message
11171161
lists the keys it checked against. A `[runtime.<capability>]` sub-table is a
11181162
provider override rather than a key, so it is not swept. The same rule applies
1119-
to `[target.<predicate>.runtime]`, whose vocabulary is `libraries` and
1120-
`link_library_dirs` only ([22 — The Target Side](22-target-side.md)).
1163+
to `[target.<predicate>.runtime]`, whose vocabulary is `libraries`,
1164+
`link_library_dirs` and `frameworks` (mcpp 2026.9.12.3+)
1165+
([22 — The Target Side](22-target-side.md)). `frameworks` on that table is
1166+
appended after the top-level list and renders `-framework <name>` on Mach-O
1167+
rows only, nothing on the others — the key a manifest reaches for when a
1168+
framework exists on iOS and not on macOS, or the reverse:
1169+
1170+
```toml
1171+
[runtime]
1172+
frameworks = ["Foundation", "CoreGraphics"]
1173+
1174+
[target.macos.runtime]
1175+
frameworks = ["AppKit"]
1176+
1177+
[target.'cfg(os = "ios")'.runtime]
1178+
frameworks = ["UIKit"]
1179+
```
11211180

11221181
`requirements` records a non-empty `kind`/`value`, a `link` or `run` phase,
11231182
and whether the requirement is mandatory (`required` defaults to `true`).
@@ -1215,13 +1274,22 @@ participates in toolchain ABI enforcement).
12151274

12161275
```toml
12171276
[package]
1218-
platforms = ["linux", "macos", "windows"]
1277+
platforms = ["linux", "macos", "windows", "ios", "android", "emscripten"]
12191278
```
12201279

12211280
Declares the platforms the package supports (a CI matrix hint, shown via `mcpp why`).
12221281
The vocabulary is fixed by mcpp (which owns the target/triple system):
1223-
`linux | macos | windows`; unknown values produce a warning, and an error under
1224-
`--strict`.
1282+
`linux | macos | windows | ios | android | emscripten` (mcpp 2026.9.12.3+;
1283+
`ios`, `android` and `emscripten` are new members of a vocabulary that was
1284+
`linux | macos | windows` before); unknown values produce a warning, and an
1285+
error under `--strict`.
1286+
1287+
A platform name is a target triple's `os`, except that an `env` naming a
1288+
platform of its own wins. Android rows have `os = "linux"` and
1289+
`env = "android"`, so `linux` in this list does not cover them — a package
1290+
that serves Android states `android` as well. The Web row keeps its `os`
1291+
word, `emscripten`, the same word the `cfg(...)` selector grammar uses; there
1292+
is no `web` spelling.
12251293

12261294
`mcpp pack` on a library target checks the claim against the legs it actually
12271295
produced, because that is the first moment there is evidence to check it against:

0 commit comments

Comments
 (0)