CMake学习笔记
来源:
文件资料
CMake 保姆级教程(上) | 爱编程的大丙 (subingwen.cn)
视频教学
(一)第一次开发顺序:
1.要写的main程序写好,分程序写好,头文件库写好
2.建立CMakeLists.txt文件,在里面写内容
3.在终端上建立build文件夹,cd到里面,使用MinGW编译器把cmake文件转为makefile工程文件(Windows下),即在终端写入以下内容:
1 | cmake -G "MinGW makefiles" ../ |
在Linux上:
1 | cmake .. |
即可,生成的是makefile文件夹
4.将makefile文件做成可执行程序:在终端中输入 make 即可
(二)二次修改后的问题
问题1:编译失败
检查有无语法错误
ctrl+shift+p选择工具包,使用cmake -G “MinGW makefiles” …/指令
关闭保存cmakelists.txt时候才会生成自动生成build文件夹
问题2:修改之后重新编译
关闭并保存文件,看是否报错,错了可能语法有问题,没错自动编译
(三)下面是正文:
1.CMake概述
CMake 是一个项目构建工具,并且是跨平台的。关于项目构建我们所熟知的还有Makefile(通过 make 命令进行项目的构建),大多是IDE软件都集成了make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake等,如果自己动手写 makefile,会发现,makefile 通常依赖于当前的编译平台,而且编写 makefile 的工作量比较大,解决依赖关系时也容易出错。
而 CMake 恰好能解决上述问题, 其允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,所以可以把CMake看成一款自动生成 Makefile的工具,其编译流程如下图:

蓝色虚线表示使用makefile构建项目的过程
红色实线表示使用cmake构建项目的过程
介绍完CMake的作用之后,再来总结一下它的优点:
-
跨平台
-
能够管理大型项目
-
简化编译构建过程和编译过程
-
可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能
2.CMake的使用
CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。
2.1 注释
2.1.1 注释行
CMake 使用 # 进行行注释,可以放在任何位置。
1 | # 这是一个 CMakeLists.txt 文件 |
2.1.2 注释块
CMake 使用 #[[ ]] 形式进行块注释。(哦这个展示他失败了)
1 | #[[ 这是一个 CMakeLists.txt 文件。 |
实际上是可以的:

2.1 只有源文件
2.1.1 共处一室
1.准备工作,为了方便测试,在我本地电脑准备了这么几个测试文件
add.cpp
1 |
|
sub.cpp
1 |
|
mult.cpp
1 |
|
div.cpp
1 |
|
head.h
1 |
|
main.cpp
1 |
|
2.上述文件的目录结构如下:
1 | tree |
3.添加 CMakeLists.txt 文件
在上述源文件所在目录下添加一个新文件 CMakeLists.txt,文件内容如下:
1 | cmake_minimum_required(VESION 3.0) |
接下来依次介绍一下在 CMakeLists.txt 文件中添加的三个命令:
cmake_minimum_required:指定使用的 cmake 的最低版本
可选,非必须,如果不加可能会有警告
project:定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。
1 | # PROJECT 指令的语法是: |
add_executable:定义工程会生成一个可执行程序
1 | add_executable(可执行程序名 源文件名称) |
这里的可执行程序名和project中的项目名没有任何关系
源文件名可以是一个也可以是多个,如有多个可用空格或 ; 间隔
1 | # 样式1 |
4.执行CMake 命令
万事俱备只欠东风,将 CMakeLists.txt 文件编辑好之后,就可以执行 cmake命令了。
(win关闭保存cmakelists.txt 文件后会自动执行cmake)
2.1.2 VIP 包房
通过上面的例子可以看出,如果在CMakeLists.txt文件所在目录执行了cmake命令之后就会生成一些目录和文件(包括 makefile 文件),如果再基于makefile文件执行make命令,程序在编译过程中还会生成一些中间文件和一个可执行文件,这样会导致整个项目目录看起来很混乱,不太容易管理和维护,此时我们就可以把生成的这些与项目源码无关的文件统一放到一个对应的目录里边,比如将这个目录命名为build:
1 | mkdir build |
现在cmake命令是在build目录中执行的,但是CMakeLists.txt文件是build目录的上一级目录中,所以cmake 命令后指定的路径为..,即当前目录的上一级目录。
当命令执行完毕之后,在build目录中会生成一个makefile文件
1 | tree build -L 1 |
这样就可以在build目录中执行make命令编译项目,生成的相关文件自然也就被存储到build目录中了。这样通过cmake和make生成的所有文件就全部和项目源文件隔离开了,各回各家,各找各妈。
2.2 私人订制
2.2.1 定义变量
在上面的例子中一共提供了5个源文件,假设这五个源文件需要反复被使用,每次都直接将它们的名字写出来确实是很麻烦,此时我们就需要定义一个变量,将文件名对应的字符串存储起来,在cmake里定义变量需要使用set。
1 | # SET 指令的语法是: |
VAR:变量名
VALUE:变量值
1 | # 方式1: 各个源文件之间使用空格间隔 |
2.2.2 指定使用的C标准
在编写C程序的时候,可能会用到C11、C14、C17、C20等新特性,那么就需要在编译的时候在编译命令中制定出要使用哪个标准:
1 | g++ *.cpp -std=c++11 -o app |
上面的例子中通过参数-std=c11指定出要使用c11标准编译程序,C标准对应有一宏叫做DCMAKE_CXX_STANDARD。在CMake中想要指定C标准有两种方式:
-
1.在 CMakeLists.txt 中通过 set 命令指定
1
2
3
4
5
6#增加-std=c++11
set(CMAKE_CXXSTANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)
1 | - 2.在执行 cmake 命令的时候指定出这个宏的值 |
在上面例子中 CMake 后的路径需要根据实际情况酌情修改。
2.2.3 指定输出的路径
在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH,它的值还是通过set命令进行设置:
1 | set(HOME /home/robin/Linux/Sort) |
-
第一行:定义一个变量用于存储一个绝对路径
-
第二行:将拼接好的路径值设置给
EXECUTABLE_OUTPUT_PATH宏- 如果这个路径中的子目录不存在,会自动生成,无需自己手动创建
由于可执行程序是基于 cmake 命令生成的makefile文件然后再执行 make 命令得到的,所以如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx,那么这个路径中的 ./ 对应的是 makefile 文件所在的那个目录。
2.3 搜索文件
如果一个项目里边的源文件很多,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦也不现实。所以,在CMake中为我们提供了搜索文件的命令,可以使用aux_source_directory命令或者file命令。
2.3.1 方式1
在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:
1 | aux_source_directory(< dir > < variable >) |
dir:要搜索的目录
variable:将从dir目录下搜索到的源文件列表存储到该变量中
1 | cmake_minimum_required(VERSION 3.0) |
2.3.2 方式2
如果一个项目里边的源文件很多,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦了。所以,在CMake中为我们提供了搜索文件的命令,他就是file(当然,除了搜索以外通过 file 还可以做其他事情)。
1 | file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型) |
GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
搜索当前目录的src目录下所有的源文件,并存储到变量中
1 | file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) |
CMAKE_CURRENT_SOURCE_DIR 或 CMAKE_SOURCE_DIR 宏表示当前访问的 CMakeLists.txt 文件所在的路径。
关于要搜索的文件路径和类型可加双引号,也可不加:
1 | file(GLOB MAIN_HEAD "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h") |
2.4 包含头文件
在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在CMake中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是include_directories:
1 | include_directories(headpath) |
举例说明,有源文件若干,其目录结构如下:
1 | $ tree |
CMakeLists.txt文件内容如下:
1 | cmake_minimum_required(VERSION 3.0) |
其中,第六行指定就是头文件的路径,PROJECT_SOURCE_DIR宏对应的值就是我们在使用cmake命令时,后面紧跟的目录,一般是工程的根目录。
2.5 制作动态库或静态库
有些时候我们编写的源代码并不需要将他们编译生成可执行程序,而是生成一些静态库或动态库提供给第三方使用,下面来讲解在 cmake 中生成这两类库文件的方法。
2.5.1 制作静态库
在 cmake 中,如果要制作静态库,需要使用的命令如下:
1 | add_library(库名称 STATIC 源文件1 [源文件2] ...) |
在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
在Windows中虽然库名和Linux格式不同(lib+库名字+.lib然而事实上不是这样),但也只需指定出名字即可。
下面有一个目录,需要将src目录中的源文件编译成静态库,然后再使用:
1 | . |
根据上面的目录结构,可以这样编写CMakeLists.txt文件:
1 | cmake_minimum_required(VERSION 3.0) |
这样最终就会生成对应的静态库文件libcalc.a。
2.5.2 制作动态库
在cmake中,如果要制作动态库,需要使用的命令如下:
1 | add_library(库名称 SHARED 源文件1 [源文件2] ...) |
在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
在Windows中虽然库名和Linux格式不同 (.dll文件) ,但也只需指定出名字即可。
根据上面的目录结构,可以这样编写CMakeLists.txt文件:
1 | cmake_minimum_required(VERSION 3.0) |
这样最终就会生成对应的动态库文件libcalc.so。
2.5.3 指定输出的路径
方式1 - 适用于动态库
对于生成的库文件来说和可执行程序一样都可以指定输出路径。由于在Linux下生成的动态库默认是有执行权限的,所以可以按照生成可执行程序的方式去指定它生成的目录:
1 | cmake_minimum_required(VERSION 3.0) |
方式2 - 都适用
由于在Linux下生成的静态库默认不具有可执行权限,所以在指定静态库生成的路径的时候就不能使用EXECUTABLE_OUTPUT_PATH宏了,而应该使用LIBRARY_OUTPUT_PATH,这个宏对应静态库文件和动态库文件都适用。
1 | cmake_minimum_required(VERSION 3.0) |
2.6 包含库文件
在编写程序的过程中,可能会用到一些系统提供的动态库或者自己制作出的动态库或者静态库文件,cmake中也为我们提供了相关的加载动态库的命令。
2.6.1 链接静态库
1 | src |
现在我们把上面src目录中的add.cpp、div.cpp、mult.cpp、sub.cpp编译成一个静态库文件libcalc.a。
测试目录结构如下:
1 | tree |
在cmake中,链接静态库的命令如下:
1 | link_libraries(<static lib> [<static lib>...]) |
-
参数1:指定出要链接的静态库的名字
-
可以是全名
libxxx.a -
也可以是掐头(
lib)去尾(.a)之后的名字xxx
-
-
参数2-N:要链接的其它静态库的名字
如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:
1 | link_directories(<lib path>) |
这样,修改之后的CMakeLists.txt文件内容如下:
1 | cmake_minimum_required(VERSION 3.0) |
添加了第8行的代码,就可以根据参数指定的路径找到这个静态库了
2.6.2 链接动态库
在程序编写过程中,除了在项目中引入静态库,好多时候也会使用一些标准的或者第三方提供的一些动态库,关于动态库的制作、使用以及在内存中的加载方式和静态库都是不同的,在此不再过多赘述,如有疑惑请参考Linux静态库与动态库
在cmake中链接动态库的命令如下:
1 | target_link_libraries( |
-
target:指定要加载动态库的文件的名字
-
该文件可能是一个源文件
-
该文件可能是一个动态库文件
-
该文件可能是一个可执行文件
-
-
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
-
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
-
动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
-
1 | target_link_libraries(A B C) |
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。
链接系统动态库
动态库的链接和静态库是完全不同的:
静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存
因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:
1 | cmake_minimum_required(VERSION 3.0) |
链接第三方动态库
现在,自己生成了一个动态库,对应的目录结构如下:
1 | tree |
假设在测试文件main.cpp中既使用了自己制作的动态库libcalc.so又使用了系统提供的线程库,此时CMakeLists.txt文件可以这样写:(注意以下代码是有小错误的再往下看)
1 | cmake_minimum_required(VERSION 3.0) |
在第六行中,pthread、calc都是可执行程序app要链接的动态库的名字。当可执行程序app生成之后并执行该文件,会提示有如下错误信息:
1 | ./app |
这是因为可执行程序启动之后,去加载calc这个动态库,但是不知道这个动态库被放到了什么位置,所以就加载失败了,在 CMake 中可以在生成可执行程序之前,通过命令指定出要链接的动态库的位置,指定静态库位置使用的也是这个命令:
1 | link_directories(path) |
所以修改之后的CMakeLists.txt文件应该是这样的:
1 | cmake_minimum_required(VERSION 3.0) |
通过link_directories指定了动态库的路径之后,在执行生成的可执行程序的时候,就不会出现找不到动态库的问题了。
温馨提示:使用 target_link_libraries 命令就可以链接动态库,也可以链接静态库文件。