Skip to content

RFC(勿合入): 适配 HarmonyOS/OpenHarmony —— 可重定向的 clang + 平台 SDK 作 sysroot - #351

Draft
speak-agent wants to merge 2 commits into
mainfrom
rfc/harmonyos-target
Draft

RFC(勿合入): 适配 HarmonyOS/OpenHarmony —— 可重定向的 clang + 平台 SDK 作 sysroot#351
speak-agent wants to merge 2 commits into
mainfrom
rfc/harmonyos-target

Conversation

@speak-agent

Copy link
Copy Markdown
Member

⛔ 这个 PR 不要合入

它是一次调研 + 可运行的探针:证明「mcpp 能不能适配 HarmonyOS、代价是什么」。
引擎改动是真的、跑通的,但配套的 payload 分发、动态链接档位、以及维护者对
职责边界的定调都还没有。请把它当证据齐全的 RFC 读,不是当交付读。

方案文档:.agents/docs/2026-08-04-harmonyos-target-design.md(含全部实测数据)
参考项目:TermonyHQ/Termony


TL;DR

鸿蒙可以成为 mcpp 的一等目标,而且是完整体验 —— 具名模块和 import std 都跑通了。
代价是必须先把 config② 里一直没实现的那半(clang 交叉通道)做出来。

export OHOS_NDK_HOME=/path/to/ohos-sdk/linux/native
mcpp build --target aarch64-linux-ohos
qemu-aarch64 target/aarch64-linux-ohos/*/bin/demo     # 真的跑起来

结论 1:鸿蒙 SDK 自带的编译器,mcpp 永远用不了

这是整个设计的地基,所以先摆证据 —— 实测,不是从文档推断:

$ ohos-sdk/linux/native/llvm/bin/clang++ --version
OHOS (dev) clang version 15.0.4

这是 SDK 6.1(API 23,2026-03 发布),不是什么老版本。而:

需要 起始版本 SDK 有
-fmodule-output(C++20 模块) clang 16 ❌ 15.0.4
import std(libc++ std 模块) libc++ 19 ❌ libc++ 15.004,全树无 std.cppm

差一到四代,而且已经这样很多年。「等厂商升级」不是方案。

Termony 走的是「用 SDK 编译器」那条路,对它成立(编的基本是 C);对 mcpp 结构性
不成立 —— 模块图和 import std 是 mcpp 的全部价值,那个编译器一行都编不了。
Termony 真正给到的参考是验证路径:qemu-user 跑鸿蒙 aarch64 产物可行(已复现)。

结论 2:所以鸿蒙是第一个「只能 config②、且只能 clang」的目标

.agents/docs/2026-07-24-embedded-platform-support-design.md 的决策 #2 定了
config②(mcpp 自带编译器 + 消费外部 sysroot),决策 #8 定了「先 GCC,clang 路线存档」。
鸿蒙把这两条撞在一起:

config② 需要:  能编 C++23 modules 的、面向 target 的编译器
GCC 能提供吗:  不能 —— GCC 根本没有 ohos target
SDK 能提供吗:  不能 —— clang 15.0.4
剩下:          mcpp 自己的 clang,用 --target 重定向

不是「适合用 clang」,是「除了 clang 无路可走」。 决策 #8 存档的那条路线被现实
提前触发了。

而这个缺口仓库里早就写着:

# .github/workflows/cross-build-test.yml
#   * llvm/clang cross  : clang is inherently a cross-compiler, but mcpp does not
#                         yet inject `-target <triple>` + a cross sysroot for a
#                         clang toolchain … Wire the clang cross path first.

本 PR 做的就是 "wire the clang cross path first"。鸿蒙只是第一个非它不可的消费者
—— aarch64-linux-gnu(决策 #3 的树莓派滩头)之后可以走同一条缝,不必再造一套。

结论 3:import std 不是能力缺口,是 payload 缺口

用 LLVM 源码给 aarch64-linux-ohos 编一份 libc++(关键是 -DLIBCXX_HAS_MUSL_LIBC=ON),
std.cppm 就有了:

$ qemu-aarch64 target/aarch64-linux-ohos/*/bin/ohosstd
import std on HarmonyOS
total=20

所以是两档,mcpp 自动识别并明说自己在哪一档:

C++ 标准库 具名模块 import std
原版 SDK SDK 的 libc++ 15.0.4 ❌(有明确提示,不是编译器报错)
+ 目标 libc++ 自建 libc++(MCPP_OHOS_LIBCXX)

两档都有 e2e,都在 qemu 下真跑。

机制(三个不显然的决定)

ohosenv 不是 os

aarch64-linux-ohos = arch aarch64 + os linux + env ohos,与上游 LLVM
(llvm::Triple::OpenHOS)一致。

这有后果,而且后果是对的:内核确实是 Linux,所以已有可移植包里的
cfg(os = "linux") / cfg(family = "unix") 必须继续匹配。若拼成一个新 os,那些段会
静默失配 —— 一个包在鸿蒙上突然少编一半源码,零诊断。

反向那条同样重要:is_musl() 返回 false。OHOS libc 确实是 musl 的 fork,但
mcpp 里 "musl" 处处指上游 musl(payload 选择、abi:musl capability),鸿蒙产物
与之不可互换 ⇒ ABI 维度上 libc = "ohos" 自成取值。而 loader 命名照旧走 musl 那条
(实测 PT_INTERP = /lib/ld-musl-aarch64.so.1)。同一个事实在两个问题上给出不同
答案,这正是它们得是两个函数的原因。

-resource-dir 只能出现在链接侧

上游 clang 的 OHOS driver 会去自己的 resource dir 找 libclang_rt.builtins.a
clang_rt.crt{begin,end}.o —— 它当然没有 aarch64-linux-ohos 子目录,于是
cannot open crtbeginT.o。指向 SDK 的那份就解决了。

但同一个 flag 也换掉内建头(stddef.h 等),让 clang 20 去读 clang 15 的内建头是
另一个安静得多的 bug。mcpp 的 compile/link flags 本来就是两条串,所以只在后者发。

std.cppm 由 provider 给,不由 driver 探

clang::enrich_toolchain-print-library-module-manifest-pathstd.cppm ——
driver 答的是它自己的 libc++,交叉时那是宿主的。拿它去编 std BMI 不会报错,
只会产出一个面向错误平台的 BMI。所以交叉路径整段短路;供不出来就
hasImportStd = false + 明确提示。

同理 probe_payload_paths() 在交叉时必须跳过 —— 它会找到编译器旁边宿主的 glibc
xpkg,而 CLibMode::PayloadFirst 会把宿主的 crt1.o/libc.so/loader 喂给一个外国目标。

验证

本机全部跑过(x86_64 Linux + OpenHarmony SDK 6.1.0.31 + qemu-aarch64):

  • mcpp build --target aarch64-linux-ohosELF … ARM aarch64 … statically linked
  • qemu 下真的执行并输出预期内容(两档都是)
  • 单测 16 条全绿(全部 host-independent,没有 SDK 的机器也跑)
  • mcpp test 56 个测试二进制全绿;自举构建正常 ⇒ 宿主路径零行为变化
    (OhosCross.HostToolchainIsUntouchedByTheseChanges 是这条的回归闸)

CI(.github/workflows/ci-harmonyos.yml,两个 job):tier 1 用
openharmony-rs/setup-ohos-sdk 装 SDK 后交叉编 + qemu 跑;tier 2 额外从 LLVM 源码
编目标 libc++ 再验 import std

CI 证明不了什么(重要)

  • qemu-user 跑的是指令集,不是 HarmonyOS。 对系统调用兼容性、设备行为一概不发言。
  • .hnp/.hap 打包与 hdc 安装完全没做。
  • 没有链接平台 NDK 库(libace_napi.z.so 等)。全静态产物本来也链不了(只有 .so);
    真做鸿蒙 App 要走动态链接,而实测动态产物在 qemu 下跑不起来 ⇒ 那一档必须真机/
    模拟器验证,CI 给不了绿。
  • 鸿蒙模拟器没用上 —— GitHub runner 跑不了(要 DevEco + 虚拟化 + 厂商镜像)。
    这是「利用 CI 的各种 OS 资源」这条里唯一没兑现的部分,原因是客观不可得。

verified 档位的定义是「CI builds and executes」。本 PR 的 aarch64-linux-ohos
满足这个定义 —— 但满足的是与 aarch64-linux-musl 同级的断言,不是「鸿蒙 App
能上架」。

需要维护者定调的三件事

  1. ohos-libcxx 要不要 payload 化? 这是唯一横在「能用」与「好用」之间的东西。
    做了,mcpp build --target aarch64-linux-ohos 就是开箱完整体验。
  2. .hnp/.hap 打包属不属于 mcpp 职责? 对照决策 feat: compile .c sources via a separate C rule #1「不做发行版构建器」,
    我倾向不做,但这是边界问题不是技术问题。
  3. clang 交叉缝要不要立刻用到 aarch64-linux-gnu(树莓派)? 如果要,feat/RFC: 支持 Buildroot/OpenWrt/Yocto 等嵌入式 Linux SDK 的工程化集成 #276
    P0 排序会变 —— 原方案里「自建低 glibc 的 GCC 16」是唯一真实未知,而这条缝让它
    不再是前置条件。

改了什么

引擎 12 个文件(见方案文档 §4 的表),核心是新的
Toolchain::crossTarget + src/toolchain/ohos.cppm。新增单测 1 个文件 16 条、
e2e 2 条、CI workflow 1 个、示例 1 个、文档若干。

@speak-agent speak-agent added do-not-merge 探针/RFC:不合入 enhancement New feature or request labels Aug 3, 2026
@speak-agent
speak-agent marked this pull request as ready for review August 3, 2026 20:59
@speak-agent speak-agent closed this Aug 3, 2026
@speak-agent speak-agent reopened this Aug 3, 2026
@speak-agent

Copy link
Copy Markdown
Member Author

CI 运行说明

本仓的 pull_request 事件当前没有生成任何 workflow run(PR 打开、close/reopen、以及一次 synchronize 推送都试过,check-runs 恒为 0),而 workflow_dispatch 正常。所以这一轮 CI 是手动 dispatch 到本分支的:

  • ci-linux / ci-linux-e2e / ci-macos / ci-windows — 覆盖宿主路径有没有被这次改动碰坏
  • cross-build-test — 覆盖既有 cross 矩阵(aarch64-musl / mingw-wine / windows→linux)在 linkmodel 改动后仍然成立

唯一跑不了的是 ci-harmonyos.yml 本身:workflow_dispatch 要求 workflow 文件存在于默认分支,而它只在本分支上 —— 鸡生蛋。等 pull_request 事件恢复,它会自己跑起来;在那之前,它验证的两档已在本机端到端跑过(见 PR 正文与方案文档 §5)。

本机验证结果:e2e 全量 178 passed / 0 failed / 8 skipped(含新增的 103/104 两条鸿蒙用例),单测 56 个二进制全绿(新增 16 条),自举构建正常。

@Sunrisepeak

Copy link
Copy Markdown
Member

CI 结果:五个 workflow 全绿(跑的就是本 PR 的 commit)

最后一条是这次最想看到的:既有 cross 矩阵(aarch64-musl/qemu、mingw/wine、windows→linux)在 linkmodel.cppm / hostflags.cppm / flags.cppm 被改过之后一条没坏。所有新分支都以 tc.crossTarget 为门,OhosCross.HostToolchainIsUntouchedByTheseChanges 是这条的单测闸,这三个平台的 CI 是它的端到端闸。

在 CI runner 上 103/104 两条鸿蒙 e2e 会被 capability 门跳过(没有 SDK、没有 qemu)——这是对的行为:跑不了的测试必须 skip 而不是 fail。

一处补验(把 CI 与本机之间最后一点偏差抹掉)

ci-harmonyos 的 tier 2 用 llvm 20.1.7 编目标 libc++(要与目标 pin 一致),而我最早的本机验证用的是 22.1.8。已经按 CI 的配置重跑一遍 —— 用 llvmorg-20.1.7 源码 + mcpp 索引里的 llvm@20.1.7 payload 重建目标 libc++,104_harmonyos_import_std.sh 通过:

Resolved llvm@20.1.7 → aarch64-linux-ohos → …/xim-x-llvm/20.1.7/bin/clang++
Resolved aarch64-linux-ohos → OpenHarmony SDK 6.1.0.31 (API 23) + external libc++
import std on HarmonyOS
total=20
OK: import std works on HarmonyOS

所以 triple::pins::kOhosLlvm = llvm@20.1.7 这个 pin 本身也是实测过的,不是照抄 kSuggestLlvm

还差一个 job 没跑

ci-harmonyos.yml 本身跑不了:workflow_dispatch 要求 workflow 文件在默认分支上,而它只在本分支 —— 鸡生蛋。而本仓当前的 pull_request 事件不产生 run(见上一条评论),所以它得等那个恢复、或者等这个 workflow 以别的方式落到 main。它验证的两档已在本机端到端跑过。

@Sunrisepeak
Sunrisepeak marked this pull request as draft August 3, 2026 21:19
…atform SDK

RFC / 探针 —— 不要合入。方案与实测数据:
.agents/docs/2026-08-04-harmonyos-target-design.md

鸿蒙 SDK 自带的编译器 mcpp 永远用不了:实测 SDK 6.1(API 23,2026-03)仍是
clang 15.0.4,比 C++20 模块所需的 -fmodule-output(clang 16)差一代,比
import std(libc++ 19+)差四代。而 GCC 根本没有 ohos target。

所以鸿蒙是第一个「只能走 config②、且只能用 clang」的目标 —— mcpp 自带编译器,
平台只提供 sysroot。这逼出了仓库里早就记着待做的那件事:

    # cross-build-test.yml
    #   * llvm/clang cross : … Wire the clang cross path first, then add a row.

本 commit 就是 wire 那条缝(Toolchain::crossTarget:--target= + 外部 sysroot
+ 目标 libc++ + 链接侧 -resource-dir),鸿蒙只是第一个非它不可的消费者。

三个不显然的决定:

* `ohos` 是 env 不是 os(与上游 LLVM 一致)。内核确实是 Linux,已有包里的
  cfg(os = "linux") / cfg(family = "unix") 必须继续匹配;拼成新 os 会让那些段
  静默失配。反过来 is_musl() 返回 false —— OHOS libc 是 musl 的 fork,但 mcpp
  里 "musl" 处处指上游 musl,产物不可互换 ⇒ ABI 上 libc = "ohos" 自成取值。
* -resource-dir 只在链接侧。它供给目标的 compiler-rt/crt*,但同一个 flag 也换掉
  内建头,让新 clang 读 clang-15 的内建头是另一个安静得多的 bug。
* std.cppm 由 provider 给,不由 driver 探。driver 答的是它自己的 libc++,交叉时
  那是宿主的 —— 拿去编 std BMI 不报错,只产出面向错误平台的 BMI。

两档,mcpp 自动识别并明说在哪一档:原版 SDK ⇒ 具名模块可用、import std 不可用
(有明确提示);再配一份为目标编译的 libc++($MCPP_OHOS_LIBCXX)⇒ import std
也可用。两档都有 e2e,都在 qemu-aarch64 下真跑。

验证:e2e 全量 178 passed / 0 failed / 8 skipped;单测 56 个二进制全绿(新增 16
条 host-independent 用例);自举构建正常 —— 宿主路径零行为变化,
OhosCross.HostToolchainIsUntouchedByTheseChanges 是这条的回归闸。

CI 证明不了:.hnp/.hap 打包、链接平台 NDK 库、任何需要真机或模拟器的行为。
qemu-user 跑的是指令集不是 HarmonyOS。
@Sunrisepeak
Sunrisepeak force-pushed the rfc/harmonyos-target branch from 41ff2d9 to e5abd27 Compare August 3, 2026 21:25
…mlink chain

The import-std job failed on the line AFTER mcpp printed

    Installed llvm@20.1.7 → …/xim-x-llvm/20.1.7/bin/clang++

because the probe was `find … -type f -name clang++`, and in the LLVM
payload that path is a symlink chain (clang++ -> clang -> clang-20). The
install had succeeded; only the check was wrong. Name the path instead, and
list bin/ on failure so the next wrong assumption is visible from the log.
@Sunrisepeak

Copy link
Copy Markdown
Member

✅ 全绿 —— 包括 ci-harmonyos 自己

上一条评论里说「pull_request 事件不产生 run」是误判。真因是这个 PR 有冲突(CHANGELOG.md,与 #3502026.8.4.1 段撞在同一位置):PR 一旦冲突,GitHub 就算不出 refs/pull/N/merge,而 pull_request 触发的 workflow 正是 checkout 那个 ref —— 于是一个 run 都不会创建workflow_dispatch 直接跑分支 ref,所以照常工作,这正是把我带偏的地方。rebase 到 main 解掉冲突之后,8 个 workflow 立刻全部触发,ci-harmonyos 也终于跑上了。

workflow 结果
ci-linux / ci-linux-e2e
ci-macos / ci-macos-e2e
ci-windows / ci-windows-e2e
cross-build-test ✅ 既有 cross 矩阵一条没坏
ci-harmonyos 两个 job 都过

CI 里的实证(不是本机的)

tier 1(原版 SDK):

OHOS (dev) clang version 15.0.4        ← SDK 自带的,mcpp 不用它
aarch64-linux-ohos  static, cross  llvm 20.1.7  available   ← host_can_serve 探到 SDK
… ELF 64-bit LSB executable, ARM aarch64 … statically linked
harmonyos cross named-module OK
__OHOS__ is defined: this really is a HarmonyOS target
OK: HarmonyOS cross artefact builds and executes

tier 2(用 llvmorg-20.1.7 源码为目标现编 libc++):

Resolved aarch64-linux-ohos → OpenHarmony SDK 6.1.0.31 (API 23) + external libc++
import std on HarmonyOS
total=20
OK: import std works on HarmonyOS

两条都在 qemu-aarch64真的执行过。

中间挂过一次,记一下

tier 2 第一轮红在我自己的探针上,不在改动上 —— mcpp 刚打印完

Installed llvm@20.1.7 → …/xim-x-llvm/20.1.7/bin/clang++

下一行就 FAIL: no clang++ in the llvm payload。因为探针写的是 find … -type f -name clang++,而 payload 里那是一条符号链(clang++ -> clang -> clang-20),-type f 看不见。已改成直接写路径,并在失败时把 bin/ 列出来,免得下一个错误假设又只能靠猜。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge 探针/RFC:不合入 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants