前言

自从 AndroidStudio2.2 开始支持 CMAKE 来构建 C/C++ 程序之后,作为 Android 开发就可以使用 CMake 来替换 Android.mk 了。

基本使用

在学习 CMAKE 之前,我们先来看一下如何使用 CMAKE 编译项目。假设 CMakeLists.txt 已经编写完毕,并且是正确的。

1
2
3
4
mkdir build && cd build
cmake ..
make
make install

在这个例子中,我们建了一个 build 文件夹用于执行 cmake 指令,这种做法叫做外部编译(out-of-source-build)。注意 cmake .. 这后边有个 .. 这是用于告诉 CMAKE CMakeLists.txt 在哪个文件夹下。

这样做有一个好处就是不会污染源码,因为在执行 cmake 的过程中会产生一些临时文件、缓存文件、日志文件等,如果都和源码放在一起就非常的乱,会影响到代码的阅读。

使用外部编译也可方便源码的管理。

既然有外部编译那么也就有内部编译(in-source-build),内部编译很好理解也就是在 CMakeList.txt 所在的目录存放产生的临时文件。

建议尽可能使用外部编译。

语法

基本语法

来看一下 CMAKE 的最小配置,在项目的根目录创建一个 CMakeLists.txt 文件,并把如下内容拷贝进去

1
2
3
cmake_minimum_required(VERSION 3.12) # 设置 CMake 的版本
project(Demo) # 设置项目名
add_executable(${PROJECT_NAME} main.cpp) # 编译成可执行文件,参与编译的源文件为 main.cpp

CMAKE 的最小配置需要你设置 CMAKE 的最小版本号,为项目取一个名字,编译成可执行文件(或者库)并指定需要参与编译的源文件。

编写好了 CMakeLists.txt 就可以通过上一小节的知识进行构建了,但还需要准备一个 main.cpp 文件就行了。

设置变量

CMake 中设置变量的语法使用 set ,来看个例子

1
2
set(name cmake) # 把 cmake 赋值给 name
message(${name}) # 打印 name 的值

设置变量挺简单的,在使用的时候通过 ${} 来引用,我们可以用 message 来打印变量的值。

变量区分大小写

预定义变量

我们可以自己定义变量,系统也内置来一些变量来方便我们的使用

变量名 作用
PROJECT_SOURCE_DIR 项目的源代码所在的目录,最顶层的目录
CMAKE_SOURCE_DIR 项目的源代码所在的目录,也是最顶层目录
CMAKE_CURRENT_SOURCE_DIR 当前CMakeLists.txt所在的目录
CMAKE_CURRENT_LIST_FILE 当前CMakeLists.txt的完整路径

编译成可执行文件

把要源码编译成可执行文件只需要使用 add_executable 函数,并指定名字和要参与编译的源文件就行。

除此之外,我们还可以设置编译的类型为 Debug 还是 Release

1
2
3
# 设置构建类型为 Debug,也可以设置为 Release
set(CMAKE_BUILD_TYPE Debug)
add_executable(${PROJECT_NAME} main.cpp)

除了在 CMakeLists.txt 中指定编译类型,还可以通过命令行来指定,如下所示

1
cmake .. -DCMAKE_BUILD_TYPE=Debug

其中 -D 表示定义变量并赋值,可以理解为 definition

编译成库

Android 开发中,我们通常使用 C/C++ 来进行动态库的开发,而不是直接编译成可执行文件。来看个例子

1
2
3
4
5
6
add_library(# 设置库的名字
  native-lib
  # 设置库为动态库还是静态库,SHARED动态库,STATIC 静态库
  SHARED
  # 参与编译的源文件
  native-lib.cpp)

编译成库文件使用 add_library 函数,需要指定库的名字并且要指定是动态库还是静态库,最后就是指定需要参与编译的源文件了。

添加第三方库

在平时的开发中难免会用到第三方库,我们来看一下怎么添加第三方库的依赖

1
2
3
add_library(glfw SHARED IMPORTED)
set_target_properties(glfw PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/lib/libglfw3.so)
target_link_libraries(${PROJECT_NAME} glfw)

添加第三方库和编译第三方库一样使用 add_library 函数,不一样的是第三库使用的是导入的形式,不需要使用源码再进行编译一遍。

不过第三方库需要指定 so 的位置,使用 set_target_properties 来指定导入路径这个属性。

最后还需要把第三方库链接到我们自己的库中,使用 target_link_libraries ,只需要把指定目标和要链接到目标的库就行。

添加头文件

在开发中我们常常会把头文件和源文件分开放,这样方便管理。分开放之后我们就需要告诉 CMake 去哪里查找头文件,通过 include_directories

1
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)

include_directories 相当于 g++ 选项中的 -I 参数。

一次性添加所有源文件

之前使用 add_library 或者 add_executable 都是在变量里直接写源文件的路径,文件少还好,文件多了那简直是噩梦。如果不小心删掉了,或者增加来非常容易遗忘,所以在 CMake 中可以通过 AUX_SOURCE_DIRECTORY 来一次性把指定目录下的源文件存在一个变量中。

1
2
3
aux_source_directory(. SRC_LIST)  # 查找当前目录下的所有源文件,放到 SRC_LIST 中
add_library(${PROJECT_NAME} ${SRC_LIST})
add_executable(${PROJECT_NAME} ${SRC_LIST})

需要注意的是 aux_source_directory 不会遍历子目录

查找系统库

在做 Android 开发的时候, Android 系统本身也提供一些库,比如 log ,当我们需要的时候就可以直接使用,不需要再自己写一遍了。使用系统库的时候,先要查找一下在哪,通过 find_library

1
2
3
# 查找 log 库,把找到的库放到 log-lib 这个变量中
find_library(log-lib log)
target_link_libraries(native-lib ${log-lib}) # 别忘了要链接到需要使用的库里边

参考

CMake Tutorial — CMake 3.19.1 Documentation

ttroy50/cmake-examples: Useful CMake Examples