|
| 1 | +# aarch64 Linux:让整个生态可用,并且可测 |
| 2 | + |
| 3 | +> 2026-08-26。起因:把 `linux-aarch64` 加进 `ci-target-matrix.yml` 的构建机轴, |
| 4 | +> 第一次运行就连挖出三层缺陷,而它们都不在 mcpp 里。 |
| 5 | +
|
| 6 | +--- |
| 7 | + |
| 8 | +## 0. 一句话 |
| 9 | + |
| 10 | +**mcpp 为 aarch64 Linux 发布二进制,而它到那里之后能用的东西只有 `musl-gcc` |
| 11 | +和 `ninja`。** 其余每一个宿主代码包 —— llvm、glibc、linux-headers、zlib、 |
| 12 | +libxml2、gcc-runtime、mingw-cross-gcc —— 都只发 x86_64。 |
| 13 | + |
| 14 | +⚠️ **这不是「aarch64 没做」,是「aarch64 做了一半而没有任何东西在看」**: |
| 15 | +`ci-aarch64-fresh-install.yml` 一直是绿的,因为它验的是 |
| 16 | +`xlings install mcpp` 装得上、能编一个 hello world,而不是那台机器上的目标矩阵。 |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## 1. 判据:什么叫「aarch64 全生态可用」 |
| 21 | + |
| 22 | +不用形容词。四条,每条都能被一条命令回答: |
| 23 | + |
| 24 | +| # | 判据 | 怎么测 | |
| 25 | +|---|---|---| |
| 26 | +| **J1** | `mcpp toolchain list --format json` 在 aarch64 上列出的每一行,`status != planned` 的都真的装得上 | `tests/matrix/scan.sh` 的 `unsupported/other` 与 `mismatch` 格数为 0 | |
| 27 | +| **J2** | aarch64 的 `expected.tsv` 行存在,且 `coverage` job 通过 | 发布集 == 矩阵集 | |
| 28 | +| **J3** | 七个 openkal 仓库在 aarch64 runner 上 CI 绿 | 各仓 `ci.yml` 加 aarch64 leg | |
| 29 | +| **J4** | `mcpp-index` 的每个包在 aarch64 上能被 `mcpp add` + 构建 | 索引 smoke 加 aarch64 leg | |
| 30 | + |
| 31 | +⭐ **J1 不要求「全部 ok」。** 一个诚实的 `unsupported` 带具名 `reason` 就是通过。 |
| 32 | +不可接受的是 `mismatch`(解析说行、构建炸了)与 `other`(拒绝了而没有理由)。 |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## 2. 现状:四个仓库各缺什么(实测 2026-08-26) |
| 37 | + |
| 38 | +### 2.1 `xim-pkgindex` —— 主要缺口在这里 |
| 39 | + |
| 40 | +| 包 | `archs` 声明 | 实际有 linux-aarch64 资产 | 性质 | |
| 41 | +|---|---|---|---| |
| 42 | +| `llvm` | `{x86_64, arm64}` | **否** | ⚠️ 声明了却没有;`arm64` 只对 macosx 成立 | |
| 43 | +| `gcc` | `{x86_64}` | 否 | 正确 —— 其它架构由 `musl-gcc` 覆盖 | |
| 44 | +| `musl-gcc` | `{x86_64, aarch64}` | **是** ✅ | | |
| 45 | +| `ninja` | `{x86_64, aarch64}` | **是** ✅ | | |
| 46 | +| `glibc` | `{x86_64}` | 否 | 目标代码 | |
| 47 | +| `linux-headers` | `{x86_64}` | 否 | 目标代码 | |
| 48 | +| `zlib` | `{x86_64}` | 否 | llvm 的宿主侧依赖 | |
| 49 | +| `libxml2` | `{x86_64}` | 否 | llvm 的宿主侧依赖 | |
| 50 | +| `gcc-runtime` | `{x86_64}` | 否 | llvm 的宿主侧依赖 | |
| 51 | +| `mingw-cross-gcc` | `{x86_64}` | 否 | 宿主代码 | |
| 52 | +| `picolibc-riscv` | `{x86_64, aarch64}` | 不需要 ✅ | ⭐ 目标代码,一份归档服务所有宿主 | |
| 53 | + |
| 54 | +⚠️ **`llvm.lua` 第 178 行**:`local llvmdir = "llvm-" .. version .. "-linux-x86_64"` |
| 55 | +—— 架构写死在解包路径里,与 `archs` 的声明矛盾。 |
| 56 | + |
| 57 | +### 2.2 `xlings` —— 发了 aarch64,但形状不同 |
| 58 | + |
| 59 | +`xlings-2026.8.17.2-linux-aarch64.tar.gz` 存在。⚠️ 但两个包的**内部布局不一样**: |
| 60 | + |
| 61 | +``` |
| 62 | +linux-x86_64 subos/default/bin/xlings 513 条目 |
| 63 | +linux-aarch64 bin/xlings 494 条目 |
| 64 | +``` |
| 65 | + |
| 66 | +(x86_64 的 `subos/default/bin/xlings` 是指向 `bin/xlings` 的符号链接,同一文件。) |
| 67 | + |
| 68 | +### 2.3 `mcpp-index` —— 结构上无缺口 |
| 69 | + |
| 70 | +包是**源码分发**(`add_urls` + `add_versions` + sha256),没有 `archs` 字段, |
| 71 | +也不需要。缺的是**验证**:没有任何一条 CI 在 aarch64 上构建过它们。 |
| 72 | + |
| 73 | +### 2.4 `mcpp` 自己 —— 发布了,但表在说谎 |
| 74 | + |
| 75 | +`release.yml` 产出 `mcpp-<v>-linux-aarch64.tar.gz`。而 |
| 76 | +`available_toolchain_indexes()` 是一张按 OS 分支、**不问架构**的静态表: |
| 77 | + |
| 78 | +```cpp |
| 79 | +{ "gcc", ... }, { "musl-gcc", ... }, { llvm::package_name(), ... } |
| 80 | +… else if constexpr (is_linux) out.push_back({ "mingw-cross-gcc", ... }); |
| 81 | +``` |
| 82 | +
|
| 83 | +⇒ aarch64 上 `toolchain list` 会说 llvm 可装、四个裸机行 `available`, |
| 84 | +而 `mcpp toolchain install llvm 22.1.8` 会 404。**声明 ≠ 安装**,又一次。 |
| 85 | +
|
| 86 | +--- |
| 87 | +
|
| 88 | +## 3. 分类:宿主代码与目标代码,决定了每一项的工作量 |
| 89 | +
|
| 90 | +⭐⭐ **这是整份计划里唯一真正的结构判断。** 一个包属于哪一类,决定它需不需要 |
| 91 | +per-host-arch 的构建: |
| 92 | +
|
| 93 | +| 类 | 含义 | aarch64 需要什么 | 本仓涉及的包 | |
| 94 | +|---|---|---|---| |
| 95 | +| **宿主代码** | 在构建机上运行 | **一份 aarch64 构建** | llvm, musl-gcc, ninja, zlib, libxml2, gcc-runtime, mingw-cross-gcc | |
| 96 | +| **目标代码** | 在目标机上运行 | 什么都不用 —— 一份归档服务所有宿主 | picolibc-*, openkal-*(源码) | |
| 97 | +| **目标侧 sysroot** | 目标机的 C 库 | 一份 **per-target** 构建,与宿主无关 | glibc, linux-headers | |
| 98 | +
|
| 99 | +⚠️ **`glibc` 与 `linux-headers` 在这张表里最容易被归错。** 它们是 |
| 100 | +`x86_64-linux-gnu` 这个**目标**的 C 库,不是宿主的。aarch64 宿主要它们,是因为 |
| 101 | +`aarch64-linux-gnu` 这个**目标**需要 —— 那是另一件事,见 §5.4。 |
| 102 | +
|
| 103 | +--- |
| 104 | +
|
| 105 | +## 4. 关键决策:aarch64 的 LLVM 怎么建 |
| 106 | +
|
| 107 | +### 4.1 上游没有 |
| 108 | +
|
| 109 | +| 来源 | linux-aarch64 | |
| 110 | +|---|---| |
| 111 | +| `llvm/llvm-project` 19.1.7 | ✅ `clang+llvm-19.1.7-aarch64-linux-gnu.tar.xz` | |
| 112 | +| `llvm/llvm-project` 20.1.7 / 21.1.0 | **无** | |
| 113 | +| `xlings-res/llvm` 20.1.7 / 22.1.8 | **无** | |
| 114 | +
|
| 115 | +⚠️ 上游从 20.x 起停发 linux-aarch64,而 mcpp 钉 20.1.7 / 22.1.8,且需要 |
| 116 | +C++23 modules + `import std` —— 19.x 顶不上。**只能自己建。** |
| 117 | +
|
| 118 | +### 4.2 两个方案 |
| 119 | +
|
| 120 | +**方案 A:照搬 x86_64 的形状(glibc 动态链接 + 五个依赖包)** |
| 121 | +
|
| 122 | +x86_64 的载荷实测:896 MB(bin 756 MB / lib 123 MB / include 16 MB), |
| 123 | +`deps = {glibc>=2.39, linux-headers, zlib, libxml2, gcc-runtime}`。 |
| 124 | +
|
| 125 | +照搬意味着 aarch64 上要先补齐 **zlib / libxml2 / gcc-runtime** 三个宿主包, |
| 126 | +外加 aarch64 的 `glibc` / `linux-headers`。⇒ **6 个包的链**。 |
| 127 | +
|
| 128 | +**方案 B:静态链接 musl(⭐ 推荐)** |
| 129 | +
|
| 130 | +用索引里已有的 `aarch64-linux-musl-gcc` 自举,把 clang/lld 静态链到 musl: |
| 131 | +
|
| 132 | +- 依赖数 **5 → 0**。没有 glibc、没有 gcc-runtime、没有 zlib/libxml2 的 |
| 133 | + 运行期问题(编进去)。 |
| 134 | +- 与 `musl-gcc` 在 aarch64 上已经成立的形状一致 —— 那个包正是这样做的, |
| 135 | + 而它是这台机器上**唯一现在就能用的工具链**。 |
| 136 | +- ⚠️ 代价:体积更大,且要关掉 `LLVM_ENABLE_LIBXML2` 等可选特性。 |
| 137 | +- ⚠️ 未验:libc++ 的 `std` 模块在 musl-static 的 clang 下是否完整可用。 |
| 138 | + **这是方案 B 的第一个判据,必须先于其余工作验证。** |
| 139 | +
|
| 140 | +### 4.3 建在哪 |
| 141 | +
|
| 142 | +`ubuntu-24.04-arm` 是原生 aarch64 runner,GitHub 单 job 上限 6 小时。 |
| 143 | +LLVM 完整构建约 1.5–3 小时 —— **放得下,不需要交叉编译**。 |
| 144 | +
|
| 145 | +⚠️ `xlings-res/llvm` 目前是纯资产仓(只有 README + releases),构建脚本 |
| 146 | +`build-llvm-subpkg.sh` 不在其中。⇒ 需要在该仓新建一个 workflow, |
| 147 | +并把 carve 配方(哪些留、哪些删)显式写进去,而不是继续留在某个人的工作目录里。 |
| 148 | +
|
| 149 | +--- |
| 150 | +
|
| 151 | +## 5. 分阶段计划与依赖 |
| 152 | +
|
| 153 | +``` |
| 154 | +P0 ──┬── P1(llvm 构建)──┬── P3(索引接线)── P4(mcpp 诚实)── P5(生态 CI) |
| 155 | + │ │ |
| 156 | + └── P2(xlings 形状)──┘ P6(aarch64-linux-gnu 目标) |
| 157 | +``` |
| 158 | +
|
| 159 | +### P0 — 让 aarch64 引导跑通(⏳ 进行中,已提交) |
| 160 | +
|
| 161 | +三层,一层盖住一层,每修一层才露出下一层: |
| 162 | +
|
| 163 | +| # | 缺陷 | 症状 | 状态 | |
| 164 | +|---|---|---|---| |
| 165 | +| ① | `bootstrap-mcpp` 按 `uname -s` 取 x86_64 xlings | `Exec format error` 126 | ✅ 已修 | |
| 166 | +| ② | 缓存键无 `runner.arch`,aarch64 命中 x86_64 缓存 | ①的修复一次没跑到 | ✅ 已修 | |
| 167 | +| ③ | 两个 tarball 内部布局不同 | `No such file or directory` 127 | ✅ 已修(find 而非硬编码) | |
| 168 | +
|
| 169 | +**判据**:`invariants (linux-aarch64)` 绿。 |
| 170 | +
|
| 171 | +### P1 — 建 aarch64 的 LLVM |
| 172 | +
|
| 173 | +- **P1.1 可行性判据(先做)**:在 `ubuntu-24.04-arm` 上用 |
| 174 | + `aarch64-linux-musl-gcc` 静态建一个最小 clang+lld,验证 |
| 175 | + `import std` 与 libc++ 的 `std.cppm` 可用。⭐ 这一条不过,方案 B 作废,回 A。 |
| 176 | +- **P1.2**:在 `xlings-res/llvm` 新建 `build-aarch64.yml`,产出 |
| 177 | + `llvm-22.1.8-linux-aarch64.tar.xz` + `.sha256`,carve 配方写进 workflow。 |
| 178 | +- **P1.3**:同样产出 20.1.7(target 表两个版本都在用)。 |
| 179 | +- **判据**:资产可下载,sha256 匹配,解包后 `bin/clang++ --version` 在 |
| 180 | + aarch64 上退 0。 |
| 181 | +
|
| 182 | +### P2 — xlings 包形状统一 |
| 183 | +
|
| 184 | +⚠️ 不改 xlings 的发布(那会动别的消费者)。**mcpp 侧已经改成 find 而非硬编码**, |
| 185 | +这是正确的方向:消费者不该假设生产者的内部布局。 |
| 186 | +
|
| 187 | +- **P2.1**:向 xlings 报一个 issue,说明两个 Linux 包布局不一致。 |
| 188 | +- **判据**:issue 有编号;mcpp 侧的 find 已经不依赖它被修。 |
| 189 | +
|
| 190 | +### P3 — `xim-pkgindex` 接线 |
| 191 | +
|
| 192 | +- **P3.1** `llvm.lua`:把第 178 行的 `linux-x86_64` 改成按 `os.arch()` 取, |
| 193 | + 并给 linux 分支加 aarch64 的资源条目。 |
| 194 | +- **P3.2** ⚠️ **`archs` 要按 OS 拆**,或者在 linux 分支缺资产时让 xim 说 |
| 195 | + 「本架构未发布」而不是 404。⭐ 这是「声明 ≠ 安装」在索引侧的同一条: |
| 196 | + 一个包级 `archs` 覆盖三个 OS,而三个 OS 的资产覆盖面不同。 |
| 197 | +- **P3.3**(方案 A 才需要)补 zlib / libxml2 / gcc-runtime 的 aarch64。 |
| 198 | +- **判据**:`xlings install llvm@22.1.8` 在 aarch64 上成功; |
| 199 | + 失败时的消息点名架构。 |
| 200 | +
|
| 201 | +### P4 — mcpp 对 aarch64 诚实 |
| 202 | +
|
| 203 | +- **P4.1** `available_toolchain_indexes()` 目前按 OS 分支不问架构。 |
| 204 | + ⚠️ **不要把索引数据抄进 mcpp** —— 那正是会漂移的形状。 |
| 205 | + 正确做法:让这张表回答「这个 (os, arch) 上这个族有没有载荷」, |
| 206 | + 而答案来自已有的 `list_available_xpkg_versions()` 查询, |
| 207 | + 与 `gcc_native_payload_is_musl()` 已经在做的事同型。 |
| 208 | +- **P4.2** `host_can_serve()` 对裸机无条件 `true`,理由是 |
| 209 | + 「clang/lld 按构造就是交叉编译器」。⚠️ **那个理由预设了 clang 在这台机器上 |
| 210 | + 存在。** 在 llvm 落地前,aarch64 上四个裸机行应报一句可读的拒绝, |
| 211 | + 带 `reason`,而不是 404。 |
| 212 | +- **判据**:aarch64 上 `scan.sh` 的 `mismatch` 与 `other` 均为 0。 |
| 213 | +
|
| 214 | +### P5 — 生态 CI 加 aarch64 |
| 215 | +
|
| 216 | +- **P5.1** 七个 openkal 仓库的 `ci.yml` 各加一条 `ubuntu-24.04-arm` leg。 |
| 217 | + ⚠️ `openkal-llvm-runtime` 要求 `mcpp:compiler=llvm`,**依赖 P1**; |
| 218 | + `openkal-musl` 只 `provides c-abi=musl`,**现在就能跑**。 |
| 219 | + ⇒ 分两批:musl 层先行,llvm 层等 P1。 |
| 220 | +- **P5.2** `mcpplibs-index` smoke 加 aarch64 leg(源码分发,只需验证能建)。 |
| 221 | +- **P5.3** 回填 `tests/matrix/expected.tsv` 的 `linux-aarch64` 行。 |
| 222 | +- **判据**:J2 / J3 / J4。 |
| 223 | +
|
| 224 | +### P6 — `aarch64-linux-gnu` 作为**目标**(独立,可后置) |
| 225 | +
|
| 226 | +今天它是 `planned`。要让它 `verified` 需要 aarch64 的 `glibc` 与 |
| 227 | +`linux-headers` **目标侧**载荷。⚠️ 与 P1–P5 无依赖关系 —— 那是「aarch64 作为 |
| 228 | +构建机」,这是「aarch64 作为目标」。**两件事不要混。** |
| 229 | +
|
| 230 | +--- |
| 231 | +
|
| 232 | +## 6. CI 验收 |
| 233 | +
|
| 234 | +| 层 | 在哪 | 加什么 | |
| 235 | +|---|---|---| |
| 236 | +| 构建机轴 | `ci-target-matrix.yml` | ✅ 已加 `linux-aarch64` | |
| 237 | +| 分母 | `coverage` job | ✅ 已加:发布集 == 矩阵集,双向 | |
| 238 | +| 期望表 | `tests/matrix/expected.tsv` | P5.3 回填 aarch64 行 | |
| 239 | +| 生态 | 七个 openkal 仓 | P5.1,分两批 | |
| 240 | +| 索引 | `mcpplibs-index` | P5.2 | |
| 241 | +| 引导 | `ci-aarch64-fresh-install.yml` | ⚠️ 它验的是装得上,**不是**目标矩阵。保留,但不要把它的绿读成覆盖 | |
| 242 | +
|
| 243 | +⚠️⚠️ **`expected.tsv` 里不写 `mismatch`。** 写下它就是把缺陷声明成期望, |
| 244 | +矩阵会在那一格恒绿。aarch64 的行要么 `ok`,要么带具名 `reason` 的 |
| 245 | +`unsupported` —— llvm 缺席期间,四个裸机行应当是后者。 |
| 246 | +
|
| 247 | +--- |
| 248 | +
|
| 249 | +## 7. 已知陷阱 |
| 250 | +
|
| 251 | +⚠️ **一层修复会被上一层盖住。** P0 的三条就是这样:缓存键的缺陷让架构修复 |
| 252 | +一次都没执行。改完一层一定要看下一层的读数,而不是假定它通了。 |
| 253 | +
|
| 254 | +⚠️ **改共享路径前先查那三台正在工作的宿主。** P0③ 的 find 在 x86_64 上解析到 |
| 255 | +另一个路径;查过才知道两者是符号链接、同一文件。一个悄悄把 macOS 和 Windows |
| 256 | +挪到别的二进制上的修复,比它修的缺陷更糟。 |
| 257 | +
|
| 258 | +⚠️ **`ci-aarch64-fresh-install.yml` 一直是绿的。** 它走 |
| 259 | +`quick_install.sh`(自己读架构),所以从来没碰到 P0①。**一条绿的 CI 不证明 |
| 260 | +另一条路径可用** —— 这正是为什么构建机轴要按 mcpp **发布**的那一组来定, |
| 261 | +而不是按「手头有哪几台 runner」。 |
| 262 | +
|
| 263 | +⚠️ **不要把索引的架构覆盖面抄进 mcpp。** P4.1 的诱惑是写一张 |
| 264 | +「llvm 没有 aarch64」的表。那张表会在 P1 落地的当天变成错的,而没有任何东西 |
| 265 | +会提醒你。要问索引,不要记住索引。 |
0 commit comments