本文主要描述如何通过 conan 1.x 交叉编译常见的三方库到鸿蒙平台,基础的环境配置及工程化集成配置,以及在编译过程中遇到过的问题。

在现有业务中,我们已经有了一些成熟的跨平台项目在运行,支持 Android、iOS、Windows、macOS、Linux like 系统,这些项目各自都依赖了大量的三方开源代码,如常见的 C 库 zlib、openssl、libcurl、libuv、sqlite3,C++ 库 jsoncpp、gtest 等。我们不可能为每个系统去单独维护编译脚本或产物,因为这个过程真的相当痛苦,你不仅需要了解不同的工程管理工具如 gn、cmake、autotools、makefile 等,还需要了解各个三方库的各类编译配置,做跨平台开发的同学一定能深有体会。

幸运的是我们已经在现有平台下通过 conan 全自动管理三方库产物,不需要刻意关心三方库的编译细节,只需要通过 conan 指定一个版本号让其自动帮我们编译即可。本文也将围绕这个话题,描述如何通过 conan 交叉编译常见的三方库到鸿蒙平台。通过本文你可以了解到:

  • 如何快速编译一个鸿蒙的三方库(静态或动态)
  • 如何配置驱动现有使用 conan 1.x 的项目支持鸿蒙系统
  • 有哪些已经踩过的坑
  • 写好的 Dockerfile 可直接创建一个带有 conan 的鸿蒙交叉编译环境用于 CI 集成

其中的优势在于:

  • 除特别需要不需要修改 conan 或三方库代码
  • 使用系统已有的环境如 cmake、conan 等,无需使用工具链中定制的程序

因为历史债务问题,我们虽然已经切换了部分项目到 conan 2.x,但还有一部分项目依然在使用 conan 1.x,的确是因为切换成本较高且有些项目上的定制功能尚未在 conan 2.x 中找到好的解决方案,所以通过 conan 1.x 交叉编译到鸿蒙也是目前我们必须要考虑的重点。

步骤演示

官方提供了多个平台的交叉编译工具链,我们这里以 Linux 平台交叉编译到鸿蒙举例,因为后续 CI 自动化集成使用 Linux 更方便我们做自动化部署。

配置交叉编译环境

首先登录华为开发者中心,下载完整的工具链压缩包:https://developer.huawei.com/consumer/cn/download/command-line-tools-for-hmos,下载时选 for HarmonyOS 的版本。

由于这个压缩包过于庞大,一些与交叉编译工具链无关的工具,我们在解压时可以过滤掉,真正需要的只有 command-line-tools/sdk/default/openharmony/native 目录下的文件:

使用 unzip 解压可以控制只解压该目录下的文件:

1
unzip commandline-tools-linux-x64-5.0.13.230.zip "command-line-tools/sdk/default/openharmony/native/*"

接下来我们要配置环境变量,让项目工程可以识别使用鸿蒙的工具链进行初始化工程及编译,假设你的工具链目录解压到了 ~/Downloads/command-line-tools 目录下:

设置当前终端环境变量(不污染整个系统)

1
2
3
4
5
6
7
8
9
10
export CC=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang
export CXX=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++
export CMAKE_C_COMPILER=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang
export CMAKE_CXX_COMPILER=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++
export STRIP=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-strip
export RANLIB=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ranlib
export AS=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ar
export AS=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-as
export AR=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ar
export LD=~/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/ld.lld

此时如果你在当前终端编译一个没有三方库依赖的代码,就已经可以使用鸿蒙的工具链了,我使用了一个简单的 CMake 项目做了测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cmake -Bbuild -DCMAKE_C_FLAGS="--target=aarch64-linux-ohos -D__MUSL__=1" -DCMAKE_CXX_FLAGS="--target=aarch64-linux-ohos -D__MUSL__=1"
-- The C compiler identification is Clang 15.0.4
-- The CXX compiler identification is Clang 15.0.4
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/yunxin/Downloads/openharmony-library/build

可以看到,C compiler 和 CXX compiler 都已经使用我们之前设置的鸿蒙的工具链了,其中 CMAKE_C_FLAGS 和 CMAKE_CXX_FLAGS 携带的两个参数分别表示:

  • --target=aarch64-linux-ohos 明确使用 aarch64-linux-ohos 工具链
  • -D__MUSL__=1 添加全局宏明确表示使用 musl libc 而不是 GNU libc,这是华为官网帮助文档中要求加入的

执行 cmake 的编译指令后,即可编译出产物:

1
2
3
4
$ cmake --build build --config Release
[ 50%] Building CXX object CMakeFiles/openharmony-library.dir/main.cc.o
[100%] Linking CXX shared library libopenharmony-library.so
[100%] Built target openharmony-library

使用 file 命令查看一下产物,也已经是 aarch64 架构了:

1
2
$ file build/libopenharmony-library.so
build/libopenharmony-library.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, with debug_info, not stripped

如果没有以上两个参数,会无法正确初始化 CMake 工程报错,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ cmake -Bbuild
-- The C compiler identification is Clang 15.0.4
-- The CXX compiler identification is Clang 15.0.4
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - failed
-- Check for working CXX compiler: /home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++
-- Check for working CXX compiler: /home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++ - broken
CMake Error at /usr/share/cmake-3.22/Modules/CMakeTestCXXCompiler.cmake:62 (message):
The C++ compiler

"/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++"

is not able to compile a simple test program.

It fails with the following output:

Change Dir: /home/yunxin/Downloads/openharmony-library/build/CMakeFiles/CMakeTmp

Run Build Command(s):/usr/bin/gmake -f Makefile cmTC_55ffc/fast && /usr/bin/gmake -f CMakeFiles/cmTC_55ffc.dir/build.make CMakeFiles/cmTC_55ffc.dir/build
gmake[1]: Entering directory '/home/yunxin/Downloads/openharmony-library/build/CMakeFiles/CMakeTmp'
Building CXX object CMakeFiles/cmTC_55ffc.dir/testCXXCompiler.cxx.o
/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++ -MD -MT CMakeFiles/cmTC_55ffc.dir/testCXXCompiler.cxx.o -MF CMakeFiles/cmTC_55ffc.dir/testCXXCompiler.cxx.o.d -o CMakeFiles/cmTC_55ffc.dir/testCXXCompiler.cxx.o -c /home/yunxin/Downloads/openharmony-library/build/CMakeFiles/CMakeTmp/testCXXCompiler.cxx
Linking CXX executable cmTC_55ffc
/usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_55ffc.dir/link.txt --verbose=1
/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++ CMakeFiles/cmTC_55ffc.dir/testCXXCompiler.cxx.o -o cmTC_55ffc
/usr/bin/ld: cannot find -lstdc++: No such file or directory
clang-15: error: linker command failed with exit code 1 (use -v to see invocation)
gmake[1]: *** [CMakeFiles/cmTC_55ffc.dir/build.make:100: cmTC_55ffc] Error 1
gmake[1]: Leaving directory '/home/yunxin/Downloads/openharmony-library/build/CMakeFiles/CMakeTmp'
gmake: *** [Makefile:127: cmTC_55ffc/fast] Error 2

CMake will not be able to correctly generate this project.
Call Stack (most recent call first):
CMakeLists.txt:3 (project)

添加鸿蒙平台的 conan profile

如果想让 conan 也使用这套配置,我们还需要新增一个针对鸿蒙工具链的 profile 配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[settings]
os=Linux
arch=armv8
compiler=clang
compiler.version=15
compiler.libcxx=c++_static
compiler.cppstd=11
[options]
[build_requires]
[conf]
[env]
CC=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang
CXX=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++
CMAKE_C_COMPILER=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang
CMAKE_CXX_COMPILER=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++
STRIP=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-strip
RANLIB=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ranlib
AS=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-as
AR=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ar
LD=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/ld.lld
CFLAGS="--target=aarch64-linux-ohos -D__MUSL__=1"
CXXFLAGS="--target=aarch64-linux-ohos -D__MUSL__=1"

参数解释:

  • os=Linux 表示让产物还是以 Linux like 方式编译,因为鸿蒙的交叉编译工具链还是基于 Linux like 改进
  • arch=armv8 编译为 armv8 架构的,这个参数还是要给,虽然重要性不大,因为我们在工具链中设置了 —target 参数
  • compiler=clang 表示使用 clang,conan 会做校验
  • compiler.version=15 表示使用 clang 15 版本,版本号是我从鸿蒙工具链中获取的,如果这里给的不对 conan 会给警告
  • compiler.libcxx=c++_static 表示静态链接 C++ 标准库,这个类似 NDK,给 c++_shared 表示动态链接系统库
  • compiler.cppstd=11 表示如果被编译目标是 C++ 代码,则以 C++ 11 作为标准编译
  • [env] 中所有参数就是工具链的目录,其中比较重要的是 CFLAGS 和 CXXFLAGS 明确使用哪种架构和添加全局宏

将以上内容保存为名为 openharmony-arm64-v8a-clang15 的文件,然后尝试使用 conan 编译一个三方库 openssl 1.1.1w 的版本,执行命令后输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
$ conan install openssl/1.1.1w@ -pr:b=default -pr:h=./openharmony-arm64-v8a-clang15 --build missing

WARN: **************************************************
WARN: *** Conan 1 is legacy and on a deprecation path **
WARN: *********** Please upgrade to Conan 2 ************
WARN: **************************************************
Configuration (profile_host):
[settings]
arch=armv8
compiler=clang
compiler.cppstd=11
compiler.libcxx=c++_static
compiler.version=15
os=Linux
[options]
[build_requires]
[env]
AR=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ar
AS=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-as
CC=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang
CFLAGS=--target=aarch64-linux-ohos -D__MUSL__=1
CMAKE_CXX_COMPILER=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++
CMAKE_C_COMPILER=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang
CXX=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++
CXXFLAGS=--target=aarch64-linux-ohos -D__MUSL__=1
LD=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/ld.lld
RANLIB=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ranlib
STRIP=/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-strip
Configuration (profile_build):
[settings]
arch=x86_64
arch_build=x86_64
build_type=Release
compiler=gcc
compiler.cppstd=17
compiler.libcxx=libstdc++11
compiler.version=11
os=Linux
os_build=Linux
[options]
[build_requires]
[env]
openssl/1.1.1w: Not found in local cache, looking in remotes...
openssl/1.1.1w: Trying with 'conancenter'...
Downloading conanmanifest.txt completed [0.18k]
Downloading conanfile.py completed [27.92k]
Downloading conan_export.tgz completed [0.42k]
Decompressing conan_export.tgz completed [0.00k]
openssl/1.1.1w: Downloaded recipe revision a8f0792d7c5121b954578a7149d23e03
Installing package: openssl/1.1.1w
Requirements
openssl/1.1.1w from 'conancenter' - Downloaded
Packages
openssl/1.1.1w:5cd37d28260aadc1d02b4d029448db7bbf9e1e04 - Build

Cross-build from 'Linux:x86_64' to 'Linux:armv8'
Installing (downloading, building) binaries...
Downloading conan_sources.tgz completed [0.49k]
Decompressing conan_sources.tgz completed [0.00k]
openssl/1.1.1w: Configuring sources in /home/yunxin/.conan/data/openssl/1.1.1w/_/_/source/src
Downloading openssl-1.1.1w.tar.gz completed [9661.51k] openssl/1.1.1w: enssl/1.1.1w:
openssl/1.1.1w:
openssl/1.1.1w: Copying sources to build folder
openssl/1.1.1w: Building your package in /home/yunxin/.conan/data/openssl/1.1.1w/_/_/build/5cd37d28260aadc1d02b4d029448db7bbf9e1e04
openssl/1.1.1w: Generator txt created conanbuildinfo.txt
openssl/1.1.1w: Calling generate()
openssl/1.1.1w: Aggregating env generators
openssl/1.1.1w: Calling build()
openssl/1.1.1w: Apply patch (portability): TVOS and WatchOS don't like fork()
openssl/1.1.1w: gen_info = {'CFLAGS': ['-fPIC'], 'CXXFLAGS': ['-fPIC'], 'DEFINES': [], 'LDFLAGS': []}
openssl/1.1.1w: using target: conan-None-Linux-armv8-clang-15 -> linux-aarch64
openssl/1.1.1w: my %targets = (
"conan-None-Linux-armv8-clang-15" => {
inherit_from => [ "linux-aarch64" ],
cflags => add("-fPIC"),
cxxflags => add("-fPIC"),

includes => add(),
lflags => add(""),



cc => "/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang",
cxx => "/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang++",
ar => "/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ar",
ranlib => "/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/llvm-ranlib",

},
);

openssl/1.1.1w: ['"conan-None-Linux-armv8-clang-15"', 'no-shared', '--prefix=/', '--openssldir="/etc/ssl"', 'no-unit-test', 'threads', 'PERL=perl', 'no-tests', '--release', '--libdir=lib', '-fPIC', 'no-md2']
Configuring OpenSSL version 1.1.1w (0x1010117fL) for conan-None-Linux-armv8-clang-15
Using os-specific seed configuration
Creating configdata.pm
Creating Makefile

**********************************************************************
*** ***
*** OpenSSL has been successfully configured ***
*** ***
*** If you encounter a problem while building, please open an ***
*** issue on GitHub <https://github.com/openssl/openssl/issues> ***
*** and include the output from the following command: ***
*** ***
*** perl configdata.pm --dump ***
*** ***
*** (If you are new to OpenSSL, you might want to consult the ***
*** 'Troubleshooting' section in the INSTALL file first) ***
*** ***
**********************************************************************
perl "-I." -Mconfigdata "util/dofile.pl" \
"-oMakefile" include/crypto/bn_conf.h.in > include/crypto/bn_conf.h
perl "-I." -Mconfigdata "util/dofile.pl" \
"-oMakefile" include/crypto/dso_conf.h.in > include/crypto/dso_conf.h
perl "-I." -Mconfigdata "util/dofile.pl" \
"-oMakefile" include/openssl/opensslconf.h.in > include/openssl/opensslconf.h
make depend && make _all
make[1]: Entering directory '/home/yunxin/.conan/data/openssl/1.1.1w/_/_/build/5cd37d28260aadc1d02b4d029448db7bbf9e1e04/src'
make[1]: Leaving directory '/home/yunxin/.conan/data/openssl/1.1.1w/_/_/build/5cd37d28260aadc1d02b4d029448db7bbf9e1e04/src'
make[1]: Entering directory '/home/yunxin/.conan/data/openssl/1.1.1w/_/_/build/5cd37d28260aadc1d02b4d029448db7bbf9e1e04/src'
/home/yunxin/Downloads/command-line-tools/sdk/default/openharmony/native/llvm/bin/clang -I. -Iinclude -fPIC -pthread -fPIC -Wa,--noexecstack -Qunused-arguments --target=aarch64-linux-ohos -D__MUSL__=1 -fPIC -fPIC -DOPENSSL_USE_NODELETE -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_BN_ASM_MONT -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DVPAES_ASM -DECP_NISTZ256_ASM -DPOLY1305_ASM -DOPENSSLDIR="\"/etc/ssl\"" -DENGINESDIR="\"//lib/engines-1.1\"" -DNDEBUG -MMD -MF apps/app_rand.d.tmp -MT apps/app_rand.o -c -o apps/app_rand.o apps/app_rand.c
……… 省略

可以看到编译 openssl 已经能正确识别到我们通过 conan profile 设置的工具链,命令相关参数解释:

  • -pr:b=default 如果在交叉编译中需要运行一下本地程序来对代码进行更改如应用 patch 等,需要使用本地环境而不是编译的目标环境,系统默认是 gcc
  • -pr:h=./openharmony-arm64-v8a-clang15 表示编译的目标环境使用这个配置
  • --build missing 表示本地如果没有或有依赖不存则,则直接重新编译

说白了这几个参数就是告诉 conan 需要从本地的 x86_64 环境交叉编译到鸿蒙的 arm64-v8a,同时给出了本地环境的工具链和目标环境的工具链路径、参数配置等信息。如果想进一步验证配置有效性,我建议使用 libcurl 进行测试,因为 libcurl 在编译目标产物时还需要在本地执行 m4、automake 等工具来生成一些编译配置,而且完整的 libcurl 编译也会自动依赖 openssl、zlib 等三方库。如果 libcurl 也可以通过,那么你的环境配置就没什么问题了,命令如下:

1
conan install libcurl/8.6.0@ -pr:b=default -pr:h=./openharmony-arm64-v8a-clang15 -s build_type=Release --build missing

多了个 -s build_type=Release 参数主要是一些三方库要明确指定编译的类型,如 zlib

工程化集成

为了方便 CI 集成,我们将以上步骤封装为一个携带了 conan + 鸿蒙工具链的 Dockerfile,如果你需要快速创建一个鸿蒙的交叉编译环境,可以考虑使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
FROM conanio/gcc9-ubuntu18.04:latest

LABEL maintainer="Dylan <2894220@gmail.com>"

# Set global arguments
ARG CONAN_VERSION=1.66.0
ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE=3.31.6
ARG TOOLCHAIN_URL
ARG TOOLCHAIN_PREFIX=command-line-tools/sdk/default/openharmony/native/llvm/bin

# Copy custom sources.list to use faster mirrors
COPY sources.list /etc/apt/sources.list
RUN sudo apt-get update
RUN sudo apt-get install -q -y zip vim

# Reinstall conan and cmake to specific versions
RUN sudo pip3 uninstall conan -y
RUN sudo pip3 install conan==${CONAN_VERSION}
RUN conan profile new default --detect --force
RUN sudo pip3 uninstall cmake && pip3 install cmake==3.31.6
RUN sudo ln -sf /opt/pyenv/shims/cmake /usr/bin

# Download and unpack the toolchain
RUN sudo curl -L -s ${TOOLCHAIN_URL} -o command-line-tools.zip
RUN unzip command-line-tools.zip "command-line-tools/sdk/default/openharmony/native/*"

# Write toolchain paths to /etc/environment for system-wide access
RUN sudo chmod 777 /etc/environment
RUN sudo echo >> /etc/environment
RUN sudo echo CC=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang >> /etc/environment
RUN sudo echo CXX=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang++ >> /etc/environment
RUN sudo echo CMAKE_C_COMPILER=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang >> /etc/environment
RUN sudo echo CMAKE_CXX_COMPILER=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang++ >> /etc/environment
RUN sudo echo STRIP=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-strip >> /etc/environment
RUN sudo echo RANLIB=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-ranlib >> /etc/environment
RUN sudo echo AS=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-as >> /etc/environment
RUN sudo echo AR=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-ar >> /etc/environment
RUN sudo echo LD=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/ld.lld >> /etc/environment

# Set environment variables for the current session
ENV CC=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang
ENV CXX=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang++
ENV CMAKE_C_COMPILER=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang
ENV CMAKE_CXX_COMPILER=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/clang++
ENV STRIP=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-strip
ENV RANLIB=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-ranlib
ENV AS=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-as
ENV AR=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/llvm-ar
ENV LD=${CONAN_USER_HOME}/${TOOLCHAIN_PREFIX}/ld.lld

编译镜像需要至少指定一个 TOOLCHAIN_URL 的参数,该参数是鸿蒙交叉编译工具链的下载地址,因为华为官方的地址需要鉴权,所以我们下载后备份到了自己的服务器上,您也可以上传到自己的内网服务器上来测试用:

1
2
docker build -t openharmony-toolchain . --build-arg \
TOOLCHAIN_URL=https://yourdomain.com/commandline-tools-linux-x64-5.0.13.230.zip

编译完成后可以直接基于镜像运行容器:

1
2
3
docker run -it \
--name openharmony-toolchain-1 \
--entrypoint /bin/bash openharmony-toolchain

运行后就进入了包含 conan 和鸿蒙交叉编译工具链的环境了,您可以直接在该环境下编译鸿蒙的项目,您可以在镜像中集成 jenkins agent 或 gitlab runner 作为一个 CI 环境直接使用。

已知问题

在编译业务代码时,我们碰到了一些小问题,如链接不到 pthread_cancel 函数,虽然编译能过,但是链接失败,这是因为 musl 没有导出该方法,推荐使用 pthread_kill 实现,在业务代码中,可通过宏 __OHOS__ 来判断是否是使用鸿蒙工具链编译的产物,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#if defined(ANDROID) || defined(__OHOS__)
/* pthread_cancel() will not be supported in Bionic,
* because doing this would involve making the C library
* significantly bigger for very little benefit.
* [...] All of this is contrary to the Bionic design goals.
* If your code depends on thread cancellation, please consider alternatives.
*/
if ((ret = pthread_kill(*((pthread_t*)tid), SIGUSR1)) != 0) {
printf("Error cancelling thread %ld, error = %d (%s)", *((pthread_t*)tid), ret, strerror(ret));
}
#else
pthread_cancel(*((pthread_t*)tid));
#endif

另外因为业务代码有依赖 libuv,在使用 conan 依赖 libuv 时同样会出现链接 pthread_setaffinity_np 失败的情况,你可以切换到直接链接系统的 libuv 而不是使用 conan 依赖,鸿蒙工具链中携带了预编译好的 uv 库,还能进一步减少产物体积。

如果你就是不想用系统的 libuv,希望独立管理 libuv 代码,你需要关注一下 libuv 1.51.x 版本针对 OHOS 的适配

1
2
3
4
5
6
7
8
#if defined(__ANDROID__) || defined(__OHOS__)
if (sched_setaffinity(pthread_gettid_np(*tid), sizeof(cpuset), &cpuset))
r = errno;
else
r = 0;
#else
r = pthread_setaffinity_np(*tid, sizeof(cpuset), &cpuset);
#endif

更重要的是,libuv 官方代码即使你设置了 -fvisibility=hidden 选项,其代码还是会默认导出 API,这会与系统默认携带的 libuv 冲突,运行时挂掉,这是我碰到的崩溃栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Module name:com.netease.nimsample
Version:2.0.0
VersionCode:1
PreInstalled:No
Foreground:Yes
Timestamp:2025-09-09 20:40:27.387
Pid:48309
Uid:20020197
Process name:com.netease.nimsample
Process life time:13s
Reason:Signal:SIGSYS(UNKNOWN)
Fault thread info:
Tid:48556, Name:TuanjieMain
#00 pc 00000000000a4bdc /system/lib/ld-musl-aarch64.so.1(f77c0346c0084ebbadf721ea319f5f77)
#01 pc 0000000000283648 /data/storage/el1/bundle/libs/arm64/libnim.so(uv__iou_init+200)
#02 pc 0000000000283554 /data/storage/el1/bundle/libs/arm64/libnim.so(uv__platform_loop_init+76)
#03 pc 000000000027a9d8 /data/storage/el1/bundle/libs/arm64/libnim.so(uv_loop_init+292)
#04 pc 00000000001b67b8 /data/storage/el1/bundle/libs/arm64/libnim.so(http_run_uv_loop+244)
#05 pc 000000000015553c /data/storage/el1/bundle/libs/arm64/libnim.so(nim_component_init+1028)
#06 pc 000000000011d150 /data/storage/el1/bundle/libs/arm64/libnim.so(nim_client_init+20)

你需要修改 libuv 的代码,根据配置决定是否导出符号,可以参考这个 PR(未合入官方代码):https://github.com/libuv/libuv/pull/4144