HarmonyOS CMake 快速入门

956 阅读4分钟

背景

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 版本官方文档

HarmonyOS SDK自带的官方文档

  • Library/Huawei/Sdk/openharmony/9/native/build-tools/cmake/doc/cmake/html/guide/tutorial/index.html

实验代码

github.com/ttroy50/cma…

git clone https://github.com/ttroy50/cmake-examples.git

实验设备

MacBook Pro

编译命令

  1. cmake -S .
  2. cmake --build .
  3. ./xxx

A-hello-cmake 为例

  1. cd A-hello-cmake
  2. cmake -S .
  3. cmake --build .
  4. ./hello_cmake

命令行终端中输出:Hello CMake! 字样

以下为前三个阶段目录结构的变化说明

没执行任何命令前的目录结构 WX20240327-161024@2x.png

执行 cmake -S . 之后的结构

WX20240327-160956@2x.png

执行 cmake --build . 之后的结构

WX20240327-215639@2x.png

执行 ./hello_cmake

WX20240327-220423@2x.png

你会发现,所有编译产出都和源文件在同一级,接下来我们将所有输出指定到.cxx文件夹下边

使用上边的 A-hello-cmake 你可以尝试一下

  1. cmake -S . -B .cxx
  2. cmake --build .cxx

执行 cmake -S . -B .cxx 之后的结构

WX20240327-221627@2x.png

执行 cmake --build .cxx 之后的结构

WX20240327-221909@2x.png

以上编译过程,第一没有指定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. 子目录依赖

WX20240328-160823@2x.png

实践编译命令

  1. 进入 02-sub-projects/A-basic 目录
  2. cmake -S . -B .cxx;
  3. cmake --build .cxx
  4. .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种形式出现

  1. CMakeLists.txt 文件
  2. 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特征的公共变量,大概有如下几个

  1. OHOS_ARCH 【CPU架构】
  2. OHOS_SDK_NATIVE 【SDK对应Native路径】
  3. CMAKE_OHOS_ARCH_ABI 【CPU架构】

环境变量

获取一个环境变量:$ENV{变量名称}

比如获取系统所有的PATH 环境变量: $ENV{PATH}

可以通过message($ENV{PATH}) 将值打印出来

即然是变量,所以也可以设置

set(ENV{MY} TEST)
message($ENV{MY})

祝好运!

OSZAR »