Skip to content

Commit a1248b0

Browse files
committed
feat(examples, docs): the curriculum covers the new targets, and a warning stops advising what is now refused
EXAMPLE. `examples/13-platform-targets` is one source with no `cfg`, built for Linux, WebAssembly and both Android ABIs by changing only `--target`. The curriculum had twelve examples and none for any of the three platforms this release adds, which is a gap in the one place a reader looks first. Its host build needs no payload, so it is in BUILD rather than SKIP -- and that is itself worth one build: a manifest naming targets the runner has no payload for must still parse and build for the host. The cross legs are not built there; ci-target-matrix scans every row on four hosts and tests/e2e/641 asserts the vocabulary. Every claim in its README is measured against the artifact the README describes, not carried over from another one: the host and wasm runs print `1-2-3`, both Android artifacts are the file(1) output shown, and the x86_64 artifact was pushed to an API 24 emulator image and executed there. A MISLEADING WARNING, FOUND BY BUILDING openkal FOR ANDROID. When a project's target side comes from the dependency graph, mcpp warns that the row's convention pin was unnecessary and tells the reader to declare their own default instead. On a capability row both halves of that are false: warning: ... so gcc@16.1.0 would have served x86_64-linux-android. State the preference: [target.x86_64-linux-android] toolchain = "gcc@16.1.0" $ (declaring exactly that) error: target 'x86_64-linux-android' cannot be emitted by 'gcc@16.1.0'. Following the advice replaced a warning with an error. The first claim is false on its own terms too: this gcc payload cannot emit an Android object whatever the graph supplies. `graph` answers "who supplies the SYSTEM"; a capability pin answers "who can emit the FORMAT AND THE SYSTEM", and only the second decides whether a substitution was avoidable. The warning is now gated on `pin_is_capability()`, whose row set is asserted exhaustively in test_toolchain_triple, so the gate cannot silently cover every row. DOCS: docs/24-openkal-cross.md (+zh) now answers what the three new platforms mean for openkal, because the answers differ and the difference is where an implementation sits relative to a C library. Android shares openkal-linux UNCHANGED. It is written on the Linux kernel's own system calls and borrows nothing from a C library; Android's kernel IS Linux, the per-arch syscall ABI is identical, and sys.h dispatches on __x86_64__/__aarch64__ -- architecture, not OS. `cfg(os = "linux")` is true for an Android triple, so a portable program selects it with the line a Linux consumer already writes. Measured: openkal-linux compiles for both ABIs, and a program written against openkal alone -- no C library, no `import std` -- printed `openkal: 1-2-3` with exit 0 on an API 24 emulator image. iOS would share openkal-macos by the same argument, and the argument is not evidence: the SDKs are not redistributable, so there is nothing to build against and the rows claim nothing. Web needs a NEW implementation and a different shape. Emscripten has no kernel and no system calls; an implementation would have to sit ABOVE its C library, which the specification permits and which is new software rather than a sharing decision. Until then the row is served the ordinary way, by the `xim:emsdk` payload, which is what its `verified` tier records. The docs/21 support table and the two Android rows' Windows cells were also reconciled with the index: `—` there means no payload is published for that host, while `toolchain list` still shows the rows, because which hosts an index serves is the index's answer and not a constant compiled into the engine.
1 parent f6519dc commit a1248b0

9 files changed

Lines changed: 368 additions & 1 deletion

File tree

.github/tools/build_examples.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ BUILD=(
5757
# three-file package.
5858
examples/12-a-new-device-language/toyc
5959
examples/12-a-new-device-language/app
60+
# One source, three platforms. BUILT here rather than skipped, because its
61+
# HOST build needs no payload at all -- the `[target.*-linux-android]`
62+
# sections are inert unless that target is selected, which is itself worth
63+
# one build: a manifest that names a target the runner has no payload for
64+
# must still parse and build for the host.
65+
#
66+
# The cross legs are not built here. `wasm32-emscripten` and the two
67+
# Android rows would pull `xim:emsdk` and `xim:android-ndk` -- about 1.5 GB
68+
# between them -- and the signal already exists elsewhere:
69+
# ci-target-matrix scans every row on four hosts, tests/e2e/641 asserts the
70+
# vocabulary, and the example's README records the measured artifacts and
71+
# the emulator run for the binary it describes.
72+
examples/13-platform-targets
6073
)
6174

6275
# `key|reason`.

docs/03-examples.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ the map; the table below is what each sub-example adds.
8282
|---|---|
8383
| [`08-build-rules`](../examples/08-build-rules/) | two rule packages and a project using both; `host-module = true`, `mcpp::action` with `role = "check"` |
8484
| [`12-a-new-device-language`](../examples/12-a-new-device-language/) | `device_extensions` and `rule_module`: a rule package teaching mcpp a language the engine has never heard of, whose compiler is a package built through `tools = [...]` for the build machine |
85+
| [`13-platform-targets`](../examples/13-platform-targets/) | one source and no `cfg`, built for Linux, WebAssembly and both Android ABIs by changing only `--target`; `min_api_level` as the project's own decision, and a capability pin that cannot be overridden |
8586

8687
[31 — Authoring a Rule Package](31-authoring-a-rule-package.md) is the reference
8788
these two illustrate.

docs/24-openkal-cross.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,91 @@ the object ABI on Windows, the object format where there is no operating system
178178
— and one value records which, rather than a boolean recording only whether the
179179
first case holds.
180180

181+
## Android, Web And iOS Under This Model
182+
183+
The three platforms mcpp added target rows for in 2026.9.11.3 are not one
184+
question. What decides each is where its implementation would have to sit
185+
relative to a C library, and the answers are different.
186+
187+
### Android shares the Linux implementation, unchanged
188+
189+
`openkal-linux` is written on the Linux kernel's own system-call interface and
190+
borrows nothing from any C library — that is what lets it be placed beneath one.
191+
Android's kernel **is** Linux, the system-call ABI for a given architecture is
192+
the same, and `src/sys.h` dispatches on `__x86_64__` / `__aarch64__`, which is
193+
the architecture rather than the operating system. Nothing in it is glibc's or
194+
bionic's.
195+
196+
So a portable program needs no new line. `cfg(os = "linux")` is **true for an
197+
Android triple**, because Android is an `env` value on a `linux` OS — the
198+
modelling decision [21 — The Target Triple](21-the-target-triple.md) records —
199+
and the implementation is selected by the line a Linux consumer already writes:
200+
201+
```toml
202+
[target.'cfg(os = "linux")'.dependencies]
203+
openkal-linux = "0.12.0"
204+
```
205+
206+
Measured 2026-09-11, a program written against openkal and nothing else — no C
207+
library, no `import std`:
208+
209+
```
210+
mcpp build --target x86_64-linux-android
211+
kernel-abi openkal (openkal-linux@0.12.0, graph)
212+
-> ELF 64-bit LSB pie, x86-64, interpreter /system/bin/linker64
213+
214+
mcpp build --target aarch64-linux-android
215+
-> ELF 64-bit LSB pie, ARM aarch64, same interpreter
216+
```
217+
218+
and the x86_64 artifact, pushed to an API 24 emulator image and executed:
219+
220+
```
221+
openkal: 1-2-3 exit 0
222+
```
223+
224+
`openkal-linux` itself also compiles for both Android targets unchanged, which
225+
is the weaker claim of the two and is worth stating separately: the first says
226+
the implementation builds, the second says a program over it runs.
227+
228+
### iOS would share the macOS implementation, and that cannot be claimed yet
229+
230+
The same argument applies on Apple's side — iOS and macOS share the Darwin
231+
kernel, and `openkal-macos` is arch-dispatched the same way — but the argument
232+
is not evidence. The iPhoneOS and iPhoneSimulator SDKs ship inside Xcode and
233+
are not redistributable, so the `aarch64-ios` and `*-ios-sim` rows are
234+
`planned`: there is nothing to build against and therefore nothing to run.
235+
Declaring support on a structural argument alone is the shape this ecosystem has
236+
paid for before — a package present in an index is not a package that builds a
237+
real project — so these rows claim nothing until an SDK is reachable.
238+
239+
### Web needs a new implementation, and a different one
240+
241+
Emscripten is the one of the three that changes the model rather than extending
242+
it. There is no kernel and there are no system calls to issue: Emscripten
243+
supplies its own C library over a JavaScript host. An openkal implementation for
244+
it therefore cannot be written the way `openkal-linux` is — beneath a C library
245+
— and would have to sit **above** one. The specification permits exactly that
246+
("an implementation may be built upon a C library, beneath one, or without
247+
one"), so this is new software rather than a sharing decision, and it is the one
248+
of the three that is neither done nor blocked.
249+
250+
Until it exists, `wasm32-emscripten` is served the ordinary way: by a payload.
251+
`xim:emsdk` ships the compiler, the sysroot and a libc++ module surface, so a
252+
program that uses `import std` builds and runs for the Web today without openkal
253+
being involved at all — which is what the row's `verified` tier records.
254+
255+
### The table
256+
257+
| platform | implementation | status |
258+
|---|---|---|
259+
| Linux (glibc, musl) | `openkal-linux` | the reference implementation |
260+
| Android (both ABIs) | `openkal-linux`, unchanged | builds; a program over it ran on an emulator |
261+
| macOS | `openkal-macos` | on the macOS system-call surface |
262+
| iOS, iOS simulator | `openkal-macos` would serve it | blocked: the SDK is not redistributable |
263+
| Windows | `openkal-windows` | on Win32 and the object manager |
264+
| Web (Emscripten) | none | needs an implementation written ABOVE a C library |
265+
181266
## Bare Metal
182267

183268
A target with no operating system is the same model with the platform layer

docs/zh/03-examples.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ mcpp build && mcpp run
7777
|---|---|
7878
| [`08-build-rules`](../../examples/08-build-rules/) | 两个规则包与同时使用它们的工程;`host-module = true``role = "check"``mcpp::action` |
7979
| [`12-a-new-device-language`](../../examples/12-a-new-device-language/) | `device_extensions``rule_module`:规则包教会 mcpp 一门引擎从未听说过的语言,而它的编译器是一个经 `tools = [...]` 为构建机构建出来的包 |
80+
| [`13-platform-targets`](../../examples/13-platform-targets/) | 一份源码、零个 `cfg`,只改 `--target` 就为 Linux、WebAssembly 和两个 Android ABI 构建;`min_api_level` 作为工程自己的决定,以及一条不能被覆盖的能力钉 |
8081

8182
[31 —— 编写规则包](31-authoring-a-rule-package.md) 是这两个示例所演示内容的参考。
8283

docs/zh/24-openkal-cross.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,81 @@ Target x86_64-windows-gnu → x86_64-w64-windows-gnu (gnu selects the Itanium
154154
没有操作系统时是对象格式 —— 因此记录的是**它是哪一个**,
155155
而不是一个只记录「是否为第一种」的布尔。
156156

157+
## 这个模型下的 Android、Web 与 iOS
158+
159+
mcpp 在 2026.9.11.3 里加出目标行的这三个平台不是同一个问题。决定每一个的是它的
160+
实现相对一个 C 库该落在哪一侧,而三个答案各不相同。
161+
162+
### Android 共用 Linux 的实现,一行都不用改
163+
164+
`openkal-linux` 写在 Linux 内核自己的系统调用接口上,不向任何 C 库借用任何东西 ——
165+
这正是它能被放到一个 C 库**底下**的原因。Android 的内核**就是** Linux,给定架构上
166+
的系统调用 ABI 完全相同,而 `src/sys.h``__x86_64__` / `__aarch64__` 分支,也就是
167+
**架构**而不是按操作系统。它里面没有任何属于 glibc 或 bionic 的东西。
168+
169+
所以一个可移植程序不需要新增任何一行。`cfg(os = "linux")` 对一个 Android triple
170+
**为真**,因为 Android 是 `linux` OS 上的一个 `env` 值 ——
171+
[21 — 目标三元组](21-the-target-triple.md) 记着这处建模决定 —— 于是实现由一个
172+
Linux 消费者本来就会写的那一行选出:
173+
174+
```toml
175+
[target.'cfg(os = "linux")'.dependencies]
176+
openkal-linux = "0.12.0"
177+
```
178+
179+
实测 2026-09-11,一个只针对 openkal 写的程序 —— 没有 C 库,也没有 `import std`:
180+
181+
```
182+
mcpp build --target x86_64-linux-android
183+
kernel-abi openkal (openkal-linux@0.12.0, graph)
184+
-> ELF 64-bit LSB pie, x86-64, interpreter /system/bin/linker64
185+
186+
mcpp build --target aarch64-linux-android
187+
-> ELF 64-bit LSB pie, ARM aarch64, 同一个 interpreter
188+
```
189+
190+
而那个 x86_64 产物被推到一台 API 24 的模拟器镜像上执行:
191+
192+
```
193+
openkal: 1-2-3 exit 0
194+
```
195+
196+
`openkal-linux` 自己也能为两个 Android 目标原样编译,这是两条里较弱的那一条,值得
197+
分开陈述:前者说的是**实现**构建得起来,后者说的是**它上面的程序**跑得起来。
198+
199+
### iOS 会共用 macOS 的实现,而这一条现在还不能声称
200+
201+
同样的论证在 Apple 这一侧成立 —— iOS 与 macOS 共用 Darwin 内核,而 `openkal-macos`
202+
是按同样方式按架构分支的 —— 但**论证不是证据**。iPhoneOS 与 iPhoneSimulator 的 SDK
203+
在 Xcode 里且不可再分发,所以 `aarch64-ios``*-ios-sim` 三行是 `planned`:没有
204+
东西可以拿来构建,因此也没有东西可以拿来运行。仅凭一个结构性论证就声明支持,是这个
205+
生态已经付过代价的那种形状 —— 一个在索引里的包不等于一个能构建真实工程的包 ——
206+
所以在 SDK 可达之前,这三行什么都不声称。
207+
208+
### Web 需要一份新的实现,而且是另一种形状
209+
210+
Emscripten 是三者里**改变模型**而不是扩展表格的那一个。那里没有内核,也没有系统
211+
调用可发:Emscripten 在一个 JavaScript 宿主之上供给它自己的 C 库。所以给它写的
212+
openkal 实现不可能按 `openkal-linux` 的方式写 —— 落在一个 C 库底下 —— 而必须落在
213+
一个 C 库**之上**。规范恰好允许这一点(「一个实现可以建立在一个 C 库之上、之下,
214+
或者不依赖 C 库」),所以这是**新软件**而不是一个共用决定,而它是三者里既没做完也
215+
没被阻塞的那一个。
216+
217+
在它出现之前,`wasm32-emscripten` 走的是普通那条路:一个载荷。`xim:emsdk` 自带
218+
编译器、sysroot 和一份 libc++ 的模块面,所以一个用 `import std` 的程序今天就能为
219+
Web 构建并运行,而 openkal 完全不参与 —— 这正是那一行的 `verified` 层级所记录的。
220+
221+
###
222+
223+
| 平台 | 实现 | 状态 |
224+
|---|---|---|
225+
| Linux(glibc、musl) | `openkal-linux` | 参考实现 |
226+
| Android(两个 ABI) | `openkal-linux`,原样 | 构建通过;它上面的程序在模拟器上跑过 |
227+
| macOS | `openkal-macos` | 在 macOS 的系统调用面上 |
228+
| iOS、iOS 模拟器 | `openkal-macos` 会服务它 | 阻塞:SDK 不可再分发 |
229+
| Windows | `openkal-windows` | 在 Win32 与对象管理器上 |
230+
| Web(Emscripten) || 需要一份写在 C 库**之上**的实现 |
231+
157232
## 裸机
158233

159234
一个没有操作系统的目标,是同一个模型,只是平台层由固件而非内核供给。
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# 13 — platform targets
2+
3+
一份源码,三个平台。这里没有任何平台感知的东西:没有 `cfg`、没有预处理分支、
4+
没有按目标分开的源文件。在 Linux 二进制、WebAssembly 模块和 Android 产物之间
5+
变的只有命令行上的 `--target`
6+
7+
```bash
8+
cd 13-platform-targets
9+
mcpp build && mcpp run # 宿主
10+
```
11+
12+
## Web
13+
14+
```bash
15+
mcpp run --target wasm32-emscripten
16+
```
17+
18+
实测(linux-x86_64,`xim:emsdk` 6.0.9):
19+
20+
```
21+
bin/platform-targets 65389 bytes the JavaScript
22+
bin/platform-targets.wasm 447183 bytes the module
23+
node bin/platform-targets -> 1-2-3
24+
```
25+
26+
`mcpp run` 会用 `node` 跑它,所以不需要额外的一步。工程侧**一个新词汇都不需要**
27+
`wasm32-emscripten` 这一行自己命名了它的载荷(`emsdk@6.0.9`),载荷自带 sysroot,
28+
而 Emscripten 自己就发布一份 libc++ 的模块面。
29+
30+
## Android
31+
32+
```bash
33+
mcpp build --target x86_64-linux-android # 模拟器
34+
mcpp build --target aarch64-linux-android # 真机
35+
```
36+
37+
实测(同一台机器,`xim:android-ndk` 30.0.16248370):
38+
39+
```
40+
aarch64-linux-android -> ELF 64-bit LSB pie, ARM aarch64,
41+
interpreter /system/bin/linker64
42+
x86_64-linux-android -> ELF 64-bit LSB pie, x86-64, 同一个 interpreter
43+
```
44+
45+
**一个钉服务两行。** NDK 不命名架构,`--target` 才命名 —— 所以
46+
`[target.<triple>] toolchain` 不需要写,而两行共用
47+
`android-ndk@30.0.16248370`
48+
49+
跑起来(x86_64 键在平台自己的模拟器上,API 24 镜像 + KVM):
50+
51+
```bash
52+
adb push target/x86_64-linux-android/*/bin/platform-targets /data/local/tmp/
53+
adb shell /data/local/tmp/platform-targets
54+
# -> 1-2-3
55+
```
56+
57+
加载时会有一句告警,它**不是**缺陷:`unsupported flags DT_FLAGS_1=0x8000001`
58+
API 24 的 bionic 加载器不认识 lld 设置的 `DF_1_PIE` 位,于是告警一句,然后照常
59+
把程序加载起来。
60+
61+
### API level 在 `mcpp.toml` 里,不在 triple 里
62+
63+
`mcpp.toml` 声明的是:
64+
65+
```toml
66+
[target.aarch64-linux-android]
67+
min_api_level = 24
68+
```
69+
70+
规范 triple 保持 `aarch64-linux-android` —— 它命名输出目录、`cfg(env = ...)`
71+
和 ABI tag。级别只进**编译器看到的** triple(`aarch64-unknown-linux-android24`
72+
**构建指纹**:级别决定哪些 bionic 符号可见,所以两个级别是两个 ABI,绝不可共用
73+
一个构建目录。
74+
75+
这个键是**可选的**。不写的话,mcpp 读 NDK 自己在 `meta/platforms.json` 里声明的
76+
下限(r30 是 21)。写在这里是因为一个要发布到某个最低版本的工程应该自己说出来,
77+
而不是继承载荷的下限恰好是多少。
78+
79+
`[package] macos_deployment_target` 是 Apple 目标上的同一根轴;一个目标要么是
80+
Apple 要么是 Android,所以两者在指纹里共用一个槽。
81+
82+
## 这一行不能被覆盖
83+
84+
Android 和 wasm 的钉是**能力**而不是约定:
85+
86+
```bash
87+
mcpp build --target aarch64-linux-android # [target.…] toolchain = "llvm@22.1.8"
88+
# error: target 'aarch64-linux-android' cannot be emitted by 'llvm@22.1.8'.
89+
# An Android target needs bionic, not just an aarch64 or x86_64 back end:
90+
# its headers, its per-API-level stubs and its loader path are inside the
91+
# NDK, and no package adds them to another compiler.
92+
```
93+
94+
一个普通 clang 发 aarch64 ELF 完全没问题 —— 它拿不出来的是**体系**。说出来比
95+
解析出 llvm 再在它内部失败要好。
96+
97+
## iOS
98+
99+
`aarch64-ios``aarch64-ios-sim``x86_64-ios-sim` 三行在词汇里,都是 `planned`
100+
101+
```bash
102+
mcpp build --target aarch64-ios-sim
103+
# error: target 'aarch64-ios-sim' is registered but not yet supported (planned)
104+
# — no toolchain is published for it yet.
105+
```
106+
107+
阻塞项是**许可**而不是载荷:NDK 是 Apache-2.0、Emscripten 是 MIT,而 iPhoneOS 与
108+
iPhoneSimulator 的 SDK 在 Xcode 里,两者都不可再分发。这三行今天买到的是一句
109+
点名那一行的 `tier-planned`,而不是一句假的 `unknown target`
110+
111+
模拟器是**一个目标**而不是一个 runner:它有自己的 SDK、产出自己的对象,取
112+
`-mios-simulator-version-min` 而真机取 `-miphoneos-version-min`。所以它有自己的
113+
行,而不是折进设备那一行。
114+
115+
## 支持矩阵
116+
117+
`docs/21-the-target-triple.md` 的表是完整的那一份;这里只列这个例子碰到的行:
118+
119+
| target | tier | pin | 运行过? |
120+
|---|---|---|---|
121+
| `wasm32-emscripten` | verified | `emsdk@6.0.9` | 是,`node` |
122+
| `x86_64-linux-android` | verified | `android-ndk@30.0.16248370` | 是,平台模拟器 |
123+
| `aarch64-linux-android` | preview | `android-ndk@30.0.16248370` | 否 —— 从 x86_64 宿主没有执行路径 |
124+
| `aarch64-ios` / `*-ios-sim` | planned |||
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "platform-targets"
3+
version = "0.1.0"
4+
5+
# ANDROID'S API LEVEL IS A PROJECT DECISION, NOT A TOOLCHAIN PROPERTY, which
6+
# is why it lives here and not in the triple. One NDK serves a range of levels,
7+
# and the level selects which bionic symbols exist -- so it is an ABI axis and
8+
# it enters the build fingerprint: two levels are two build directories.
9+
#
10+
# It is OPTIONAL. Left out, mcpp reads the floor the NDK itself declares in
11+
# `meta/platforms.json` (21 for r30). It is stated here because a project that
12+
# ships to a minimum should say so rather than inherit whatever the payload's
13+
# floor happens to be.
14+
#
15+
# `macos_deployment_target` in `[package]` is the same axis for Apple targets;
16+
# a target is one or the other, so they share one slot in the fingerprint.
17+
[target.aarch64-linux-android]
18+
min_api_level = 24
19+
20+
[target.x86_64-linux-android]
21+
min_api_level = 24
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// One source, three platforms. Nothing here is platform-aware: no `cfg`, no
2+
// preprocessor branch, no per-target source. The only thing that changes
3+
// between a Linux binary, a WebAssembly module and an Android artifact is the
4+
// `--target` on the command line.
5+
//
6+
// `import std` is the point. A target whose toolchain cannot compile a module
7+
// interface unit would be worse than its absence in a module-first build tool,
8+
// so this deliberately exercises the standard library module rather than a
9+
// header, on every target the README lists.
10+
import std;
11+
12+
int main() {
13+
std::vector<int> v{3, 1, 2};
14+
std::ranges::sort(v);
15+
std::print("{}-{}-{}\n", v[0], v[1], v[2]);
16+
}

0 commit comments

Comments
 (0)