Skip to content

Commit f6519dc

Browse files
committed
feat(targets): x86_64-linux-android is verified — the artifact ran on the platform's emulator
`adb push` then `adb shell ./andtest` printed `1-2-3`, exit 0, on an API 24 x86_64 system image under Google's own emulator with KVM, from a source that imports std and no project vocabulary beyond `--target`. So one pin serves two rows at two tiers, and the difference is execution rather than confidence in the build. The device row stays `preview` and the reason is recorded where the next attempt will look: the emulator refuses a foreign guest outright, and the documented qemu-user fallback needs four files extracted from an ext4 partition image by `debugfs` -- the one program in `xim:e2fsprogs@1.47.3` that is a broken build. That is an ecosystem defect with its own record in the index, and fixing it moves this row without an engine change. The docs now also reconcile the two records that would otherwise disagree: the support table's Windows cell for both Android rows is `—` because the index publishes no Windows NDK (its archive ships no libc++ module surface), while `mcpp toolchain list` still shows the rows there, because which hosts an index serves is the index's answer and not a constant compiled into the engine.
1 parent 09fc2ad commit f6519dc

6 files changed

Lines changed: 122 additions & 12 deletions

File tree

.agents/docs/2026-09-11-platform-targets-design-review.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,28 @@ plan.
994994
| E15 | R2: `min_api_level` under `[target.<triple>]`, via `llvm_triple(param)` and the fingerprint, per §12.1 | **done** |
995995
| E16 | `host_can_serve` stops hardcoding Linux for `has_own_sysroot()` rows | **done** -- see below; this is the line the predicate's own comment named as its expiry |
996996

997+
**E16 leaves one thing unanswered, and it is recorded rather than hidden.**
998+
With the host constant gone, `mcpp toolchain list` reports
999+
`aarch64-linux-android` as `available` on every host -- including Windows,
1000+
where `xim:android-ndk` deliberately has no table. Google publishes a Windows
1001+
NDK and it downloads; what it does not contain is the libc++ module surface
1002+
(measured: 9108 entries, no `std.cppm`, no `std/*.inc`, against darwin's 10024
1003+
and 110), so for a module-first build tool that payload cannot serve and an
1004+
entry that can never serve is worse than none.
1005+
1006+
The engine is not the place to encode that. Which hosts an index serves is the
1007+
index's answer, it changes without an engine release, and a constant stating it
1008+
here is exactly what E16 removed -- it would go stale again the first time a
1009+
future NDK ships the surface. So a Windows user sees the row, the pin resolves,
1010+
and xim refuses with `no payload for this platform` before anything is fetched,
1011+
naming the package. That is legible at the point of use, which is the standard
1012+
this repository already applies to a per-package engine floor.
1013+
1014+
What would change the answer is a cheap way to ask the index for a payload's
1015+
platform coverage during a listing. There is none today that does not cost a
1016+
network round trip per row, and a fourth status word would describe the gap
1017+
rather than close it.
1018+
9971019
**E16 was not in the original plan, and the target matrix is what produced
9981020
it.** The predicate returned `mcpp::platform::is_linux` for a row whose SDK
9991021
ships its own sysroot, because `xim:emsdk` and `xim:android-ndk` declared only

CHANGELOG.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,28 @@ x86_64-linux-android -> ELF 64-bit LSB pie, x86-64, 同一个 interpreter
4949
性质所系:NDK 不命名架构,`--target` 才命名 —— 于是每一处「谁说出目标」的缺口都
5050
会在这里现形,而在 wasm 上都不会,因为 `em++` 只有一个目标。
5151

52-
`preview` 而不是 `verified`:两者都**构建**过,都没有被**执行**过 —— 跑起来需要一台
53-
设备或一个模拟器,而那正是这两个层级的差别。
52+
**一个钉,两个层级**,而层级的差别是**执行**,不是对构建的信心。
53+
54+
`x86_64-linux-android``verified`:产物在平台自己的模拟器上跑起来了。
55+
2026-09-11,linux-x86_64,API 24 的 x86_64 系统镜像 + KVM:
56+
57+
```
58+
adb push <mcpp 构建出的产物> /data/local/tmp/
59+
adb shell ./andtest -> 1-2-3 exit 0
60+
```
61+
62+
`aarch64-linux-android``preview`:构建方式完全相同,而从一台 x86_64 宿主没有
63+
执行路径。记下来是为了下一次尝试不重复:Google 的模拟器直接拒绝异构 guest ——
64+
`QEMU2 emulator does not support arm64 CPU architecture` —— 所以 arm64 镜像需要一台
65+
arm64 宿主。文档给出的退路是 qemu-user 配系统镜像自带的 bionic,而准备它要用
66+
`debugfs` 从一个 ext4 分区镜像里取四个文件,而 `debugfs` 恰好是
67+
`xim:e2fsprogs@1.47.3` 里唯一一个构建坏了的程序(任何打开文件系统的命令都 SIGFPE,
68+
而同一份构建里的 dumpe2fs/e2fsck/tune2fs 都正常)。那是一个生态缺陷,在索引侧有
69+
自己的记录,不是引擎的缺口 —— 它被修好时这一行就变成 `verified`,而这里什么都不用动。
70+
71+
还有一条链接器告警值得记下来,因为用户会看到它而它**不是**缺陷:
72+
`unsupported flags DT_FLAGS_1=0x8000001`。API 24 的 bionic 加载器不认识 lld 设置的
73+
`DF_1_PIE` 位,于是告警一句,然后照常把程序加载起来。
5474

5575
五处引擎缺口,每一处都是前一处的失败找出来的,而每一处都**只在多目标载荷上现形**:
5676

docs/21-the-target-triple.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,8 @@ other's rows.
483483
| `thumbv8m.base-none-eabi` | preview | `llvm@22.1.8` | payload | payload | payload | payload |
484484
| `thumbv8m.main-none-eabi` | verified | `llvm@22.1.8` | payload | payload | payload | payload |
485485
| `thumbv8m.main-none-eabihf` | preview | `llvm@22.1.8` | payload | payload | payload | payload |
486-
| `aarch64-linux-android` | preview | `android-ndk@30.0.16248370` | payload | payload | payload | payload |
487-
| `x86_64-linux-android` | preview | `android-ndk@30.0.16248370` | payload | payload | payload | payload |
486+
| `aarch64-linux-android` | preview | `android-ndk@30.0.16248370` | payload | payload | payload | |
487+
| `x86_64-linux-android` | verified | `android-ndk@30.0.16248370` | payload | payload | payload | |
488488
| `aarch64-ios` | planned || planned | planned | planned | planned |
489489
| `aarch64-ios-sim` | planned || planned | planned | planned | planned |
490490
| `x86_64-ios-sim` | planned || planned | planned | planned | planned |
@@ -513,6 +513,24 @@ installed by mcpp · `SDK` the platform's own · `—` unreachable from this hos
513513
instead — which is why `x86_64-windows-musl` reads `via dependency graph` on
514514
Linux and produces a real PE32+ there.
515515

516+
**And for the two Android rows the `` on Windows is the INDEX's answer, so
517+
`mcpp toolchain list` still shows them there.** Google publishes a Windows NDK
518+
and it downloads; what it does not contain is the libc++ module surface
519+
(measured: no `std.cppm` and no `std/*.inc`, against 110 on the other two
520+
hosts), so `xim:android-ndk` declares no Windows table — an entry that can
521+
never serve a module-first build is worse than none. The engine does not encode
522+
that: which hosts an index serves changes without an engine release, and a
523+
constant stating it here is what the wasm row's own history shows going stale.
524+
A Windows user therefore sees the row, the pin resolves, and xim refuses with
525+
`no payload for this platform` before anything is fetched, naming the package.
526+
527+
**The two Android rows differ in tier because one of them was run.** An
528+
x86_64 Android artefact executes on the platform's own emulator, and a
529+
`verified` row means exactly that was done. The device row builds identically
530+
and has no execution path from an x86_64 host: the emulator refuses a foreign
531+
guest (`QEMU2 emulator does not support arm64 CPU architecture`), so it needs
532+
an arm64 host or the qemu-user route.
533+
516534
### And CI measures every one of them
517535

518536
[`ci-target-matrix.yml`](../.github/workflows/ci-target-matrix.yml) runs on all

docs/zh/21-the-target-triple.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,8 @@ CRT;图供给时是 `musl`。一个目标字符串,两个不同的 C 库 ——
433433
| `thumbv8m.base-none-eabi` | preview | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 |
434434
| `thumbv8m.main-none-eabi` | verified | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 |
435435
| `thumbv8m.main-none-eabihf` | preview | `llvm@22.1.8` | 载荷 | 载荷 | 载荷 | 载荷 |
436-
| `aarch64-linux-android` | preview | `android-ndk@30.0.16248370` | payload | payload | payload | payload |
437-
| `x86_64-linux-android` | preview | `android-ndk@30.0.16248370` | payload | payload | payload | payload |
436+
| `aarch64-linux-android` | preview | `android-ndk@30.0.16248370` | payload | payload | payload | |
437+
| `x86_64-linux-android` | verified | `android-ndk@30.0.16248370` | payload | payload | payload | |
438438
| `aarch64-ios` | planned || planned | planned | planned | planned |
439439
| `aarch64-ios-sim` | planned || planned | planned | planned | planned |
440440
| `x86_64-ios-sim` | planned || planned | planned | planned | planned |
@@ -461,6 +461,21 @@ CRT;图供给时是 `musl`。一个目标字符串,两个不同的 C 库 ——
461461
载荷产出它」,而依赖图可以改为供给系统 —— 这就是 `x86_64-windows-musl` 在 Linux
462462
上显示 `via dependency graph`、并在那里产出真正的 PE32+ 的原因。
463463

464+
**而两个 Android 行在 Windows 上的 ``**索引**的答案,所以
465+
`mcpp toolchain list` 在那里仍然会显示它们。** Google 确实发布 Windows NDK,它也
466+
下载得到;它不包含的是 libc++ 的**模块面**(实测:没有 `std.cppm`、没有
467+
`std/*.inc`,而另两个宿主各有 110 个),所以 `xim:android-ndk` 不声明 windows 表 ——
468+
一条永远不能服务「模块优先」构建的条目比没有更坏。引擎不把这件事编进来:一个索引
469+
服务哪些宿主会在没有引擎发布的情况下变化,而把它写成这里的一个常量,正是 wasm 那
470+
一行自己的历史所展示的会变陈旧的东西。因此 Windows 用户会看到这一行、钉能解析,
471+
而 xim 在任何东西被下载之前以 `no payload for this platform` 拒绝,并点名那个包。
472+
473+
**两个 Android 行层级不同,是因为其中一个被运行过。** 一个 x86_64 的 Android 产物
474+
在平台自己的模拟器上执行得起来,而 `verified` 这个层级断言的正是「做过这件事」。
475+
真机那一行构建方式完全相同,而从一台 x86_64 宿主没有执行路径:模拟器直接拒绝异构
476+
guest(`QEMU2 emulator does not support arm64 CPU architecture`),所以它需要一台
477+
arm64 宿主,或者 qemu-user 那条路。
478+
464479
### 而 CI 把每一台都测了
465480

466481
[`ci-target-matrix.yml`](../../.github/workflows/ci-target-matrix.yml) 在全部四台

modules/toolchain-model/src/triple.cppm

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,38 @@ inline constexpr TargetInfo kKnownTargets[] = {
653653
// cost, and until it lands `[target.<triple>].sysroot` is the escape hatch
654654
// for a machine that has an NDK already.
655655
{ "aarch64-linux-android", "preview", "", "android-ndk@30.0.16248370", "", false },
656-
// The emulator's row. Not a convenience: x86_64 is what an Android
657-
// emulator image runs, so a row for the device without one for the
658-
// emulator describes a target nothing in CI can execute.
659-
{ "x86_64-linux-android", "preview", "", "android-ndk@30.0.16248370", "", false },
656+
// The emulator's row, and the one of the pair that could be EXECUTED.
657+
//
658+
// Not a convenience: x86_64 is what an Android emulator image runs, so a
659+
// row for the device without one for the emulator describes a target
660+
// nothing can execute. That argument is now measured rather than asserted.
661+
// 2026-09-11, linux-x86_64, an API 24 x86_64 system image under the
662+
// platform's own emulator with KVM:
663+
//
664+
// adb push <the mcpp-built artifact> /data/local/tmp/
665+
// adb shell ./andtest -> 1-2-3 exit 0
666+
//
667+
// from `import std;` and no project vocabulary beyond `--target`. So this
668+
// row is `verified` while `aarch64-linux-android` is `preview`, and the
669+
// difference is execution rather than confidence in the build.
670+
//
671+
// WHY THE DEVICE ROW COULD NOT FOLLOW, recorded so the next attempt does
672+
// not repeat it. Google's emulator refuses a foreign guest outright --
673+
// "QEMU2 emulator does not support arm64 CPU architecture" -- so the arm64
674+
// image needs an arm64 host. The documented fallback is qemu-user with the
675+
// system image's own bionic, and preparing it needs four files extracted
676+
// from an ext4 partition image by `debugfs`, which is the one program in
677+
// `xim:e2fsprogs@1.47.3` that is a broken build (SIGFPE on every
678+
// filesystem-opening command, while dumpe2fs/e2fsck/tune2fs from the same
679+
// payload work). That is an ecosystem defect with its own record in the
680+
// index, not an engine gap, and it moves this row to `verified` when it is
681+
// fixed -- nothing here changes.
682+
//
683+
// One linker warning is worth recording because a user will see it and it
684+
// is not a defect: `unsupported flags DT_FLAGS_1=0x8000001`. API 24's
685+
// bionic linker does not recognise the `DF_1_PIE` bit that lld sets, warns,
686+
// and loads the program anyway.
687+
{ "x86_64-linux-android", "verified", "", "android-ndk@30.0.16248370", "", false },
660688

661689
// iOS IS NEXT. `aarch64-macos` is `verified`, so Mach-O, `arm64`, the
662690
// linker and the Apple half of the toolchain model all exist; what is

tests/unit/test_toolchain_triple.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,13 +707,20 @@ TEST(Triple, EachRowsTierMatchesTheEvidenceThatExistsForIt) {
707707
// ONE PIN SERVES BOTH ROWS, which is the property the whole Android path
708708
// rests on: the NDK names no arch, `--target` does, and that is why the
709709
// std module's own precompile had to be told the target as well.
710-
for (auto name : {"aarch64-linux-android", "x86_64-linux-android"}) {
710+
// ONE PIN, TWO TIERS, and the tiers differ by EXECUTION rather than by
711+
// confidence in the build. `x86_64-linux-android` ran on the platform's
712+
// own emulator (API 24 x86_64 image, KVM): `adb push` then
713+
// `adb shell ./andtest` printed `1-2-3`, exit 0. The device row has no
714+
// execution path from an x86_64 host -- Google's emulator refuses a
715+
// foreign guest outright -- so it stays `preview`.
716+
for (auto [name, tier] : {std::pair{"aarch64-linux-android", "preview"},
717+
std::pair{"x86_64-linux-android", "verified"}}) {
711718
auto t = parse(name);
712719
ASSERT_TRUE(t.has_value()) << name;
713720
EXPECT_EQ(t->str(), name);
714721
auto* info = find_known_target(*t);
715722
ASSERT_NE(info, nullptr) << name;
716-
EXPECT_EQ(info->tier, "preview") << name;
723+
EXPECT_EQ(info->tier, tier) << name;
717724
EXPECT_EQ(info->pin, "android-ndk@30.0.16248370") << name;
718725
// Same statement the wasm row makes: the SDK ships the sysroot.
719726
EXPECT_TRUE(info->sysroot.empty()) << name;

0 commit comments

Comments
 (0)