在鸿蒙 PC 上 `import std;` 拢共分几步?手把手带你在鸿蒙 PC 上编译 llvm-project
在鸿蒙 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; 实际上拢共这么几步:
- 安装 brew
- 用 brew 安装 deepseek-harness
- 让 deepseek-harness 帮你编译一个新版本的 llvm-project ,要带上 runtimes
- 用新版本的 clang 编译
std.cppm - 编译
import std;
上篇 · 构建 clang / clang-tools-extra / lld
0. 前置条件
- 已通过 Harmonybrew 安装
ohos-sdk、clang、cmake、ninja、make等工具。 - 记两个常用变量:
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:528:get_host_triple(LLVM_INFERRED_HOST_TRIPLE)runtimes/CMakeLists.txt:209:get_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
两个原因叠加:
uname -s返回HarmonyOS,config.guess不认识;- 它想通过「临时目录里编译一个探针程序」来判断系统,而沙箱拦住了临时文件创建(即便换成 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-ohos和aarch64-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 装好之后,它自带的
lld、llvm-ar、llvm-ranlib、llvm-strip、clang-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=OHOS 时 clang_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-ohos 经 clang -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.h与sys/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/DOCS、ZLIB/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.cfg 和 clang++.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.cmake 为 aarch64-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 分层逐个推进。
更多推荐




所有评论(0)