Android NDK(三)Cmake

CMake(跨平台的Make)是一个开源的、跨平台的构建系统。在 Android 中,我们使用 CMake 来构建 C/C++ 代码。这篇文章就将介绍 Cmake 的使用。

单项目的构建

如上图所示,C++ 构建需要经过预处理、编译、汇编、链接等步骤。Cmake 就是用来管理这些步骤的执行,相关的配置都放在 CMakeLists.txt 文件中。一个最简单的 CMakeLists.txt 配置示例如下:

cmake 复制代码
# 指定运行此 CMakeLists.txt 文件所需的 CMake 最低版本为 3.22.1。
cmake_minimum_required(VERSION 3.22.1)

# 定义当前项目的名称为 "hello-jni"。
# 同时 CMake 会创建如 ${PROJECT_NAME} 这样的变量供后续配置使用。
project("hello-jni")

# add_library 指令用于将指定的源文件编译成一个库文件。
# 第一个参数 "hello-jni" 是生成的库文件的名称
# 第二个参数 SHARED" 表明生成的库类型为共享库(动态链接库),静态库则使用 STATIC 关键字
# 第三个参数 "hello-jni.cpp" 是用于编译库文件的源文件,即把这个文件编译成共享库。
add_library(hello-jni SHARED hello-jni.cpp)

# target_link_libraries 指令用于将目标库(或可执行文件)与其他库进行链接。
target_link_libraries(hello-jni #  "hello-jni" 是要进行链接操作的目标库。
                      android # android 是 Android NDK 提供的系统库
                      log) # log 也是 Android NDK 提供的系统库,用于在 Android 平台上进行日志输出

从示例可以看出,我们可以通过在 CMakeLists.txt 中配置参数,来实现C++构建的功能。

Android NDK 提供的系统库,可以在 原生 API 中找到。需要注意 libandroidliblogandroidlog 是指向相同的库,lib 只是 Linux 中的通用前缀。

多文件编译

当源文件很多时,有三种方法来设置需要编译的源文件的位置。

  • 方案一:一一列举
scss 复制代码
add_library(echo
  SHARED
    audio_main.cpp
    audio_player.cpp
    audio_recorder.cpp
    audio_effect.cpp
    audio_common.cpp
    debug_utils.cpp)
  • 方案二:使用 set 设置变量
scss 复制代码
set(SOURCES audio_main.cpp
    audio_player.cpp
    audio_recorder.cpp
    audio_effect.cpp
    audio_common.cpp
    debug_utils.cpp)
add_library(mylib ${SOURCES})
  • 方案三:设置目录
scss 复制代码
file(GLOB SOURCES "src/*.cpp")
add_library(mylib ${SOURCES})

引用了其他库的头文件

如果你的库引用了其他库的头文件,比如#include "utils/native_debug.h",那么你需要使用 target_include_directories 命令来指定这些头文件的位置。代码示例如下:

cmake 复制代码
add_library(hello-jni SHARED hello-jni.cpp)
# 指定 hello-jni 引用头文件的位置
target_include_directories(hello-jni PRIVATE ${CMAKE_SOURCE_DIR}/path/to/include)
  • PRIVATE 和 PUBLIC

PRIVATE 表示这些头文件仅用于 hello-libs 库的编译,不会传递给链接 hello-libs 的其他目标。而 PUBLIC 关键字,意味着这些头文件搜索路径不仅对当前目标的编译有效,还会传递给所有链接该目标的其他目标。

  • CMAKE_SOURCE_DIR

CMAKE_SOURCE_DIR 是预定义变量,代表最顶层的 CMakeLists.txt 文件所在的目录路径。而 ${} 语法则是获取该变量的值。除了这个预定义变量外,常用的预定义变量有:

objectivec 复制代码
CMAKE_CURRENT_SOURCE_DIR 表示当前正在处理的 CMakeLists.txt 文件所在的目录路径

include_directories 也是设置头文件的位置,不同的是 include_directories 设置的是全局的;而 target_include_directories 需要指定库,比如这里是 hello-jni

导入共享库

如果要导入已经编译好的静态库或者动态库,需要在 add_library 使用 IMPORTED。同时需要使用 set_target_properties 来设置导入库的文件位置。

bash 复制代码
// 设置 distribution_DIR 变量的值
set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../distribution)

# 声明要导入一个静态库,库的逻辑名称为 lib_gmath。
# 静态库在编译时会被完整地链接到可执行文件或其他库中。
add_library(lib_gmath STATIC IMPORTED)

# 设置导入的静态库 lib_gmath 的实际文件位置。
set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION
    ${distribution_DIR}/gmath/lib/${ANDROID_ABI}/libgmath.a)

ANDROID_ABI 变量的值由 Gradle 在运行 CMake 命令时自动传入。变量的值就是当前正在构建的 ABI 类型。我们可以根据不同的 ANDROID_ABI 值进行不同的处理,例如添加不同的编译选项,链接不同的预编译的库等等。

查找库

scss 复制代码
find_library(
    third-party-lib
    third-party
    PATHS ${CMAKE_SOURCE_DIR}/src/main/cpp/third_party/lib
)

target_link_libraries(
    native-lib
    ${third-party-lib}
)

find_library 命令是用来寻找库的,语法是 find_library(variable_to_store_path library_name 路径),它会在设置的路径(如果没有设置,则使用预定义的路径)下查找名为 library_name 的库,然后将找到的库的路径存储在 variable_to_store_path 变量中。

链接顺序

bash 复制代码
target_link_libraries(hello-jni #  "hello-jni" 是要进行链接操作的目标库。
                      android # android 是 Android NDK 提供的系统库
                      log) # log 也是 Android NDK 提供的系统库,用于在 Android 平台上进行日志输出

由于 hello-jni 依赖 androidlog 库,因此 hello-jni 应该放在 androidlog 库前面,而不是后面。注意在 target_link_libraries 方法中这个依赖的顺序不能错误。

Cmake日志

Cmake 的日志格式如下:

cmake 复制代码
message([<mode>] "message text")

其中 <mode> 是可选参数,用于指定消息的类型,代码示例如下:

cmake 复制代码
message("This is a status message")
# WARNING 用于输出警告信息,通常会在控制台以醒目的方式显示。
message(WARNING "This is a warning message")
# 用于输出致命错误信息,CMake 会在输出该消息后终止配置过程
message(FATAL_ERROR "Required file not found")
# 用于输出作者自定义的警告信息
message(AUTHOR_WARNING "This is an author warning")
# 用于标记某个功能或特性已被弃用。
message(DEPRECATION "This feature is deprecated and will be removed in future versions")

Cmake语法

在 Cmake 中,我们可以使用逻辑判断和控制语句来实现更复杂的构建控制功能。

条件表达式

  • 比较运算符LESS(小于)、GREATER(大于)、EQUAL(等于)、LESS_EQUAL(小于等于)、GREATER_EQUAL(大于等于)。
cmake 复制代码
if(${MY_VARIABLE} LESS 10)
    message("MY_VARIABLE is less than 10")
endif()
  • 逻辑运算符AND(逻辑与)、OR(逻辑或)、NOT(逻辑非)。
cmake 复制代码
if(${MY_VARIABLE} GREATER 5 AND ${MY_VARIABLE} LESS 15)
    message("MY_VARIABLE is between 5 and 15")
endif()
  • 字符串判断STREQUAL(字符串相等)、STRLESS(字符串按字典序小于)、STRGREATER(字符串按字典序大于)。
cmake 复制代码
if(${MY_STRING} STREQUAL "hello")
    message("MY_STRING is equal to 'hello'")
endif()
  • 变量存在性判断:可以直接使用变量名来判断变量是否存在且不为空。
cmake 复制代码
if(MY_VARIABLE)
    message("MY_VARIABLE is defined and not empty")
endif()

if

cmake 复制代码
if(condition)
    # 当 condition 为真时执行的命令
    COMMAND1()
    COMMAND2()
elseif(another_condition)
    # 当 another_condition 为真时执行的命令
    COMMAND3()
else()
    # 当所有前面的条件都为假时执行的命令
    COMMAND4()
endif()

while

cmake 复制代码
while(condition)
    # 循环体命令
    COMMAND()
endwhile()

代码示例如下:

cmake 复制代码
set(COUNT 0)
while(${COUNT} LESS 5)
    message("COUNT is ${COUNT}")
    math(EXPR COUNT "${COUNT} + 1")
endwhile()

foreach

cmake 复制代码
foreach(VAR IN ITEMS item1 item2 ...)
    # 循环体命令
    COMMAND(${VAR})
endforeach()

代码示例如下:

cmake 复制代码
foreach(FILE IN ITEMS file1.txt file2.txt file3.txt)
    message("Processing file: ${FILE}")
endforeach()

编译选项

使用 CMake 除了可以控制编译的流程外,还可以设置编译相关的配置。常见的编译选项有

  • target_compile_features 和 target_compile_options
scss 复制代码
# target_compile_features 可以设置编译特性,这里设置为采用 C++14 标准编译
target_compile_features(your-lib PUBLIC cxx_std_14)

# target_compile_options 命令可以用来添加编译选项,
# 这里添加了一个 -Wall 选项,它会让编译器输出所有类型的警告信息
target_compile_options(your-lib PRIVATE -Wall)

add_compile_options 是为项目中的所有目标(可执行文件、库等)添加编译选项

  • set

使用 set 来设置一些配置选项。

bash 复制代码
set(CMAKE_VERBOSE_MAKEFILE on) # 输出编译和链接信息

# `CMAKE_C_FLAGS` 是 CMake 中用于存储 C 编译器编译选项的变量。
# 此语句将 `-Wall`、`-Werror` 和 `-Wno-unused-function` 这三个编译选项追加到原有
# 的 `CMAKE_C_FLAGS` 变量值后面
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wno-unused-function")

多项目的构建

如下所示,大项目都会分模块处理。对于这种情况,子项目的 CMakeLists.txt 不需要特殊处理。而根目录的 CMakeLists.txt 只需要使用 add_subdirectory 引入子项目就可以了。

objectivec 复制代码
project/
│   CMakeLists.txt
│
├───subproject1
│   │   CMakeLists.txt
│   │   other code files...
│
└───subproject2
    │   CMakeLists.txt
    │   other code files...

代码示例如下:

scss 复制代码
// subproject1 的 CMakeLists.txt
add_library(sub1 sub1.cpp)
target_link_libraries(sub1 ...)

// subproject2 的 CMakeLists.txt
add_library(sub2 sub2.cpp)
target_link_libraries(sub2 ...)

// 根目录的 CMakeLists.txt
add_subdirectory(subproject1)
add_subdirectory(subproject2)
add_executable(MyProject main.cpp)
target_link_libraries(MyProject sub1 sub2)

自定义命令

自定义命令看 CMake 高级特性

参考

相关推荐
太空漫步111 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手7 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391914 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup
rkmhr_sef14 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb