1. CMake介绍
CMake一般用来构建大型项目,为了弄清整个项目的结构,源码在哪里,生成的产物在哪里等等,了解CMake以后以上问题都会迎刃而解。
1.1 构建
一般来说,为了方便管理,避免弄乱项目结构,我们会新一个build目录去存放生成的产物。如果不新建build会生成很多杂乱的文件,相关文件,可以参考第二节或者Linux静态库和动态库 | 就是要摸鱼 (wjdmyby98k.github.io)里面的c文件。

上面执行的过程如下
- 书写CMakeLists.txt
1 | cmake_minimum_required(VERSION 3.0) |
cmake_minimum_required:指定使用的 cmake 的最低版本 可选,非必须,如果不加可能会有警告
project:定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。可选项很多, 我们一般写一个工程名就行了
add_executable:定义工程会生成一个可执行程序
1
add_executable(可执行程序名 源文件名称)
这里的可执行程序名和project中的项目名没有任何关系,有多个源文件,可以用空格或;间隔
执行cmake命令,一般就是
cmake .
cmake ..
1
2# cmake 命令原型
$ cmake CMakeLists.txt文件所在路径执行make
当执行cmake命令以后,会根据CMakeLists.txt生成一些文件,其中就有makefile文件,然后再执行make命令进行构建,就能得到可执行程序。
根据前面的测试可以发现,生成的文件跟源码混在一起,比较混乱,所以为了方便管理,一般都新建一个build目录去存放生成产物。这时候在build目录下执行cmake ..
1.2 CMake常用命令
定义变量
1 | # SET 指令的语法是: |
指定使用的C++标准
gcc/g++编译的时候我们一般这样写
1 | g++ *.cpp -std=c++11 -o app |
上面的例子中通过参数-std=c++11
指定出要使用c++11
标准编译程序,C++标准对应有一宏叫做DCMAKE_CXX_STANDARD
。在CMake中想要指定C++标准有两种方式:
- 在CMakeLists.txt中通过set指定:
set(CMAKE_CXX_STANDARD 11)
- 在执行cmake的时候设置宏的值:
cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11
指定输出路径
同上面一样,在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH,它的值还是通过set命令进行设置:
1 | set(HOME /home/robin/Linux/Sort) |
如果这个路径中的子目录不存在,会自动生成,无需自己手动创建
搜索文件
一般来说有两种方式,aux_source_directory
命令或者file
命令
1、aux_source_directory
1 | aux_source_directory(< dir > < variable >) |
2、file
1 | file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型) |
- GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
- GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
1 | # 搜索当前目录的src目录下所有的源文件,并存储到变量中 |
包含头文件
在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在CMake中设置要包含的目录也很简单,通过一个命令就可以搞定了,他就是include_directories:
1 | include_directories(${PROJECT_SOURCE_DIR}/include) |
PROJECT_SOURCE_DIR
宏对应的值就是我们在使用cmake命令时,后面紧跟的目录,一般是工程的根目录。
1.3 制作静态库或动态库
静态库
1
add_library(库名称 STATIC 源文件1 [源文件2] ...)
动态库
1
add_library(库名称 SHARED 源文件1 [源文件2] ...)
1.4 指定输出路径
在Linux中生成的动态库,默认是有可执行权限的,静态库默认不具有可执行权限。
针对动态库
1
2
3
4
5
6
7cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库生成路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(calc SHARED ${SRC_LIST})对于这种方式来说,其实就是通过set命令给
EXECUTABLE_OUTPUT_PATH
宏设置了一个路径,这个路径就是可执行文件生成的路径都适用,设置
LIBRARY_OUTPUT_PATH
1
2
3
4
5
6
7
8
9
10cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
# 设置动态库/静态库生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
#add_library(calc SHARED ${SRC_LIST})
# 生成静态库
add_library(calc STATIC ${SRC_LIST})
如果只是单纯的生成库,这两种方式足以,但其实还有几个宏,可以指定生成路径,可参考2.2 存在的问题
1.5 链接库文件
静态库
1
2
3
4link_libraries(<static lib> [<static lib>...])
# 链接静态库
link_libraries(calc)如果是自定义的库,一般还需指明一下库的路径
1
2
3
4
5
6
7
8
9
10
11cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(app ${SRC_LIST})动态库
1
2
3
4
5target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)target:指定要加载动态库的文件的名字
该文件可能是一个源文件
该文件可能是一个动态库文件
该文件可能是一个可执行文件
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
1
2target_link_libraries(A B C)
target_link_libraries(D A)- PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用
- PRIVATE:在private后面的库仅被Link到前面的target中,并且终结掉,第三方不能感知你调了啥库
- INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号
这里需要注意的是,动态库的加载和静态库的加载是不同的,在Linux静态库和动态库 | 就是要摸鱼 (wjdmyby98k.github.io)已经详细解释过。静态库在生成可执行程序的时候,就被打包到了可执行程序中,可执行程序一启动就被加载到内存中,而动态库只有在库函数被调用的时候,才会加载到内存中。
因此我们链接动态库是先生成可执行程序,再链接,而静态库则是先链接!
1 | cmake_minimum_required(VERSION 3.0) |
在上面的例子中,用link_libraries链接静态库,使用target_link_libraries命令链接动态库,但事实上,target_link_libraries也可以链接静态库文件。
1.6 常见的宏⭐
CMAKE_BINARY_DIR
:项目实际构建路径,也就是cmake命令执行的路径,比如/build目录
CMAKE_ARCHIVE_OUTPUT_DIRECTORY
:默认存放静态库的文件夹位置;CMAKE_LIBRARY_OUTPUT_DIRECTORY
:默认存放动态库的文件夹位置;LIBRARY_OUTPUT_PATH
:默认存放库文件的位置,如果产生的是静态库并且没有指定CMAKE_ARCHIVE_OUTPUT_DIRECTORY
则存放在该目录下,动态库也类似;CMAKE_RUNTIME_OUTPUT_DIRECTORY
:存放可执行软件的目录;PROJECT_SOURCE_DIR
:在使用cmake命令时,后面紧跟的目录,一般是工程的根目录;
CMAKE_SOURCE_DIR
: 最外层CMakeLists.txt所在目录;CMAKE_CURRENT_SOURCE_DIR
:当前正在处理的源目录的路径;
2. 嵌套的CMake⭐
如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。
先来看一下下面的这个的目录结构:
1 | tree |
- include 目录:头文件目录
- calc 目录:目录中的四个源文件对应的加、减、乘、除算法
对应的头文件是include中的calc.h - sort 目录 :目录中的两个源文件对应的是插入排序和选择排序算法
对应的头文件是include中的sort.h - test1 目录:测试目录,对加、减、乘、除算法进行测试
- test2 目录:测试目录,对排序算法进行测试
可以看到各个源文件目录所需要的CMakeLists.txt文件现在已经添加完毕了。接下来庖丁解牛,我们依次分析一下各个文件中需要添加的内容。
2.1 准备工作
节点关系
众所周知,Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:
- 根节点CMakeLists.txt中的变量全局有效
- 父节点CMakeLists.txt中的变量可以在子节点中使用
- 子节点CMakeLists.txt中的变量只能在当前节点中使用
添加子目录
1 | add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) |
- source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录
- binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
- EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。
通过这种方式CMakeLists.txt文件之间的父子关系就被构建出来了。
2.2 解决问题
在上面的目录中我们要做如下事情:
- 通过 test1 目录中的测试文件进行计算器相关的测试
- 通过 test2 目录中的测试文件进行排序相关的测试
现在相当于是要进行模块化测试,对于calc和sort目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式。
一般来说,这里要么是把源文件编译成一个可执行文件,要么就是编译成一个库,然后在搞一个test目录!根目录🚀
根目录中的 CMakeLists.txt文件内容如下:
1 | cmake_minimum_required(VERSION 3.0) |
在根节点对应的文件中主要做了两件事情:定义全局变量和添加子目录。
定义的全局变量主要是给子节点使用,目的是为了提高子节点中的CMakeLists.txt文件的可读性和可维护性,避免冗余并降低出差的概率。
一共添加了四个子目录,每个子目录中都有一个CMakeLists.txt文件,这样它们的父子关系就被确定下来了。
calc目录
calc 目录中的 CMakeLists.txt文件内容如下:
1 | cmake_minimum_required(VERSION 3.0) |
- 第3行aux_source_directory:搜索当前目录(calc目录)下的所有源文件
- 第4行include_directories:包含头文件路径,HEAD_PATH是在根节点文件中定义的
- 第5行set:设置库的生成的路径,LIB_PATH是在根节点文件中定义的
- 第6行add_library:生成静态库,静态库名字CALC_LIB是在根节点文件中定义的
sort目录
sort 目录中的 CMakeLists.txt文件内容如下:
1 | cmake_minimum_required(VERSION 3.0) |
第6行add_library:生成动态库,动态库名字SORT_LIB是在根节点文件中定义的
这个文件中的内容和calc节点文件中的内容类似,只不过这次生成的是动态库。
在生成库文件的时候,这个库可以是静态库也可以是动态库,一般需要根据实际情况来确定。如果生成的库比较大,建议将其制作成动态库。test1目录
test1 目录中的 CMakeLists.txt文件内容如下:
1 | cmake_minimum_required(VERSION 3.0) |
- 第4行include_directories:指定头文件路径,HEAD_PATH变量是在根节点文件中定义的
- 第6行link_libraries:指定可执行程序要链接的静态库,CALC_LIB变量是在根节点文件中定义的
- 第7行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
- 第8行add_executable:生成可执行程序,APP_NAME_1变量是在根节点文件中定义的
test2目录
test2 目录中的 CMakeLists.txt文件内容如下:
1 | cmake_minimum_required(VERSION 3.0) |
- 第四行include_directories:包含头文件路径,HEAD_PATH变量是在根节点文件中定义的
- 第五行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
- 第六行link_directories:指定可执行程序要链接的动态库的路径,LIB_PATH变量是在根节点文件中定义的
- 第七行add_executable:生成可执行程序,APP_NAME_2变量是在根节点文件中定义的
- 第八行target_link_libraries:指定可执行程序要链接的动态库的名字
构建项目
一切准备就绪之后,开始构建项目,进入到根节点目录的build 目录中,执行cmake 命令,如下:
1 | cmake .. |
可以看到在build目录中生成了一些文件和目录,如下所示:
1 | tree build -L 1 |
然后在build 目录下执行make 命令:
- 在项目根目录的lib目录中生成了静态库libcalc.a
- 在项目根目录的lib目录中生成了动态库libsort.so
- 在项目根目录的bin目录中生成了可执行程序test1
- 在项目根目录的bin目录中生成了可执行程序test2
最后再来看一下上面提到的这些文件是否真的被生成到对应的目录中了:
1 | tree bin/ lib/ |

存在的问题⭐
但是上面的方式还是有一点问题,因为我们在根目录的CMakeLists.txt中定义了
1 | # 静态库生成的路径 |
CMAKE_BINARY_DIR
:项目实际构建路径,也就是cmake命令执行的路径,比如/build目录CMAKE_ARCHIVE_OUTPUT_DIRECTORY
:默认存放静态库的文件夹位置;CMAKE_LIBRARY_OUTPUT_DIRECTORY
:默认存放动态库的文件夹位置;LIBRARY_OUTPUT_PATH
:默认存放库文件的位置,如果产生的是静态库并且没有指定CMAKE_ARCHIVE_OUTPUT_DIRECTORY
则存放在该目录下,动态库也类似;CMAKE_RUNTIME_OUTPUT_DIRECTORY
:存放可执行软件的目录;
了解了上面五个宏以后,我们就可以这样修改,删除根目录下定义的库文件和可执行文件的定义,子目录也相应删掉。根目录添加下面几行:
1 | # 设置可执行文件和库的输出目录 |
这样整个目录结构就很清晰了!

3. CMake生成器⭐
CMake默认的生成器是,Unix Makefiles,也就是make,其实也可以换成Ninja,Ninja更快速。

只需要cmake .. - G Ninja
,然后ninja
、ninja install
即可
和make
、make install
一样,前者是编译生成对应的库、可执行文件等,后者会根据在CMakeLists.txt中的定义,将这些库、可执行文件,安装到你指定的路径中!
CMakeLists.txt内容为
1 | cmake_minimum_required(VERSION 3.0) |

根据上图可以清晰的看到,cmake .. -G Ninja
ninja
ninja install
后的每一步输出,ninja对应的构建文件是build.ninja,对应make的Makefile。
CMakeCache.txt里面放了一些缓存变量
cmake_install.cmake里面放了一些install的规则,比如复制、设置权限等,当你运行make install或者ninja install的时候,会自动执行里面的命令,默认的CMAKE_INSTALL_PREFIX “/usr/local”,想要执行这个文件,可以
cmake -P cmake_install.cmake