在鸿蒙 PC 上 import std; 拢共分几步?手把手带你在鸿蒙 PC 上编译 llvm-project

本文记录在 MateBook Pro S(HarmonyOS 7)上,用 Harmonybrew 的 ohos-sdk 工具链,从源码把 LLVM 22.1.8 的 clang / clang-tools-extra / lld 以及 libunwind / compiler-rt / libcxxabi / libcxx 全部构建出来,并最终做到 clang++ -std=c++23 foo.cpp -o foo 无额外参数编译、跑通 import std; 的全过程。

HarmonyOS 像是 Linux,但是不是:内核是 Linux,但 uname -s 返回 HarmonyOS;有文件沙箱(/tmp 只读);并且 ELF 必须签名才能执行。这些“没对齐”的地方,正是整个构建过程中绝大部分坑的来源。所以本文不只贴命令,更想把每个参数为什么这么写讲清楚。

给人类看的省流版:本文所涉及的工作 80% 由 AI 完成,只有最开始的一点点探索是我手动进行的。文章也完全由 AI 写出。也就是说,从零开始到成功编译 import std; 实际上拢共这么几步:

  1. 安装 brew
  2. 用 brew 安装 deepseek-harness
  3. 让 deepseek-harness 帮你编译一个新版本的 llvm-project ,要带上 runtimes
  4. 用新版本的 clang 编译 std.cppm
  5. 编译 import std;

上篇 · 构建 clang / clang-tools-extra / lld

0. 前置条件

  • 已通过 Harmonybrew 安装 ohos-sdkclangcmakeninjamake 等工具。
  • 记两个常用变量:
SDK=/storage/Users/currentUser/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/native
TARGET=aarch64-linux-ohos
  • 鸿蒙文件系统有沙箱,/tmp 不可写,所有命令先指好临时目录:
export TMPDIR=$(brew --cache)/manual-builds/tmp
mkdir -p "$TMPDIR"
export TMP="$TMPDIR" TEMP="$TMPDIR"

1. 开工前必改:host triple 探测会卡死在 config.guess

LLVM 的 CMake 在配置阶段会探测「host triple」。入口在 llvm/cmake/modules/GetHostTriple.cmake

function( get_host_triple var )
  ...
  else()
    if(CMAKE_HOST_SYSTEM_NAME STREQUAL Windows AND NOT MSYS)
      ...
    else()
      set(config_guess ${LLVM_MAIN_SRC_DIR}/cmake/config.guess)
      execute_process(COMMAND sh ${config_guess} ...)
      ...
      set( value ${TT_OUT} )
    endif()
  endif()
  ...
endfunction()

而这个 get_host_triple()无条件调用的(不在 -DLLVM_HOST_TRIPLE 之后就不跑了):

  • llvm/cmake/config-ix.cmake:528get_host_triple(LLVM_INFERRED_HOST_TRIPLE)
  • runtimes/CMakeLists.txt:209get_host_triple(LLVM_HOST_TRIPLE)(后面构建运行时库还会再踩一次)

问题在于 config.guess 在鸿蒙上根本跑不通:

$ sh llvm/cmake/config.guess
.../config.guess[1620]: can't create /storage/Users/currentUser/cgcyueMe/dummy.c: Permission denied
config.guess: unable to guess system type

两个原因叠加:

  1. uname -s 返回 HarmonyOSconfig.guess 不认识;
  2. 它想通过「临时目录里编译一个探针程序」来判断系统,而沙箱拦住了临时文件创建(即便换成 2026 版的新 config.guess 也一样失败)。

所以直接把探测结果写死。改 llvm/cmake/modules/GetHostTriple.cmake

function( get_host_triple var )
  set (value "aarch64-unknown-linux-ohos")
  set( ${var} ${value} PARENT_SCOPE )
endfunction( get_host_triple var )

这个字符串从哪来?原生 clang 的 -dumpmachine

$ clang -dumpmachine
aarch64-unknown-linux-ohos

注意:光加 -DLLVM_HOST_TRIPLE=aarch64-unknown-linux-ohos不够的,因为 get_host_triple() 仍会被无条件调用、去跑失败的 config.guess。硬编码 GetHostTriple.cmake 才是正解(-DLLVM_HOST_TRIPLE 只是锦上添花)。

附注:为什么这里 aarch64-linux-ohosaarch64-unknown-linux-ohos 混用?这俩其实是同一个 target 的两种拼写。LLVM 的 triple 是 <arch>-<vendor>-<os>-<environment> 四段,vendor 可省略,省略时 clang 归一化会把它补成 unknown。所以:

  • clang 自己「吐」出来的(-print-target-triple / -dumpmachine、cc1 的 -triple),以及 compiler-rt 的安装子目录(内部走 -print-target-triple),永远用长形式 aarch64-unknown-linux-ohos
  • 而 OHOS 驱动的 getMultiarchTriple() 把目录查找硬编码成短形式 aarch64-linux-ohos(跟 SDK 布局一致)。

一句话:--target/目录名用短形式,clang 输出的字符串用长形式;两者对不上时就软链桥一下(见下篇)。

2. 第二个坑:ohos 工具链给所有 target 强注入 -Wl,--no-undefined

SDK 自带的工具链 ohos.toolchain.cmake 里,公共链接 flag 是这样拼的:

list(APPEND OHOS_COMMON_LINKER_FLAGS
  -Wl,--build-id=sha1
  -Wl,--warn-shared-textrel
  -Wl,--fatal-warnings
  -lunwind)
if(NOT OHOS_ALLOW_UNDEFINED_SYMBOLS)
  list(APPEND OHOS_COMMON_LINKER_FLAGS -Wl,--no-undefined)
endif()

-Wl,--no-undefined(等价于 -z,defs)要求链接时所有符号都必须定义。这是给普通 App 用的合理约束,但 LLVM 有一些 target(共享库、plugin 之类)故意留下未定义符号、留到运行时再解析,于是链接就炸了。

两种修法:

  • 干净的:配置时加 -DOHOS_ALLOW_UNDEFINED_SYMBOLS=ON(上面那个 if(NOT ...) 就会跳过 --no-undefined)。
  • 粗暴的(我第一次就是这么干的):cmake 生成后直接去 build.ninja 里把 -Wl,--no-undefined 删掉,再继续 build。

顺带一提,-Wl,--fatal-warnings 也是被无条件注入的,它会把链接警告升级成错误,后续如果遇到莫名其妙的链接失败,可以一并怀疑它。

3. 完整构建命令 + 逐参数解释

cd /storage/Users/currentUser/ProjectSources/llvm-project

cmake -S llvm -B build_22_ohos -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE="/storage/Users/currentUser/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/native/build/cmake/ohos.toolchain.cmake" \
  -DOHOS_ARCH=arm64-v8a \
  -DOHOS_STL=c++_shared \
  -DOHOS_ALLOW_UNDEFINED_SYMBOLS=ON \
  -DCMAKE_INSTALL_PREFIX=/storage/Users/currentUser/Installed/clang-22 \
  -DCMAKE_BUILD_TYPE=Release \
  -DLLVM_ENABLE_ASSERTIONS=OFF \
  -DLLVM_TARGETS_TO_BUILD=AArch64 \
  -DLLVM_DEFAULT_TARGET_TRIPLE=aarch64-linux-ohos \
  -DLLVM_HOST_TRIPLE=aarch64-unknown-linux-ohos \
  -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld" \
  -DCLANG_DEFAULT_LINKER=lld \
  -DLLVM_USE_LINKER=lld \
  -DBUILD_SHARED_LIBS=OFF \
  -DLLVM_ENABLE_RTTI=ON \
  -DLLVM_INCLUDE_TESTS=OFF \
  -DLLVM_BUILD_TESTS=OFF \
  -DCLANG_INCLUDE_TESTS=OFF \
  -DLLVM_INCLUDE_EXAMPLES=OFF

cmake --build build_22_ohos --target install -j8

(注:这台机器是 4 核,-j8 会超订;内存吃紧的话换成 -j4 更稳。)

逐参数说明:

参数 作用 / 为什么
-DCMAKE_TOOLCHAIN_FILE=...ohos.toolchain.cmake 用 SDK 的 OHOS 工具链:编译器指向 SDK 的 clang 15,设好 sysroot、target triple、-D__MUSL__
-DOHOS_ARCH=arm64-v8a 目标架构 aarch64
-DOHOS_STL=c++_shared 构建出来的 clang/lld 自身用 SDK 的共享 libc++(libc++_shared.so)链接
-DOHOS_ALLOW_UNDEFINED_SYMBOLS=ON 关掉工具链强注入的 -Wl,--no-undefined(见上节)
-DCMAKE_INSTALL_PREFIX=... 安装目录
-DCMAKE_BUILD_TYPE=Release 优化构建
-DLLVM_ENABLE_ASSERTIONS=OFF 去掉断言,更小更快
-DLLVM_TARGETS_TO_BUILD=AArch64 只编 AArch64 后端(默认会编全部 target,时间和体积暴增,这里只需要 aarch64)
-DLLVM_DEFAULT_TARGET_TRIPLE=aarch64-linux-ohos 让编译出来的 clang 在不传 --target 时默认就是 aarch64-linux-ohos(省去每次 --target
-DLLVM_HOST_TRIPLE=aarch64-unknown-linux-ohos host triple(探测已被硬编码替代,这里显式给出更保险)
-DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld" 要编的三个子项目
-DCLANG_DEFAULT_LINKER=lld 关键:让产出的 clang 默认用 ld.lld 链接——这是后面「自动签名」生效的根源(见下篇/签名部分)
-DLLVM_USE_LINKER=lld 构建 LLVM 自身二进制时也用 lld 链接(SDK 默认就是 lld,保持一致)
-DBUILD_SHARED_LIBS=OFF LLVM 各库编成静态库,产物主要是 clang/lld 等可执行文件,减少链接麻烦
-DLLVM_ENABLE_RTTI=ON 打开 RTTI(LLVM 默认关;某些下游/插件需要)
-DLLVM_INCLUDE_TESTS=OFF 关掉测试、示例,省大量时间

4. 安装后记得补签名

链接器其实是自动签名的:SDK 的 ld.lld 是个 wrapper,内部调华为魔改过的 lld 并带 --code-sign

#!/bin/sh
exec -a "$0" /path/to/native/llvm/bin/lld --code-sign "$@"

所以 build_22_ohos/bin 里的 clang-22 刚链接出来是带签名的(因为 LLVM_USE_LINKER=lld 走的就是这个 wrapper)。但 install 阶段会对二进制做处理(比如 strip),把签名剥掉了,装到 Installed/clang-22/bin 后反而不能跑(Permission denied)。

验证方法:

binary-sign-tool display-sign -inFile <file>
# 有签名:code signature is self-sign
# 没签名:code signature is not found

补签即可:

binary-sign-tool sign -selfSign 1 -inFile clang-22 -outFile clang-22

小提示:clang-22 装好之后,它自带的 lldllvm-arllvm-ranlibllvm-stripclang-format同样没签名、不能执行。后续要用这些工具(比如构建运行时库)时,直接用 SDK 里已签名的同名工具。


下篇 · 构建 libunwind / compiler-rt / libcxxabi / libcxx,并跑通 import std;

上篇的 clang-22 已经能编译,但每次都要手动带 --target--sysroot-resource-dir-L,而且用的还是 SDK 的 libc++ 15。下篇把它补齐,最终零参数编译。

一、先搞清楚几个「反直觉」的鸿蒙事实

1. 签名其实是链接器自动完成的

见上篇第 4 节。因为我们编 clang 时给了 -DCLANG_DEFAULT_LINKER=lld,clang-22 链接时会从 PATH 找到 SDK 的 ld.lld wrapper,所以用 clang-22 编出来的可执行文件和 .so 都自动带签名,不需要再手动 binary-sign-tool

两个坑:

  • llvm-strip 会把签名剥掉(实测:strip 后 display-sign 报 “code signature is not found”,运行报 Permission denied)。
  • clang-22 自带的 lld/llvm-ar/llvm-ranlib/llvm-strip 本身没签名不能执行,构建时要用 SDK 里已签名的。

2. OHOS 在 clang 驱动里是「一等公民」

clang/lib/Driver/ToolChains/OHOS.cpp 写死了:

  • 默认就是 -stdlib=libc++-rtlib=compiler-rt(传别的值会直接报错);
  • 链接时总是-lc++ -lc++abi -lunwind -l:libunwind.a -lm -lc(连 C 程序也一样,因为要静态链 libunwind.a);
  • libc++ 头文件在 $prefix/include/<multiarch>/c++/v1 找,库在 $prefix/lib/<multiarch> 找,其中 multiarch = aarch64-linux-ohos
  • 动态链接器是 /lib/ld-musl-aarch64.so.1(musl)。

所以 vanilla libc++ 默认装到 $prefix/include/c++/v1$prefix/lib驱动反而找不到,要软链对齐(见下)。

3. compiler-rt 的 crt 只在系统名匹配 Linux 时才构建

compiler-rt/cmake/crt-config-ix.cmake

if (CRT_SUPPORTED_ARCH AND OS_NAME MATCHES "Linux|SerenityOS" AND NOT LLVM_USE_SANITIZER)
  set(COMPILER_RT_HAS_CRT TRUE)

LLVM 对 OHOS 无处理,用 CMAKE_SYSTEM_NAME=OHOSclang_rt.crtbegin.o/clang_rt.crtend.o 根本不会生成——这正是上篇 clang-22 资源目录缺 crt 的原因。对策:自己写工具链时把 CMAKE_SYSTEM_NAME 设成 Linux(编译器 target triple 仍是 aarch64-linux-ohos__OHOS__/__linux__ 照常定义)。

4. triple 会被「归一化」,目录名对不上

aarch64-linux-ohosclang -print-target-triple 会变成 aarch64-unknown-linux-ohos。compiler-rt 安装时用归一化后的名字做子目录,而 OHOS 驱动 multiarch 查找用的是 aarch64-linux-ohos。所以资源目录里软链:

lib/clang/22/lib/aarch64-linux-ohos -> aarch64-unknown-linux-ohos

5. 引导期 clang-22 连「编译器能不能用」都测不过

因为第 2 点里 OHOS 驱动总是链接 -l:libunwind.a 等,而刚装好的 clang-22 既没 crtbegin/crtend,也没 libunwind/libc++。CMake 在 project() 阶段做「检查编译器是否可用」这个链接测试就直接挂掉。所以得先用 SDK 里的成品把 clang-22「喂饱」。

二、准备:预置资源目录、软链

记两个路径:

SDK=/storage/Users/currentUser/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/native
PREFIX=/storage/Users/currentUser/Installed/clang-22

第一步,把 SDK 的 crt 和 builtins 拷进 clang-22 资源目录(马上会被自己编的 compiler-rt 覆盖,只是先让链接能通过),并建 triple 软链:

mkdir -p "$PREFIX/lib/clang/22/lib/aarch64-unknown-linux-ohos"
cp "$SDK/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos/clang_rt.crtbegin.o" \
   "$PREFIX/lib/clang/22/lib/aarch64-unknown-linux-ohos/"
cp "$SDK/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos/clang_rt.crtend.o" \
   "$PREFIX/lib/clang/22/lib/aarch64-unknown-linux-ohos/"
cp "$SDK/llvm/lib/clang/15.0.4/lib/aarch64-linux-ohos/libclang_rt.builtins.a" \
   "$PREFIX/lib/clang/22/lib/aarch64-unknown-linux-ohos/"
ln -sfn aarch64-unknown-linux-ohos "$PREFIX/lib/clang/22/lib/aarch64-linux-ohos"

第二步,把 SDK 的 libc++/libunwind 成品和头文件拷进 clang-22 的 prefix,并建好 OHOS 驱动期望的软链:

mkdir -p "$PREFIX/lib" "$PREFIX/include/c++"
cp "$SDK/llvm/lib/aarch64-linux-ohos"/{libunwind.a,libc++abi.a,libc++.so,libc++.a,libc++_shared.so,libc++_static.a,libc++experimental.a} \
   "$PREFIX/lib/"
cp -a "$SDK/llvm/include/libcxx-ohos/include/c++/v1" "$PREFIX/include/c++/v1"
ln -sfn . "$PREFIX/lib/aarch64-linux-ohos"        # $prefix/lib/aarch64-linux-ohos -> $prefix/lib
ln -sfn . "$PREFIX/include/aarch64-linux-ohos"    # $prefix/include/aarch64-linux-ohos -> $prefix/include

此时 clang-22 应该能只用 --sysroot 就链接 C++ 程序了。

三、自定义工具链文件

不用 ohos-sdk 的 ohos.toolchain.cmake:它把 CMAKE_C_COMPILER 写死成 SDK 的 clang 15(编不了 libc++ 22),而且会注入 -Wl,--no-undefined--rtlib=compiler-rt-lunwind 这些对「编译运行时库自身」有害的 flag。

新建 build_runtimes_22_ohos/ohos_runtimes.toolchain.cmake

# 为 aarch64-linux-ohos 构建 LLVM 运行时库(compiler-rt/libunwind/libcxxabi/libcxx)
set(OHOS_SDK_NATIVE "/storage/Users/currentUser/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/native")
set(CLANG22_PREFIX "/storage/Users/currentUser/Installed/clang-22")

# 关键:用 Linux 让 compiler-rt 走它的 crt 构建路径;编译器的 target triple 仍是 aarch64-linux-ohos
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_SYSTEM_VERSION 1)

set(CMAKE_SYSROOT "${OHOS_SDK_NATIVE}/sysroot")

set(CMAKE_C_COMPILER   "${CLANG22_PREFIX}/bin/clang")
set(CMAKE_CXX_COMPILER "${CLANG22_PREFIX}/bin/clang++")
set(CMAKE_ASM_COMPILER "${CLANG22_PREFIX}/bin/clang")

set(CMAKE_C_COMPILER_TARGET   aarch64-linux-ohos)
set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-ohos)
set(CMAKE_ASM_COMPILER_TARGET aarch64-linux-ohos)

# clang-22 自带的 binutils 没签名不能跑,用 SDK 里已签名的
set(CMAKE_AR      "${OHOS_SDK_NATIVE}/llvm/bin/llvm-ar"      CACHE FILEPATH "Archiver")
set(CMAKE_RANLIB  "${OHOS_SDK_NATIVE}/llvm/bin/llvm-ranlib"  CACHE FILEPATH "Ranlib")
set(CMAKE_NM      "${OHOS_SDK_NATIVE}/llvm/bin/llvm-nm"      CACHE FILEPATH "nm")
set(CMAKE_STRIP   "${OHOS_SDK_NATIVE}/llvm/bin/llvm-strip"   CACHE FILEPATH "strip")
set(CMAKE_OBJDUMP "${OHOS_SDK_NATIVE}/llvm/bin/llvm-objdump" CACHE FILEPATH "objdump")

# 对齐 SDK 驱动注入的宏(OHOS 用 musl)
set(CMAKE_C_FLAGS   "-D__MUSL__" CACHE STRING "")
set(CMAKE_CXX_FLAGS "-D__MUSL__" CACHE STRING "")
set(CMAKE_ASM_FLAGS "-D__MUSL__" CACHE STRING "")

# 没有 libgcc,链接走 compiler-rt + lld
set(CMAKE_EXE_LINKER_FLAGS     "-fuse-ld=lld -rtlib=compiler-rt" CACHE STRING "")
set(CMAKE_SHARED_LINKER_FLAGS  "-fuse-ld=lld -rtlib=compiler-rt" CACHE STRING "")
set(CMAKE_MODULE_LINKER_FLAGS  "-fuse-ld=lld -rtlib=compiler-rt" CACHE STRING "")

四、Phase A:先编 compiler-rt(builtins + crt)

compiler-rt 必须单独先编,因为 libc++/libc++abi/libunwind 链接 .so 时要用到它产出的 crt 和 builtins(而它们在 runtimes 构建里排在最后,一起编会鸡生蛋)。

cd /storage/Users/currentUser/ProjectSources/llvm-project

cmake -S runtimes -B build_runtimes_22_ohos/crt -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE="$PWD/build_runtimes_22_ohos/ohos_runtimes.toolchain.cmake" \
  -DLLVM_ENABLE_RUNTIMES=compiler-rt \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX=/storage/Users/currentUser/Installed/clang-22 \
  -DLLVM_ENABLE_ASSERTIONS=OFF \
  -DLLVM_INCLUDE_TESTS=OFF \
  -DLLVM_INCLUDE_DOCS=OFF \
  -DLLVM_ENABLE_ZLIB=OFF \
  -DLLVM_ENABLE_ZSTD=OFF \
  -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON \
  -DCOMPILER_RT_INSTALL_PATH=lib/clang/22 \
  -DCOMPILER_RT_BUILD_SANITIZERS=OFF \
  -DCOMPILER_RT_BUILD_XRAY=OFF \
  -DCOMPILER_RT_BUILD_LIBFUZZER=OFF \
  -DCOMPILER_RT_BUILD_PROFILE=OFF \
  -DCOMPILER_RT_BUILD_MEMPROF=OFF \
  -DCOMPILER_RT_BUILD_ORC=OFF \
  -DCOMPILER_RT_BUILD_GWP_ASAN=OFF \
  -DCOMPILER_RT_BUILD_CTX_PROFILE=OFF \
  -DCOMPILER_RT_BUILD_CRT=ON \
  -DCOMPILER_RT_BUILD_BUILTINS=ON

cmake --build build_runtimes_22_ohos/crt --target install -j4

关键参数:

  • -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON + -DCOMPILER_RT_INSTALL_PATH=lib/clang/22:让 builtins/crt 装到 $prefix/lib/clang/22/lib/<triple>/ 这个资源目录里(而不是默认的 lib/linux/),这正是 clang 驱动找它们的标准位置。
  • -DCOMPILER_RT_BUILD_CTX_PROFILE=OFF必须的:该组件默认开启,会连带编译 sanitizer_common,而它会在鸿蒙 sysroot 撞上 struct sysinfo 重复定义(linux/sysinfo.hsys/sysinfo.h 各定义一次)。我们只要 builtins + crt,把 sanitizer 全家桶都关掉即可。
  • -DCOMPILER_RT_BUILD_CRT=ON:显式打开 crt(前面 CMAKE_SYSTEM_NAME=Linux 已让它默认开)。

小提醒:若这个 build 目录之前 configure 过,COMPILER_RT_INSTALL_LIBRARY_DIR 会残留在缓存导致装错地方,建议 rm -rf build_runtimes_22_ohos/crt 后重新 configure。

编完确认资源目录里有:

lib/clang/22/lib/aarch64-unknown-linux-ohos/clang_rt.crtbegin.o
lib/clang/22/lib/aarch64-unknown-linux-ohos/clang_rt.crtend.o
lib/clang/22/lib/aarch64-unknown-linux-ohos/libclang_rt.builtins.a

五、Phase B:libunwind + libcxxabi + libcxx

cd /storage/Users/currentUser/ProjectSources/llvm-project

cmake -S runtimes -B build_runtimes_22_ohos/libcxx -G Ninja \
  -DCMAKE_TOOLCHAIN_FILE="$PWD/build_runtimes_22_ohos/ohos_runtimes.toolchain.cmake" \
  -DLLVM_ENABLE_RUNTIMES="libunwind;libcxxabi;libcxx" \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX=/storage/Users/currentUser/Installed/clang-22 \
  -DLLVM_ENABLE_ASSERTIONS=OFF \
  -DLLVM_INCLUDE_TESTS=OFF \
  -DLLVM_INCLUDE_DOCS=OFF \
  -DLLVM_ENABLE_ZLIB=OFF \
  -DLLVM_ENABLE_ZSTD=OFF \
  -DLIBCXX_USE_COMPILER_RT=ON \
  -DLIBCXXABI_USE_COMPILER_RT=ON \
  -DLIBUNWIND_USE_COMPILER_RT=ON \
  -DLIBCXXABI_USE_LLVM_UNWINDER=ON \
  -DLIBCXX_CXX_ABI=libcxxabi \
  -DLIBCXX_HAS_MUSL_LIBC=ON \
  -DLIBCXX_ENABLE_SHARED=ON \
  -DLIBCXX_ENABLE_STATIC=ON \
  -DLIBCXXABI_ENABLE_SHARED=ON \
  -DLIBCXXABI_ENABLE_STATIC=ON \
  -DLIBUNWIND_ENABLE_SHARED=ON \
  -DLIBUNWIND_ENABLE_STATIC=ON \
  -DLIBCXX_ENABLE_TIME_ZONE_DATABASE=OFF \
  -DLIBCXX_ENABLE_EXCEPTIONS=ON \
  -DLIBCXX_ENABLE_RTTI=ON

cmake --build build_runtimes_22_ohos/libcxx --target install -j4

关键参数:

  • -DLIBCXX_CXX_ABI=libcxxabi-DLIBCXXABI_USE_LLVM_UNWINDER=ON:把 libc++ 的 ABI 库和 unwinder 串起来(libc++ → libc++abi → libunwind)。
  • -DLIBCXX_HAS_MUSL_LIBC=ON很重要。鸿蒙用 musl,libc++ 22 靠这个宏启用 musl 支持(影响 locale、<filesystem>、mbstate 等)。
  • -DLIBCXX_USE_COMPILER_RT=ON 等三个:让这几个运行时链接时用 compiler-rt 而不是 libgcc(OHOS 没有 libgcc)。
  • -DLIBCXX_ENABLE_TIME_ZONE_DATABASE=OFF:OHOS 没有 IANA tzdb,关掉 <chrono> 的时区数据库,走系统 TZ 环境变量那条路。
  • 关掉 LLVM_INCLUDE_TESTS/DOCSZLIB/ZSTD,避免额外依赖和测试。

安装时的一个坑:libc++ 22 把头文件从「单文件」重构成了「目录」形式(如 __tuple 从文件变目录),如果引导时拷进去的 SDK libc++ 15 旧头文件还在,install 会报 __tuple is not a directory。装 libc++ 22 之前先删掉旧的:

rm -rf "$PREFIX/include/c++/v1"          # 旧的 SDK libc++ 15 头文件
rm -f  "$PREFIX/lib"/libc++.so "$PREFIX/lib"/libc++.a \
       "$PREFIX/lib"/libc++_shared.so "$PREFIX/lib"/libc++_static.a \
       "$PREFIX/lib"/libc++experimental.a
cmake --build build_runtimes_22_ohos/libcxx --target install -j4

装完 $prefix/lib 下应有(共享库已被链接器自动签名):

libc++.so.1.0      libc++.so.1 -> libc++.so.1.0      libc++.so(链接脚本 INPUT(libc++.so.1 -lc++abi -lunwind))
libc++abi.so.1.0   libc++abi.so.1 -> ...             libc++abi.so -> ...
libunwind.so.1.0   libunwind.so.1 -> ...             libunwind.so -> ...
以及对应的 .a 静态库

头文件在 $prefix/include/c++/v1,模块源码在 $prefix/share/libc++/v1/std.cppm + std/*.inc)。

六、让 clang-22 「零参数」编译:config 文件

引导时已建好两个软链对齐 OHOS 驱动查找约定:

$prefix/include/aarch64-linux-ohos -> .
$prefix/lib/aarch64-linux-ohos     -> .

现在只差三样没默认:--sysroot-D__MUSL__、运行时的库搜索路径(rpath)。用 clang 的 config 文件自动注入。在 $prefix/bin/clang.cfgclang++.cfg(clang 会按 driver 模式自动加载同名 .cfg):

--sysroot=/storage/Users/currentUser/.harmonybrew/Cellar/ohos-sdk/26.0.0.18_1/native/sysroot
-D__MUSL__
-Wl,-rpath,/storage/Users/currentUser/Installed/clang-22/lib
  • -Wl,-rpath,...$prefix/lib 写进 RUNPATH,运行时不用再设 LD_LIBRARY_PATH 就能找到 libc++.so.1 等。
  • -stdlib=libc++-rtlib=compiler-rt--target=aarch64-linux-ohos 都不用写——OHOS 驱动本来就默认如此。

验证:

~/Installed/clang-22/bin/clang++ -std=c++23 hello.cpp -o hello && ./hello

七、跑通 import std;(C++23 模块)

libc++ 22 安装时已把标准库模块源码放在 $prefix/share/libc++/v1/。最小程序:

// cpp23.cpp
import std;
auto main() -> int {
    std::println("你好,世界!");
}

三步:

cd ~/tmp
PREFIX=~/Installed/clang-22

# 1) 编译 std 模块接口 -> std.pcm(BMIs,约 33 MB)
$PREFIX/bin/clang++ -std=c++23 -Wno-reserved-module-identifier \
    --precompile $PREFIX/share/libc++/v1/std.cppm -o std.pcm

# 2) 用模块编译你的程序
$PREFIX/bin/clang++ -std=c++23 -fmodule-file=std=std.pcm cpp23.cpp -o cpp23

# 3) 直接运行(已被链接器自动签名)
./cpp23

输出:

你好,世界!

说明:

  • -Wno-reserved-module-identifier 只是压掉 module std; 用了保留标识符的告警。
  • std.cppm 里的 #include "std/cfenv.inc" 是相对路径引用,自动在 share/libc++/v1/std/ 找到,无需额外 -I
  • .pcm 是编译器内部模块缓存(BMI),不需要签名;只有最终可执行文件(由 lld 链接,已自动签名)需要。
  • 复用时务必让「模块编译」与「程序编译」的 flag 一致(-std=c++23、target、-D__MUSL__ 等),否则报 module file ... configuration mismatch
  • std.cppm 比较吃内存(实例化几乎整个标准库模板),内存紧张的话耐心等。

八、踩坑清单速查

现象 原因 解法
配置阶段 unable to guess system type config.guess 不认识 HarmonyOS 且被沙箱挡住临时文件 硬编码 GetHostTriple.cmakeaarch64-unknown-linux-ohos
链接报 undefined symbol--no-undefined 工具链强注入 -Wl,--no-undefined -DOHOS_ALLOW_UNDEFINED_SYMBOLS=ON 或删 build.ninja 里的 flag
Permission denied / Operation not permitted 运行二进制 二进制没签名(或被 strip 掉了签名) 用 SDK 的 ld.lld 链接会自动签;被 strip 后手动 binary-sign-tool sign -selfSign 1
链接报 unable to find library -l:libunwind.a OHOS 驱动总强链静态 libunwind 引导期先拷一份 SDK 的 libunwind/libc++ 进 prefix
链接报 cannot open crtbeginS.o / 找不到 builtins clang-22 资源目录缺 crt,builtins 目录名对不上 编 compiler-rt 打开 crt;软链 aarch64-linux-ohos -> aarch64-unknown-linux-ohos
compiler-rt 编译报 redefinition of 'sysinfo' sanitizer_common 撞上鸿蒙 sysroot 两个同名结构体 -DCOMPILER_RT_BUILD_CTX_PROFILE=OFF + 关 sanitizer 全家桶
compiler-rt 装到 lib/linux/ 而非资源目录 LLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON + COMPILER_RT_INSTALL_PATH=lib/clang/22 加这两个参数(注意清旧缓存)
libc++ 头文件 install 报 __tuple is not a directory 引导用的 SDK libc++ 15 旧头文件与新目录结构冲突 装 libc++ 22 前删掉旧的 include/c++/v1
编译后能链接但运行找不到 libc++.so.1 运行时库不在默认搜索路径 链接时加 -Wl,-rpath,$prefix/lib(或设 LD_LIBRARY_PATH
跑起来用了 SDK 的 libc++ 15 头/库路径没对齐 OHOS 驱动的 multiarch 约定 软链 include/aarch64-linux-ohos -> .lib/aarch64-linux-ohos -> .

九、写在最后

整个过程最花时间的不是编译,而是先把「鸿蒙 / LLVM 之间没对齐的地方」摸清楚:host triple 探测、OHOS 驱动的默认行为、compiler-rt 对系统名的判断、triple 归一化、签名机制、以及引导期的鸡生蛋问题。一旦理顺,构建本身反而很顺——两条 cmake 命令 + 几个软链 + 一个 config 文件就搞定了。

希望这篇记录能帮你少走弯路。后续如果想继续折腾 sanitizer 全家桶、std.compat 模块等,思路相通:先对齐 OHOS 驱动和 CMake 的路径/系统名约定,再按 runtime 分层逐个推进。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐