背景
HarmonyOS 应用开发也是支持ArkTS调用C++ / C的,这个机制用的是Node.js中的N-API。
HarmonyOS 编译C++/C时,用的是CMake, DevEco Studio 3.1.1 Release 对应的CMake版本是3.16.5。
这篇文章将介绍CmakeLists.txt的编写规则。
内容来源于CMake 3.16.9 版本官方文档
- cmake.org/cmake/help/…
- 这个文档中涉及到的代码,需要从github.com/Kitware/CMa… 仓库中下载源码,源码位置:Help/guide/tutorial
HarmonyOS SDK自带的官方文档
- Library/Huawei/Sdk/openharmony/9/native/build-tools/cmake/doc/cmake/html/guide/tutorial/index.html
实验代码
git clone https://github.com/ttroy50/cmake-examples.git
实验设备
MacBook Pro
编译命令
- cmake -S .
- cmake --build .
- ./xxx
以 A-hello-cmake 为例
- cd A-hello-cmake
- cmake -S .
- cmake --build .
- ./hello_cmake
命令行终端中输出:Hello CMake! 字样
以下为前三个阶段目录结构的变化说明
没执行任何命令前的目录结构
执行 cmake -S .
之后的结构
执行 cmake --build .
之后的结构
执行 ./hello_cmake
你会发现,所有编译产出都和源文件在同一级,接下来我们将所有输出指定到.cxx文件夹下边
使用上边的 A-hello-cmake 你可以尝试一下
cmake -S . -B .cxx
cmake --build .cxx
执行 cmake -S . -B .cxx
之后的结构
执行 cmake --build .cxx
之后的结构
以上编译过程,第一没有指定cmake, 第二没有指定cpu架构
cmake可以在电脑中存在多个,比如Android开发环境和HarmonyOS开发环境下都配置有CMake
由于我们要运行的库最终是在手机上,所以需要设置交叉式编译CPU架构,如 arm64-v8a
, armeabi-v7a
CMakeLists.txt 示例
HarmonyOS 源码移植编译中,最核心的是CMakeLists.txt文件的配置,这里将展示常用的语句编写规则。
1. 生成一个可执行文件
# $ cmake --version
# 设置本脚本支持的最小cmake版本,通过EevEco Studio 创建c++时,默认会自动生成好
cmake_minimum_required(VERSION 3.5)
# c++/c 工程的名称
# 这里也可以设置工程版本号,如 project(hello_cmake VERSION 1.0)
project (hello_cmake)
# 生成可执行的文件, hello_cmake是最终可运行文件的名称, main.cpp为其对应的源文件
# 注意,手机中编译时,使用的是add_library,因为手机应用需要的库
add_executable(hello_cmake main.cpp)
2. 为可执行文件,添加头文件
# $ cmake --version
# 设置本脚本支持的最小cmake版本,通过EevEco Studio 创建c++时,默认会自动生成好
cmake_minimum_required(VERSION 3.5)
# c++/c 工程的名称
# 这里也可以设置工程版本号,如 project(hello_cmake VERSION 1.0)
project (hello_headers)
# set 是用来创建变量,其值为变量后边带的文件
# 这里是创建一个SOURCES变量,其代表了Hello.cpp 和 main.cpp两个文件
set(SOURCES
src/Hello.cpp
src/main.cpp
)
# 生成一个名叫 hello_headers 的可执行的文件,其内容由 SOURCES 变量所对应的文件组成
add_executable(hello_headers ${SOURCES})
# 指定 hello_headers 所依赖的头文件,头文件即可以使用相对路径,也可以使用绝对路径
# PROJECT_SOURCE_DIR 是CMake中的内置变量,代表当前CMakeList.txt对应的工程根目录
target_include_directories(hello_headers
PRIVATE
${PROJECT_SOURCE_DIR}/include
)
3. 生成一个动态库
# $ cmake --version
# 设置本脚本支持的最小cmake版本,通过EevEco Studio 创建c++时,默认会自动生成好
cmake_minimum_required(VERSION 3.5)
# c++/c 工程的名称
# 这里也可以设置工程版本号,如 project(hello_cmake VERSION 1.0)
project (hello_cmake)
# 生成名为 hello_cmake 的动态库,main.cpp为其对应的源文件
add_library(hello_cmake SHARED main.cpp)
# 指定 hello_headers 所依赖的头文件,头文件即可以使用相对路径,也可以使用绝对路径
# PROJECT_SOURCE_DIR 是CMake中的内置变量,代表当前CMakeList.txt对应的工程根目录
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
4. 动态库依赖SDK动态库
# $ cmake --version
# 设置本脚本支持的最小cmake版本,通过EevEco Studio 创建c++时,默认会自动生成好
cmake_minimum_required(VERSION 3.4.1)
# c++/c 工程的名称
# 这里也可以设置工程版本号,如 project(hello_cmake VERSION 1.0)
project(HarveyCmake VERSION 1.1)
# 生成名为 hello_cmake 的动态库,main.cpp为其对应的源文件,如果要生成静态库,需要将SHARED变为 STATIC
add_library(entry SHARED hello.cpp)
# 添加entry库的依赖,libace_napi.z.so文件位置在
# Library/Huawei/Sdk/openharmony/9/native/sysroot/usr/lib/
target_link_libraries(entry PUBLIC libace_napi.z.so)
5. codelabs 中的XComponent示例
- 通过cmake定义宏
# $ cmake --version
# 设置本脚本支持的最小cmake版本,通过EevEco Studio 创建c++时,默认会自动生成好
cmake_minimum_required(VERSION 3.4.1)
# c++/c 工程的名称
# 这里也可以设置工程版本号,如 project(hello_cmake VERSION 1.0)
project(XComponent)
# 定义变量NATIVERENDER_ROOT_PATH,值来自常量CMAKE_CURRENT_SOURCE_DIR
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
# 定义宏 OHOS_PLATFORM, 即C++/C文件中使用到了#ifdef OHOS_PLATFORM
add_definitions(-DOHOS_PLATFORM)
# 指定头文件夹路径
# 在HarmonyOS 工程中设置头文件路径采用的都是 include_directories, 不会针对每个库进行单独的设置
# 如果想要针对每个库单独设置它所依赖的头文件,需要用到 target_include_directories
include_directories(
${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include
)
# 生成动态库 nativerender
add_library(nativerender SHARED
render/egl_core.cpp
render/plugin_render.cpp
manager/plugin_manager.cpp
napi_init.cpp
)
# 查找EGL库,将其指向变量EGL-lib
# EGL的全称是 libEGL.so,其位置是在Library/Huawei/Sdk/openharmony/9/native/sysroot/usr/lib/
find_library(
# Sets the name of the path variable.
EGL-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
EGL
)
# 查找EGL库,将其指向变量EGLS-lib
# GLESv3的全称是 libGLESv3.so,其位置是在Library/Huawei/Sdk/openharmony/9/native/sysroot/usr/lib/
find_library(
# Sets the name of the path variable.
GLES-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
GLESv3
)
# 查找EGL库,将其指向变量hilog-lib
# hilog_ndk.z的全称是 libhilog_ndk.z.so,其位置是在Library/Huawei/Sdk/openharmony/9/native/sysroot/usr/lib/
find_library(
# Sets the name of the path variable.
hilog-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
hilog_ndk.z
)
......
# 添加 nativerender 库的依赖
# libc++.a 位置 Library/Huawei/Sdk/openharmony/9/native/llvm/lib/aarch64-linux-ohos/c++
target_link_libraries(nativerender PUBLIC
${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libc++.a)
6. 子目录依赖
实践编译命令
- 进入 02-sub-projects/A-basic 目录
- cmake -S . -B .cxx;
- cmake --build .cxx
- .cxx/subbinary/subbinary
主 CMakeLists.txt 文件
# 设置本脚本支持的最小cmake版本
cmake_minimum_required (VERSION 3.5)
# c++/c 工程的名称
project(subprojects)
# 添加子目录
add_subdirectory(sublibrary1)
add_subdirectory(sublibrary2)
add_subdirectory(subbinary)
subbinary目录 CMakeLists.txt
# c++/c 工程的名称
project(subbinary)
# 生成名为 subbinary 的可执行文件,对应的源文件为main.cpp
# 如果是生成库,只需要将add_executable 替换为 add_library
add_executable(${PROJECT_NAME} main.cpp)
# 链接别名为sub::lib1 和 sub::lib2的库
target_link_libraries(${PROJECT_NAME}
sub::lib1
sub::lib2
)
subbinary1目录 CMakeLists.txt
# c++/c 工程的名称
project (sublibrary1)
# 生成 sublibrary1 库
add_library(${PROJECT_NAME} src/sublib1.cpp)
# 为 sublibrary1 库添加别名 sub:: lib1
add_library(sub::lib1 ALIAS ${PROJECT_NAME})
# 为 sublibrary1 库指定头文件见所在文件夹路径
target_include_directories( ${PROJECT_NAME}
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
subbinary2目录 CMakeLists.txt
# c++/c 工程的名称
project (sublibrary2)
# 生成 sublibrary2 库,仅仅包含接口
add_library(${PROJECT_NAME} INTERFACE)
# 为 sublibrary2 库添加别名 sub:: lib2
add_library(sub::lib2 ALIAS ${PROJECT_NAME})
# 为 sublibrary2 库指定头文件见所在文件夹路径
target_include_directories(${PROJECT_NAME}
INTERFACE
${PROJECT_SOURCE_DIR}/include
)
7. 链接三方库
01-basic/H-third-party-library 目录下, 为了可以编译通过, 将例子中的Boost换成ZLIB
# 设置本脚本支持的最小cmake版本
cmake_minimum_required(VERSION 3.5)
# c++/c 工程的名称
project (imported_targets)
# find a boost install with the libraries filesystem and system
#find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
# 查找已安装的ZLIB
find_package(ZLIB)
# 检查ZLIB是否被找到, <PackageName>_FOUND 是固定格式的变量
if(ZLIB_FOUND)
message ("zlib found")
else()
message (FATAL_ERROR "zlib find Boost")
endif()
# 生成名为 imported_targets 的可执行文件,对应的源文件为main.cpp
add_executable(imported_targets main.cpp)
# 链接ZLIB库
target_link_libraries( imported_targets
PRIVATE
ZLIB
)
8. 增加调试信息
在编写CMakeLists.txt时,也可以输出运行日志
最终会看到:日志 xx/xx/cmake-examples/01-basic/K-imported-targets
message(日志 ${CMAKE_CURRENT_SOURCE_DIR})
9. 链接已存在的动态库
# 设置支持的cmake最低版本号
cmake_minimum_required(VERSION 3.4.1)
#设置工程名称
project(HarmonyLearn)
#设置工程根目录路径变量
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
#指定头文件路径
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
# 生成customnapi动态库,源文件为main.cpp, util.cpp
add_library(customnapi SHARED main.cpp
util.cpp)
#指定 动态库路径
set(my_lib_path ${CMAKE_CURRENT_SOURCE_DIR}/libs/)
#生成 sm4.so
add_library(sm4 SHARED IMPORTED)
set_target_properties(sm4 PROPERTIES IMPORTED_LOCATION ${my_lib_path}${OHOS_ARCH}/libsm4.so)
#链接customnapi动态库
target_link_libraries(customnapi PUBLIC libace_napi.z.so sm4)
10. 批量添加源文件
比如在 add_library(test SHARED xx.cpp yy.cpp)
时,手动比较添加麻烦,快捷的方法
通过快捷命令file,直接将匹配到的文件路径复制给sources变量
file(GLOB sources CONFIGURE_DEPENDS *.cpp *.h)
add_library(test SHARED ${sources})
注意,批量添加带来的方便不一定适用于所有的工程,原因是工程中可能存在一些区分平台或者其它原因的源文件
CMake 语言
上边的CMakeLists.txt文件,简称为CMake文件,其内容编写规则叫做 CMake语言。
CMake语言编写的文件,在工程中以2种形式出现
- CMakeLists.txt 文件
- xxxx.cmake 文件(两种分类:a.脚本 b.模块)
源文件
CMakeLists.txt, xxx.cmake都是CMake的源文件
源文件的内容是零个或多个命令调用组成,命令调用之间采用空格或者换行分离
如下代码中的 cmake_minimum_required
叫做命令调用,看着像函数/方法调用
cmake_minimum_required(VERSION 3.4.1)
注释
# 单行注释
#[[演示多行注释,
注意#号与第一个方括号之间不能有空格]]
控制结构
条件语句
if()... elseif()...else()...endif()
if(<condition>)
<commands>
elseif(<condition>) # 可选, 可以重复出现
<commands>
else() # 可选
<commands>
endif()
循环
- foreach()...endforeach()
- while()...endwhile()
foreach(<loop_var> <items>)
<commands>
endforeach()
while(<condition>)
<commands>
endwhile()
命令定义
- macro()...endmacro()
- function()...endfunction()
macro(<name> [<arg1> ...])
<commands>
endmacro()
function(<name> [<arg1> ...])
<commands>
endfunction()
变量
定义一个变量:set(name value)
删除已定义的变量:unset(name)
如下代码,运行时会报错,报错位置是第5行,报错原因:testvar
变量没有定义。消除错误的方法:注释掉unset(testvar)
set(testvar 1)
message(${testvar})
unset(testvar)
message(${testvar})
变量也是有作用域的(即 局部/全局)
set(testvar 1) #CMakeLists.txt范围内
function(testfun)
set(testvar 2) #函数内
endfunction()
testfun()
message(${testvar})
CMake还提供了大量的公共变量,具体的可参见 cmake.org/cmake/help/…
引用这些公共变量的方法:${公共变量名}
message(${CMAKE_CURRENT_SOURCE_DIR})
注意,关于描述平台的公共变量中,目前没有HarmonyOS, 参见:cmake.org/cmake/help/…
具有HarmonyOS特征的公共变量,大概有如下几个
- OHOS_ARCH 【CPU架构】
- OHOS_SDK_NATIVE 【SDK对应Native路径】
- CMAKE_OHOS_ARCH_ABI 【CPU架构】
环境变量
获取一个环境变量:$ENV{变量名称}
比如获取系统所有的PATH 环境变量: $ENV{PATH}
可以通过message($ENV{PATH})
将值打印出来
即然是变量,所以也可以设置
set(ENV{MY} TEST)
message($ENV{MY})
祝好运!