CMake

什么是CMake

在Android Studio 2.2及以上,构建原生库的默认工具是CMake。

CMake是一个跨平台的构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。能够输出各种各样的makefile或者project文件。CMake并不直接构建出最终的软件,而是产生其他工具的脚本(如makefile),然后再依据这个工具的构建方式使用。

CMake是一个比make更高级的编译配置工具,它可以根据不同的平台、不同的编译器,生成相应的makefile或vcproj项目,从而达到跨平台的目的。

Android Studio利用CMake生成的是ninja。ninja是一个小型的关注速度的构建系统。我们不需要关心ninja的脚本,知道怎么配置CMake就可以了。

CMake其实是一个跨平台的支持产出各种不同的构建脚本的一个工具。

CMake的学习问题

CMake学习并不算难,主要是学习资料不成体系。让人觉得很难。

CMake安装

通过官网 https://cmake.org/ 下载。

Linux

Windows

VSCode配置CMake

加载CMake和CMake Tools插件。

在VSCode中,打开命令面板,输入并点击CMake:快速入门

emmm,上面GCC个人配置错误了,点击切换到MSVC。点击Build构建。

构建完成后,就可发现build目录,多了很多东西。我们找到Debug目录,就能找到可执行文件,。通过终端切换到Debug目录下,执行./cmaketest命令,

CMakeLists.txt

CMake变量

声明变量: CMake中所有变量都是string类型。

1
set(变量名 变量值)

引用变量:

1
${变量名}

移除变量:可以使用set()和unset()命令来声明或移除一个变量

1
unset(变量名)

【例】

1
2
3
4
5
6
7
8
9
10
11
12
13
# TODO CMake变量
# 声明变量:set(变量名 变量值)
set(var 666)

# 引用变量:message 命令用来打印
message("var = ${var}")

# CMake中所有变量都是string类型。可以使用set()和unset()命令来声明或移除一个变量

# 移除变量
unset(var)

message("var = ${var}") # 会取不到值,因为被移除了

CMake列表(lists)

声明列表:set(列表名 值1 值2 … 值N) 或 set(列表名 “值1;值2;…;值N”)。

1
2
3
4
5
6
7
8
9
# TODO CMake列表(lists)

# 声明列表:set(列表名 值1 值2 ... 值N) 或 set(列表名 "值1;值2;...;值N")
set(list_var 1 2 3 4 5) # 字符串列表呢? CMake中所有变量都是string类型
# 或者
set(list_var2 "1;2;3;4;5") # 字符串列表呢? CMake中所有变量都是string类型

message(list_var = ${list_var})
message(list_var2 = ${list_var2})

输出结果

1
2
[cmake] list_var=12345
[cmake] list_var2=123456

Cmake流程控制

条件命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# TODO CMake流程控制-条件命令

# true(1,ON,YES,TRUE,Y,非0的值)
# false(0,OFF,NO,FALSE,N,IGNORE,NOTFOUND)
set(if_tap OFF) # 定义一个变量if_tap,值为false
set(elseif_tap ON) # 定义一个变量elseif_tap,值为ture

if(${if_tap})
message("if")
elseif(${elseif_tap})
message("elseif")
else(${if_tap}) # 可以不加入 ${if_tap}
message("else")
# endif(${if_tap}) # 结束if
endif() # 结束if 可以不加
# 注意:elseif和else部分是可选的,也可以有多个elseif部分,缩进和空格对语句解析没有影响

输出结果

1
2
[cmake] 
[cmake] else

循环命令

while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
# TODO CMake流程控制-循环命令

#变量a
set(a "")

while(NOT a STREQUAL "xxx") # 当a不等于xxx,循环条件成立。
set(a "${a}x") # 重新设置变量a
message(">>>>>>a = ${a}")
endwhile()
#[[ 注意:
break()命令可以跳出整个循环
continue()可以继续当前循环
]]

输出结果

1
2
3
[cmake] >>>>>>a = x
[cmake] >>>>>>a = xx
[cmake] >>>>>>a = xxx

foreach循环+RANGE 区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
foreach(item 1 2 3)
message("1item = ${item}")
endforeach(item) # 结束for

foreach(item RANGE 2) # RANGE 默认从0开始, 所以是:0 1 2
message("2item = ${item}")
endforeach(item)

foreach(item RANGE 1 6 2) #step 1 3 5 每次跳级step2
message("3item = ${item}")
endforeach(item)

set(list_va3 1 2 3) # 列表
# foreach(item IN LISTS ${list_va3}) 没有报错,没有循环
foreach(item IN LISTS list_va3)
message("4item = ${item}")
endforeach(item)

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
[cmake] 1item = 1
[cmake] 1item = 2
[cmake] 1item = 3
[cmake] 2item = 0
[cmake] 2item = 1
[cmake] 2item = 2
[cmake] 3item = 1
[cmake] 3item = 3
[cmake] 3item = 5
[cmake] 4item = 1
[cmake] 4item = 2
[cmake] 4item = 3

Cmake函数

内置函数

查看函数的详细用法,请看CMake的文档,太多了。

添加库 add_library(var paths…)

文件查找函数 file(GLOB var path)

能够快速匹配文件,支持通配符(wild card)。

输出字符串 message(‘string’)

收集指定目录 aux_source_directory( )

设置语言标准 add_compile_options(-std=c++17)

自定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# TODO CMake自定义函数  Shell的函数很类似
#[[
ARGC:表示传入参数的个数
ARGV0:表示第一个参数,ARGV1、ARGV2以此类推即可
ARGV:表示所有参数
]]
function(funcName a1 a2 a3)
message("call funcName method")
message("a1 = ${a1}")
message("a2 = ${a2}")
message("a3 = ${a3}")
message("ARGC = ${ARGC}")
message("arg1 = ${ARGV0} arg2 = ${ARGV1} arg3 = ${ARGV2}")
message("all args = ${ARGV}")
endfunction(funcName a1 a2 a3)

funcName("第一个参数" "第二个参数" "第三个参数")# 调用funcName

输出结果

1
2
3
4
5
6
7
[cmake] call funcName method
[cmake] a1 = 第一个参数
[cmake] a2 = 第二个参数
[cmake] a3 = 第三个参数
[cmake] -- Configuring done
[cmake] ARGC = 3
[cmake] arg1 = 第一个参数 arg2 = 第二个参数 arg3 = 第三个参数

编译动态库与静态库

CMakeLists中使用add_library添加静态库(默认静态库)和动态库(添加动态库Flag SHARED)。

1
2
3
# 添加一个库,默认是静态库。
add_library(add_sub add_sub.cpp)
add_library(add_sub SHARED add_sub.cpp)

链接静态库和动态库

内部链接

通常情况下,我们都会创建include头文件目录用于存放头文件,创建src目录用于存放源文件。如图:,也就这样,头文件和源文件不在一个目录下,源文件必须使用类似#include "../include/add_sub.h"相对目录的形似指定头文件,非常的麻烦。

当然,CMake具备内部链接的功能。在CMakeLists.txt配置文件中,且在添加可执行文件前,使用函数include_directories(),添加include,直接include_directories(./include)即可。

对于源代码的配置,我们可以在src目录下,增加一个CMakeLists.txt,然后在外部CMakeLists.txt中添加add_subdirectory(./src),其中./src是源文件目录的相对路径。

最后我们链接目标库:

  1. 链接静态库 target_link_libraries(cmaketest add_sub)
  2. 链接动态库 target_link_libraries(cmaketest SHARED add_sub)

外部链接

Cmake预编译库与依赖源码方式

CMake文档

CMake的学习追求循序渐进,不是一蹴而就的。不懂的查文档即可,把时间放在更需要的地方上面。

参考

  1. 享学课堂
  2. https://www.bilibili.com/video/BV1zt4y1C7T9?p=5&spm_id_from=pageDriver