Cmake代码操作详解
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC。file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC。就是经过link
Cmake代码操作详解
1.基础CMakeLists编写操作
1.1.初始条件
CMake |
1.2.CMakeLists中的基础编写
CMake |
cmake_minimum_required:指定使用的Cmake的最低版本,代码中我们指定的版本是3。15
注:可选,非必须,如果不加可能会有警告
project:定义工程名称
add_executable:定义工程会生成一个可执行程序
CMake |
当有多个文件时,有两种编写方案
CMake |
1.3.执行CMake命令操作详解
CMake |
将CMakeLists.txt文件编写好了之后,就可以执行cmake命令了。
CMake |
当前路径:~/study_project$
CMake |
但是生成的一系列文件会混淆当前目录的文件,所以需要创建一个对应的目录,将生成的文件存入该目录
操作:mkdir build
Plain Text |
然后我们进入build目录进行CMakeLists的编译生成
操作:cd build
当前路径:~/study_project/build/$
所以相对于build的目录,CMakeLists在上一级目录
CMake |
在build的目录中,只要Makefile文件生成了,就没什么问题了
最后我们要通过Makefile文件生成对应的可执行程序
当前路径:~/study_project/build/$
CMake |
于是当前列表就会有一个可执行程序kzxcx(add_executable中的可执行程序名)
操作:ls
CMake |
运行:./ + 可执行程序
操作:./kzxcx
2.set的使用
2.1.初始条件
CMake |
2.2.定义变量
在“1.基础CMakeLists编写操作”中,假设3个源文件需要反复被使用,用起来会比较麻烦,此时我们需要定义一个变量,将文件名对应的字符储存起来,在CMake中定义变量需使用set
CMake |
VAR:变量名
VALUE:变量值
第三个:一般用不到
变量值之间的间隔同样是两种方法:1.空格隔开 2.‘ ;’号隔开2.
具体使用:
CMake |
2.3.指定使用的C++标准
在实际C++使用中,我们可能会用到C++11,C++14,C++17,C++20等新特性,像auto的是C++11中的新特性,不指定C++11的标准的话,编译器会默认使用C++98的标准,导致编译出错
决定C++版本的宏是CMAKE_CXX_STANDARD
2.3.1.在CMakeLists中通过set命令指定
CMake |
2.3.2.在执行CMake命令的时候指定这个宏的值
其中‘-D’是指定宏的值
CMake |
2.4.指定输出的路径
在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH
CMake |
第一行:定义一个变量用于储存一个路径
第二行:将拼接好的路径设置给EXECUTABLE_OUTPUT_PATH 宏
2.5.set的使用(综合上面基础)
CMake |
3.搜索文件
当一个项目有很多个文件时,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,就算是set的也不太现实,所以CMake提供了搜索文件的命令,可以使用aux_source_directory命令或者file命令
3.1.方式1
在CMake中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命名格式为:
CMake |
dir:要搜索的目录
variable:将从dir目录下搜索到的源文件(.c,.cpp)列表储存到该变量(<variable>)中
3.2.方式2
在CMake中使用file
CMake |
GLOB:(搜索当前目录)将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其储存到变量中
GLOB_RECURSE:(搜索当前目录以及它的子目录)递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
事例:
CMake |
CMAKE_CURRENT_SOURCE_DIR宏表示当前访问的CMakeLists.txt文件所在路径
注:要搜索的文件路径可加双引号,也可以不加
3.3.初始条件
CMake |
3.4.搜索文件的使用(综合上面)
CMake |
PROJECT_SOURCE_DIR 宏:是指执行cmake命令时后面携带的路径,该路径与CMakeLists一致,所以在搜索时后面不添加路径,与CMAKE_CURRENT_SOURCE_DIR差不多
CMAKE_CURRENT_SOURCE_DIR 宏:当前CMakeLists的路径
file中的GLOB:搜索当前目录
file中的GLOB_RECURSE:搜索当前目录以及它的子目录
4.(包含头文件)指定头文件路径
4.1.初始条件
操作:mkdir src include
并将代码存入src中,头文件存入include,这样项目study_project就有三个子目录,include,src,build
CMake |
现在CMakeLists.txt在src外,需要进行调整
CMake |
由于现在add.h头文件不与代码在同一个目录,add.c代码的头文件(add.h)在查找路径的时候查找不到,编译运行时会发生错误。这时我们需要将源文件的头文件路径指定出来,这样才能保证编译过程中编译器能找到这些头文件。
在CMake设置要包含的目录,通过include_directories就行。例:
CMake |
4.2.包含头文件路径使用(综合上面)
CMake |
5.制作库文件
5.1.制作动态库
动态库:在程序编译时,并不会被放到连接的目标代码中,而是在程序运行时载入,程序运行时还需动态库。
动态库也叫共享库,目的是减少目标文件的大小
CMake |
动态库分为三部分:lib + 库名字 + .so,此处只需要指定库的名字就可以,另外两个会在生成该文件时候自动填充。
当前初始条件
CMake |
CMake |
经过cmake生成Makefile再经过make后最后生成的是libcalc.so
动态库有可执行权限
5.2.制作静态库
静态库:静态库在程序编译时,会被连接到目标代码中,程序运行时,将不再需要静态库
CMake |
静态库分为三部分:lib + 库名字 + .a,此处只需要指定库的名字就可以,另外两个会在生成该文件时候自动填充。
静态库制作与动态库相似,只要把SHARED改为STATIC就行,最后生成的文件时的文件是libcalc.a
静态库没有可执行权限
5.3.指定库的生成路径
通过set命令给EXECUTABLE_OUT_PATH宏设置一个路径,这个路径就是可执行文件生成的路径
EXECUTABLE_OUT_PLAIN宏:这个宏是可执行文件的路径,静态库不行
LIBRARY_OUTPUT_PATH宏:这个静态和动态都通用
CMake |
5.4.总结
动态库文件和静态库文件本质都是二进制文件,一个有可执行权限,一个没有,一个是程序运行时共享,一个是编译时存入,一个节省文件空间,一个运行不需要库。
6.在程序中链接库
6.1.链接静态库
当前初始条件
CMake |
链接静态库
CMake |
参数1:可以是全名libxxx.a
也可以是xxx
参数2-N:要链接其他库的名称
自己制作或者是第三方制作的链接库可能出现静态库找不到的情况,我们可以将静态库的路径也指定出来
CMake |
修改后的CMakeLists文件内容如下:
CMake |
怎么把库文件的代码也追加到SRC中?就是经过link_libraries链接,在生成可执行文件时,会把源文件和库文件放在一起最终生成可执行程序。使用静态库最终与源文件都会被打包到可执行程序里面去。
6.2.链接动态库
cmake中链接动态库的命令如下:
CMake |
targe:指要要加载动态链接库的文件名字
该文件可能是一个源文件
该文件可能是一个动态库文件
该文件可能是一个可执行文件
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
CMake |
假如A,B,C,D都是公共的动态库,那么调用D库时就可以用D,A,B,C的库,B,C会链接到A,A会链接到D
假如B,C是私有的动态库,那么调用A(B,C只会传递一次)的可以调用B,C,调用D的时候不能调用B,C
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。
动态库的链接和静态库是完全不同的:
静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存
因此,在cmake中指定要链接的动态库的时候,应该将命令写到生成了可执行文件之后:
CMake |
在target_link_libraries(app pthread)中:
app: 对应的是最终生成的可执行程序的名字
pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。
6.3.总结使用
CMake |
假设在测试文件main.cpp中既使用了自己制作的动态库libcalc.so又使用了系统提供的线程库,此时CMakeLists.txt文件可以这样写:
CMake |
在第六行中,pthread、calc都是可执行程序app要链接的动态库的名字。当可执行程序app生成之后并执行该文件,会提示有如下错误信息:
CMake |
这是因为可执行程序启动之后,去加载calc这个动态库,但是不知道这个动态库被放到了什么位置,这需要link_directories(path)
CMake |
7.cmake日志输出
在CMake中可以用用户显示一条消息,该命令的名字为message:
CMake |
(无) :重要消息
STATUS :非重要消息
WARNING:CMake 警告, 会继续执行
AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR:CMake 错误, 终止所有处理过程
CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。
CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符
CMake |
8.变量操作
8.1 追加
使用set拼接
CMake |
例:
CMake |
使用list拼接
CMake |
list命令的功能比set要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND表示进行数据追加,后边的参数和set就一样了。
CMake |
在CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;分割的一组字符串。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e,但是最终打印变量值的时候得到的是abcde。
CMake |
结果:
CMake |
8.2 字符串移除
我们在通过file搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,比如:
CMake |
在当前这个目录有五个源文件,其中main.cpp是一个测试文件。如果我们想要把计算器相关的源文件生成一个动态库给别人使用,那么只需要add.cpp、div.cp、mult.cpp、sub.cpp这四个源文件就可以了。此时,就需要将main.cpp从搜索到的数据中剔除出去,想要实现这个功能,也可以使用list
CMake |
通过上面的命令原型可以看到删除和追加数据类似,只不过是第一个参数变成了REMOVE_ITEM。
CMake |
可以看到,在第8行把将要移除的文件的名字指定给list就可以了。但是一定要注意通过 file 命令搜索源文件的时候得到的是文件的绝对路径(在list中每个文件对应的路径都是一个item,并且都是绝对路径),那么在移除的时候也要将该文件的绝对路径指定出来才可以,否是移除操作不会成功.
list命令还有其它功能
1.获取 list 的长度。
CMake |
LENGTH:子命令LENGTH用于读取列表长度
<list>:当前操作的列表
<output variable>:新创建的变量,用于存储列表的长度。
2.读取列表中指定索引的的元素,可以指定多个索引
CMake |
<list>:当前操作的列表
<element index>:列表元素的索引
从0开始编号,索引0的元素为列表中的第一个元素;
索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推
当索引(不管是正还是负)超过列表的长度,运行会报错
<output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表。
3.将列表中的元素用连接符(字符串)连接起来组成一个字符串
CMake |
<list>:当前操作的列表
<glue>:指定的连接符(字符串)
<output variable>:新创建的变量,存储返回的字符串
4.查找列表是否存在指定的元素,若果未找到,返回-1
CMake |
<list>:当前操作的列表
<value>:需要在列表中搜索的元素
<output variable>:新创建的变量
如果列表<list>中存在<value>,那么返回<value>在列表中的索引
如果未找到则返回-1。
5.将元素追加到列表中
CMake |
6.在list中指定的位置插入若干元素
CMake |
7.将元素插入到列表的0索引位置
CMake |
8.将列表中最后元素移除
CMake |
9.将列表中第一个元素移除
CMake |
10.将指定的元素从列表中移除
CMake |
11.将指定索引的元素从列表中移除
CMake |
12.移除列表中的重复元素
CMake |
13.列表翻转
CMake |
14.列表排序
CMake |
COMPARE:指定排序方法。有如下几种值可选:
STRING:按照字母顺序进行排序,为默认的排序方法
FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
NATURAL:使用自然数顺序排序
CASE:指明是否大小写敏感。有如下几种值可选:
SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
INSENSITIVE:按照大小写不敏感方式进行排序
ORDER:指明排序的顺序。有如下几种值可选:
ASCENDING:按照升序排列,为默认值
DESCENDING:按照降序排列
9.宏定义
在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示:
C++ |
在程序的第七行对DEBUG宏进行了判断,如果该宏被定义了,那么第八行就会进行日志输出,如果没有定义这个宏,第八行就相当于被注释掉了,因此最终无法看到日志输入出(上述代码中并没有定义这个宏)。
为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来,其中一种方式就是在gcc/g++命令中去指定,如下:
CMake |
在gcc/g++命令中通过参数 -D指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,其名字为DEBUG。
在CMake中我们也可以做类似的事情,对应的命令叫做add_definitions:
CMake |
针对于上面的源文件编写一个CMakeLists.txt,内容如下:
CMake |
通过这种方式,上述代码中的第八行日志就能够被输出出来了。
预处理宏
一些CMake中常用的宏:
- 宏 功能
- PROJECT_SOURCE_DIR 使用cmake命令后紧跟的目录,一般是工程的根目录
- PROJECT_BINARY_DIR 执行cmake命令的目录
- CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径
- CMAKE_CURRENT_BINARY_DIR target 编译目录
- EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置
- LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置
- PROJECT_NAME 返回通过PROJECT指令定义的项目名称
- CMAKE_BINARY_DIR 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径
10.嵌套的CMake
如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。
10.1 前提条件
先来看一下下面的这个的目录结构:
CMake |
include 目录:头文件目录
calc 目录:目录中的四个源文件对应的加、减、乘、除算法
对应的头文件是include中的calc.h
sort 目录 :目录中的两个源文件对应的是插入排序和选择排序算法
对应的头文件是include中的sort.h
test1 目录:测试目录,对加、减、乘、除算法进行测试
test2 目录:测试目录,对排序算法进行测试
10.2 准备工作
众所周知,Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:
- 根节点CMakeLists.txt中的变量全局有效
- 父节点CMakeLists.txt中的变量可以在子节点中使用
- 子节点CMakeLists.txt中的变量只能在当前节点中使用
接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:
CMake |
source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录
binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显示构建在子路径下的目标。
通过这种方式CMakeLists.txt文件之间的父子关系就被构建出来了。
10.3 解决问题
在上面的目录中我们要做如下事情:
通过 test1 目录中的测试文件进行计算器相关的测试
通过 test2 目录中的测试文件进行排序相关的测试
现在相当于是要进行模块化测试,对于calc和sort目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式。
10.3.1 根目录
根目录中的 CMakeLists.txt文件内容如下:
CMake |
在根节点对应的文件中主要做了两件事情:定义全局变量和添加子目录。
- 定义的全局变量主要是给子节点使用,目的是为了提高子节点中的CMakeLists.txt文件的可读性和可维护性,避免冗余并降低出差的概率。
- 一共添加了四个子目录,每个子目录中都有一个CMakeLists.txt文件,这样它们的父子关系就被确定下来了。
10.3.2 calc 目录
calc 目录中的 CMakeLists.txt文件内容如下:
CMake |
- 第3行aux_source_directory:搜索当前目录(calc目录)下的所有源文件
- 第4行include_directories:包含头文件路径,HEAD_PATH是在根节点文件中定义的
- 第5行set:设置库的生成的路径,LIB_PATH是在根节点文件中定义的
- 第6行add_library:生成静态库,静态库名字CALC_LIB是在根节点文件中定义的
10.3.3 sort 目录
sort 目录中的 CMakeLists.txt文件内容如下:
CMake |
- 第6行add_library:生成动态库,动态库名字SORT_LIB是在根节点文件中定义的
这个文件中的内容和calc节点文件中的内容类似,只不过这次生成的是动态库。
10.3.4 test1 目录
test1 目录中的 CMakeLists.txt文件内容如下:
CMake |
- 第4行include_directories:指定头文件路径,HEAD_PATH变量是在根节点文件中定义的
- 第6行link_libraries:指定可执行程序要链接的静态库,CALC_LIB变量是在根节点文件中定义的
- 第7行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
- 第8行add_executable:生成可执行程序,APP_NAME_1变量是在根节点文件中定义的
此处的可执行程序链接的是静态库,最终静态库会被打包到可执行程序中,可执行程序启动之后,静态库也就随之被加载到内存中了。
10.3.5 test2 目录
test2 目录中的 CMakeLists.txt文件内容如下:
CMake |
- 第四行include_directories:包含头文件路径,HEAD_PATH变量是在根节点文件中定义的
- 第五行set:指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
- 第六行link_directories:指定可执行程序要链接的动态库的路径,LIB_PATH变量是在根节点文件中定义的
- 第七行add_executable:生成可执行程序,APP_NAME_2变量是在根节点文件中定义的
- 第八行target_link_libraries:指定可执行程序要链接的动态库的名字
10.3.6 构建项目
一切准备就绪之后,开始构建项目,进入到根节点目录的build 目录中,执行cmake 命令,如下:
CMake |
可以看到在build目录中生成了一些文件和目录,如下所示:
CMake |
然后在build 目录下执行make 命令:
通过上图可以得到如下信息:
- 在项目根目录的lib目录中生成了静态库libcalc.a
- 在项目根目录的lib目录中生成了动态库libsort.so
- 在项目根目录的bin目录中生成了可执行程序test1
- 在项目根目录的bin目录中生成了可执行程序test2
CMake |
至此,项目构建完毕.
11.流程控制
https://www.subingwen.cn/cmake/CMake-advanced/
11.1 判断条件
11.2 循环
更多推荐
所有评论(0)