Cmake代码操作详解
1.基础CMakeLists编写操作
1.1.初始条件
CMake |-study_project |-add.c |-add.h |-main.c |-CMakeLists.txt |
1.2.CMakeLists中的基础编写
CMake cmake_minimum_required(VERSION 3.15) project(study_project) add_executable(kzxcx add.c add.h main.c) |
cmake_minimum_required:指定使用的Cmake的最低版本,代码中我们指定的版本是3。15
注:可选,非必须,如果不加可能会有警告
project:定义工程名称
add_executable:定义工程会生成一个可执行程序
CMake add_executable(可执行程序名 源文件名称) |
当有多个文件时,有两种编写方案
CMake #样式1 add_executable(kzxcx add.c add.h main.c) #样式2 add_executable(kzxcx add.c;add.h;main.c) |
1.3.执行CMake命令操作详解
CMake |-study_project |-add.c |-add.h |-main.c |-CMakeLists.txt |
将CMakeLists.txt文件编写好了之后,就可以执行cmake命令了。
CMake # cmake 命令原型 cmake CMakeLists.txt文件所在目录(用 . 来表示) # .表示当前目录,..表示上一级目录 |
当前路径:~/study_project$
CMake # 已经在CMakeLists所在路径中 cmake . # 生成的文件也在该目录(src) |
但是生成的一系列文件会混淆当前目录的文件,所以需要创建一个对应的目录,将生成的文件存入该目录
操作:mkdir build
Plain Text |-study_project |--build | |-add.c |-add.h |-main.c |-CMakeLists.txt |
然后我们进入build目录进行CMakeLists的编译生成
操作:cd build
当前路径:~/study_project/build/$
所以相对于build的目录,CMakeLists在上一级目录
CMake # CMakeLists所在目录在上一级 cmake .. # 生成的文件在该目录(build) |
在build的目录中,只要Makefile文件生成了,就没什么问题了
最后我们要通过Makefile文件生成对应的可执行程序
当前路径:~/study_project/build/$
CMake # 通过Makefile生成可执行程序 make |
于是当前列表就会有一个可执行程序kzxcx(add_executable中的可执行程序名)
操作:ls
CMake kzxcx CMakeCache.txt CMakeFiles Makefile cmake_install_cmake |
运行:./ + 可执行程序
操作:./kzxcx
2.set的使用
2.1.初始条件
CMake |-study_project |--build |- |-add.c |-add.h |-main.c |-CMakeLists.txt |
2.2.定义变量
在“1.基础CMakeLists编写操作”中,假设3个源文件需要反复被使用,用起来会比较麻烦,此时我们需要定义一个变量,将文件名对应的字符储存起来,在CMake中定义变量需使用set
CMake # set 指令的语法是: # []中的参数为可选项,如不需要可以不写 set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]]) |
VAR:变量名
VALUE:变量值
第三个:一般用不到
变量值之间的间隔同样是两种方法:1.空格隔开 2.‘ ;’号隔开2.
具体使用:
CMake cmake_minimum_required(VERSION 3.15) project(study_project) set(SUM add.c add.h main.c) #相当于将add.c,add.h,main.c 存入变量sum中 add_executable(kzxcx ${SUM}) #调用sum中的元素需要使用 ${}取变量的值 |
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 # 增加-std=C++11 set(CMAKE_CXX_STANDARD 11) # 增加-std=C++14 set(CMAKE_CXX_STANDARD 14) # 增加-std=C++17 set(CMAKE_CXX_STANDARD 17) |
2.3.2.在执行CMake命令的时候指定这个宏的值
其中‘-D’是指定宏的值
CMake # 增加-std=C++11 cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=11 |
2.4.指定输出的路径
在CMake中指定可执行程序输出的路径,也对应一个宏,叫做EXECUTABLE_OUTPUT_PATH
CMake set(HOME /home) set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin} |
第一行:定义一个变量用于储存一个路径
第二行:将拼接好的路径设置给EXECUTABLE_OUTPUT_PATH 宏
2.5.set的使用(综合上面基础)
CMake cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本 project(study_project) #项目名称 set(SUM add.c add.h main.c) # 相当于将add.c,add.h,main.c 存入变量sum中 set(CMAKE_CXX_STANDARD 11) # C++标准赋值(声明) set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径 add_executable(kzxcx ${SUM}) # 调用sum中的元素需要使用 ${}取变量的值 |
3.搜索文件
当一个项目有很多个文件时,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,就算是set的也不太现实,所以CMake提供了搜索文件的命令,可以使用aux_source_directory命令或者file命令
3.1.方式1
在CMake中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命名格式为:
CMake aux_source_directory(<dir> <variable>) |
dir:要搜索的目录
variable:将从dir目录下搜索到的源文件(.c,.cpp)列表储存到该变量(<variable>)中
3.2.方式2
在CMake中使用file
CMake file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型) |
GLOB:(搜索当前目录)将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其储存到变量中
GLOB_RECURSE:(搜索当前目录以及它的子目录)递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
事例:
CMake file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找src目录下的所以cpp文件并存入MAIN_SRC file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)# 找include目录下的.h文件并存入MAIN_HEAD |
CMAKE_CURRENT_SOURCE_DIR宏表示当前访问的CMakeLists.txt文件所在路径
注:要搜索的文件路径可加双引号,也可以不加
3.3.初始条件
CMake |-study_project |--build |- |-add.c |-add.h |-main.c |-CMakeLists.txt |
3.4.搜索文件的使用(综合上面)
CMake cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本 project(study_project) #项目名称 # set(SUM add.c add.h main.c) # 相当于将add.c,add.h,main.c 存入变量sum中
aux_source_directory(${PROJECT_SOURCE_DIR} SRC) # 搜索当前CMakeLists的路径 存入的变量(宏) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)# 找到CMakeLists的目录下的所有cpp文件并存入MAIN_SRC
set(CMAKE_CXX_STANDARD 11) # C++标准赋值(声明) set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径 add_executable(kzxcx ${SRC}) # 调用SRC中的元素需要使用 ${}取变量的值 |
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 |-study_project |--build |- |--include |-add.h |--src |-add.c |-main.c |-CMakeLists.txt |
现在CMakeLists.txt在src外,需要进行调整
CMake cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本 project(study_project) #项目名称 # set(SUM add.c add.h main.c) # 相当于将add.c,add.h,main.c 存入变量sum中
# aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC # set(CMAKE_CXX_STANDARD 11) # C++标准赋值(声明)
set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径 add_executable(kzxcx ${SRC}) # 调用SRC中的元素需要使用 ${}取变量的值 |
由于现在add.h头文件不与代码在同一个目录,add.c代码的头文件(add.h)在查找路径的时候查找不到,编译运行时会发生错误。这时我们需要将源文件的头文件路径指定出来,这样才能保证编译过程中编译器能找到这些头文件。
在CMake设置要包含的目录,通过include_directories就行。例:
CMake include_directories(headpath) |
4.2.包含头文件路径使用(综合上面)
CMake cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本 project(study_project) #项目名称 # set(SUM add.c add.h main.c) # 相当于将add.c,add.h,main.c 存入变量sum中
# aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC # set(CMAKE_CXX_STANDARD 11) # C++标准赋值(声明) include_directories(${PROJECT_SOURCE_DIR}/include) #指定头文件的目录
set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径 add_executable(kzxcx ${SRC}) # 调用SRC中的元素需要使用 ${}取变量的值 |
5.制作库文件
5.1.制作动态库
动态库:在程序编译时,并不会被放到连接的目标代码中,而是在程序运行时载入,程序运行时还需动态库。
动态库也叫共享库,目的是减少目标文件的大小
CMake add_library(库名称 SHARED 源文件1 [源文件2] ...) |
动态库分为三部分:lib + 库名字 + .so,此处只需要指定库的名字就可以,另外两个会在生成该文件时候自动填充。
当前初始条件
CMake |-build |-CMakeLists.txt |--include |-head.h |-main.cpp |--src |-add.cpp |-div.cpp |-mult.cpp |-sub.cpp |
CMake cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本 project(study_project) #项目名称 # set(SUM add.c add.h main.c) # 相当于将add.c,add.h,main.c 存入变量sum中 # aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC # set(CMAKE_CXX_STANDARD 11) # C++标准赋值(声明) include_directories(${PROJECT_SOURCE_DIR}/include) #指定头文件的目录 # set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径 # add_executable(kzxcx ${SRC}) # 调用SRC中的元素需要使用 ${}取变量的值
add_library(calc SHARED ${SRC}) |
经过cmake生成Makefile再经过make后最后生成的是libcalc.so
动态库有可执行权限
5.2.制作静态库
静态库:静态库在程序编译时,会被连接到目标代码中,程序运行时,将不再需要静态库
CMake add_library(库名称 STATIC 源文件1 [源文件2] ...) |
静态库分为三部分:lib + 库名字 + .a,此处只需要指定库的名字就可以,另外两个会在生成该文件时候自动填充。
静态库制作与动态库相似,只要把SHARED改为STATIC就行,最后生成的文件时的文件是libcalc.a
静态库没有可执行权限
5.3.指定库的生成路径
通过set命令给EXECUTABLE_OUT_PATH宏设置一个路径,这个路径就是可执行文件生成的路径
EXECUTABLE_OUT_PLAIN宏:这个宏是可执行文件的路径,静态库不行
LIBRARY_OUTPUT_PATH宏:这个静态和动态都通用
CMake cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本 project(study_project) #项目名称 # set(SUM add.c add.h main.c) # 相当于将add.c,add.h,main.c 存入变量sum中 # aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC # set(CMAKE_CXX_STANDARD 11) # C++标准赋值(声明) include_directories(${PROJECT_SOURCE_DIR}/include) #指定头文件的目录 # set(EXECUTABLE_OUTPUT_PATH /home/setable/aa/bb/cc} # 输出到/home/setable/aa/bb/cc路径 # add_executable(kzxcx ${SRC}) # 调用SRC中的元素需要使用 ${}取变量的值 set(LIABRARY_OUTPUT_PATH home/hafisdf/fhadsh/) # 如果右边的路径不存在,会将路径先生成出来 add_library(calc SHARED ${SRC}) |
5.4.总结
动态库文件和静态库文件本质都是二进制文件,一个有可执行权限,一个没有,一个是程序运行时共享,一个是编译时存入,一个节省文件空间,一个运行不需要库。
6.在程序中链接库
6.1.链接静态库
当前初始条件
CMake |-build |-CMakeLists.txt |--include |-head.h |-main.cpp |--lib1 |-libcalc.a # 静态库 |--lib2 |-libcalc.so # 动态库 |
链接静态库
CMake link_libraries(<static lib> [stdtic lib> ...]) |
参数1:可以是全名libxxx.a
也可以是xxx
参数2-N:要链接其他库的名称
自己制作或者是第三方制作的链接库可能出现静态库找不到的情况,我们可以将静态库的路径也指定出来
CMake link_directories(<lib path>) # 指定库的路径 |
修改后的CMakeLists文件内容如下:
CMake cmake_minimum_required(VERSION 3.15)# CMake使用的最低版本 project(study_project) #项目名称 # set(SUM add.c add.h main.c) # 相当于将add.c,add.h,main.c 存入变量sum中 # aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC) # 搜索当前CMakeLists的src目录的路径 存入的变量(宏) file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)# 找study_project项目src目录下的所有cpp文件并存入MAIN_SRC # set(CMAKE_CXX_STANDARD 11) # C++标准赋值(声明) include_directories(${PROJECT_SOURCE_DIR}/include) # 指定头文件的目录 link_libraries(calc) # 链接静态库 link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib1) # 把静态库路径显示出来 add_executable(kzxcx ${SRC}) |
怎么把库文件的代码也追加到SRC中?就是经过link_libraries链接,在生成可执行文件时,会把源文件和库文件放在一起最终生成可执行程序。使用静态库最终与源文件都会被打包到可执行程序里面去。
6.2.链接动态库
cmake中链接动态库的命令如下:
CMake target_link_libraries( <target> <PRIVATE [PUBLIC]INTERFACE> <item>.. [<PRIVATE [PUBLIC]INTERFACE> <item>..]..) |
targe:指要要加载动态链接库的文件名字
该文件可能是一个源文件
该文件可能是一个动态库文件
该文件可能是一个可执行文件
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
CMake target_link_libraries(A B C) target_link_libraries(D A) |
假如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 cmake_minimum_required(VERSION 3.0) project(study_project) file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include_directories(${PROJECT_SOURCE_DIR}/include) # 添加头文件路径 # 声明链接库的路径 link_directories(${PROJECT_SOURCE_DIR}/lib2) # 添加并指定最终生成的可执行程序名 add_executable(app ${SRC_LIST}) # 指定可执行程序要链接的动态库名字 target_link_libraries(app pthread) |
在target_link_libraries(app pthread)中:
app: 对应的是最终生成的可执行程序的名字
pthread:这是可执行程序要加载的动态库,这个库是系统提供的线程库,全名为libpthread.so,在指定的时候一般会掐头(lib)去尾(.so)。
6.3.总结使用
CMake $ tree . ├── build ├── CMakeLists.txt ├── include │ └── head.h # 动态库对应的头文件 ├── lib │ └── libcalc.so # 自己制作的动态库文件 └── main.cpp # 测试用的源文件 |
假设在测试文件main.cpp中既使用了自己制作的动态库libcalc.so又使用了系统提供的线程库,此时CMakeLists.txt文件可以这样写:
CMake cmake_minimum_required(VERSION 3.0) project(TEST) file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) include_directories(${PROJECT_SOURCE_DIR}/include) add_executable(app ${SRC_LIST}) target_link_libraries(app pthread calc) |
在第六行中,pthread、calc都是可执行程序app要链接的动态库的名字。当可执行程序app生成之后并执行该文件,会提示有如下错误信息:
CMake $ ./app ./app: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory |
这是因为可执行程序启动之后,去加载calc这个动态库,但是不知道这个动态库被放到了什么位置,这需要link_directories(path)
CMake cmake_minimum_required(VERSION 3.0) project(TEST) file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) # 指定源文件或者动态库对应的头文件路径 include_directories(${PROJECT_SOURCE_DIR}/include) # 指定要链接的动态库的路径 link_directories(${PROJECT_SOURCE_DIR}/lib) # 添加并生成一个可执行程序 add_executable(app ${SRC_LIST}) # 指定要链接的动态库 target_link_libraries(app pthread calc) |
7.cmake日志输出
在CMake中可以用用户显示一条消息,该命令的名字为message:
CMake message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...) |
(无) :重要消息
STATUS :非重要消息
WARNING:CMake 警告, 会继续执行
AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR:CMake 错误, 终止所有处理过程
CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。
CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符
CMake # 输出一般日志信息 message(STATUS "source path: ${PROJECT_SOURCE_DIR}") # 输出警告信息 message(WARNING "source path: ${PROJECT_SOURCE_DIR}") # 输出错误信息 message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}") |
8.变量操作
8.1 追加
使用set拼接
CMake set(变量名1 ${变量名1} ${变量名2} ...) |
例:
CMake cmake_minimum_required(VERSION 3.0) project(TEST) set(TEMP "hello,world") file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp) file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp) # 追加(拼接) set(SRC_1 ${SRC_1} ${SRC_2} ${TEMP}) message(STATUS "message: ${SRC_1}") |
使用list拼接
CMake list(APPEND <list> [<element> ...]) |
list命令的功能比set要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND表示进行数据追加,后边的参数和set就一样了。
CMake cmake_minimum_required(VERSION 3.0) project(TEST) set(TEMP "hello,world") file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/src1/*.cpp) file(GLOB SRC_2 ${PROJECT_SOURCE_DIR}/src2/*.cpp) # 追加(拼接) list(APPEND SRC_1 ${SRC_1} ${SRC_2} ${TEMP}) message(STATUS "message: ${SRC_1}") |
在CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;分割的一组字符串。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e,但是最终打印变量值的时候得到的是abcde。
CMake set(tmp1 a;b;c;d;e) set(tmp2 a b c d e) message(${tmp1}) message(${tmp2}) |
结果:
8.2 字符串移除
我们在通过file搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,比如:
CMake $ tree . ├── add.cpp ├── div.cpp ├── main.cpp ├── mult.cpp └── sub.cpp
0 directories, 5 files |
在当前这个目录有五个源文件,其中main.cpp是一个测试文件。如果我们想要把计算器相关的源文件生成一个动态库给别人使用,那么只需要add.cpp、div.cp、mult.cpp、sub.cpp这四个源文件就可以了。此时,就需要将main.cpp从搜索到的数据中剔除出去,想要实现这个功能,也可以使用list
CMake list(REMOVE_ITEM <list> <value> [<value> ...]) |
通过上面的命令原型可以看到删除和追加数据类似,只不过是第一个参数变成了REMOVE_ITEM。
CMake cmake_minimum_required(VERSION 3.0) project(TEST) set(TEMP "hello,world") file(GLOB SRC_1 ${PROJECT_SOURCE_DIR}/*.cpp) # 移除前日志 message(STATUS "message: ${SRC_1}") # 移除 main.cpp list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp) # 移除后日志 message(STATUS "message: ${SRC_1}") |
可以看到,在第8行把将要移除的文件的名字指定给list就可以了。但是一定要注意通过 file 命令搜索源文件的时候得到的是文件的绝对路径(在list中每个文件对应的路径都是一个item,并且都是绝对路径),那么在移除的时候也要将该文件的绝对路径指定出来才可以,否是移除操作不会成功.
list命令还有其它功能
1.获取 list 的长度。
CMake list(LENGTH <list> <output variable>) |
LENGTH:子命令LENGTH用于读取列表长度
<list>:当前操作的列表
<output variable>:新创建的变量,用于存储列表的长度。
2.读取列表中指定索引的的元素,可以指定多个索引
CMake list(GET <list> <element index> [<element index> ...] <output variable>) |
<list>:当前操作的列表
<element index>:列表元素的索引
从0开始编号,索引0的元素为列表中的第一个元素;
索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推
当索引(不管是正还是负)超过列表的长度,运行会报错
<output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表。
3.将列表中的元素用连接符(字符串)连接起来组成一个字符串
CMake list (JOIN <list> <glue> <output variable>) |
<list>:当前操作的列表
<glue>:指定的连接符(字符串)
<output variable>:新创建的变量,存储返回的字符串
4.查找列表是否存在指定的元素,若果未找到,返回-1
CMake list(FIND <list> <value> <output variable>) |
<list>:当前操作的列表
<value>:需要在列表中搜索的元素
<output variable>:新创建的变量
如果列表<list>中存在<value>,那么返回<value>在列表中的索引
如果未找到则返回-1。
5.将元素追加到列表中
CMake list (APPEND <list> [<element> ...]) |
6.在list中指定的位置插入若干元素
CMake list(INSERT <list> <element_index> <element> [<element> ...]) |
7.将元素插入到列表的0索引位置
CMake list (PREPEND <list> [<element> ...]) |
8.将列表中最后元素移除
CMake list (POP_BACK <list> [<out-var>...]) |
9.将列表中第一个元素移除
CMake list (POP_FRONT <list> [<out-var>...]) |
10.将指定的元素从列表中移除
CMake list (REMOVE_ITEM <list> <value> [<value> ...]) |
11.将指定索引的元素从列表中移除
CMake list (REMOVE_AT <list> <index> [<index> ...]) |
12.移除列表中的重复元素
CMake list (REMOVE_DUPLICATES <list>) |
13.列表翻转
CMake list(REVERSE <list>) |
14.列表排序
CMake list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>]) |
COMPARE:指定排序方法。有如下几种值可选:
STRING:按照字母顺序进行排序,为默认的排序方法
FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
NATURAL:使用自然数顺序排序
CASE:指明是否大小写敏感。有如下几种值可选:
SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
INSENSITIVE:按照大小写不敏感方式进行排序
ORDER:指明排序的顺序。有如下几种值可选:
ASCENDING:按照升序排列,为默认值
DESCENDING:按照降序排列
9.宏定义
在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,如下所示:
C++ #include <stdio.h> #define NUMBER 3
int main() { int a = 10; #ifdef DEBUG printf("我是一个程序猿, 我不会爬树...\n"); #endif for(int i=0; i<NUMBER; ++i) { printf("hello, GCC!!!\n"); } return 0; } |
在程序的第七行对DEBUG宏进行了判断,如果该宏被定义了,那么第八行就会进行日志输出,如果没有定义这个宏,第八行就相当于被注释掉了,因此最终无法看到日志输入出(上述代码中并没有定义这个宏)。
为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来,其中一种方式就是在gcc/g++命令中去指定,如下:
CMake $ gcc test.c -DDEBUG -o app |
在gcc/g++命令中通过参数 -D指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏,其名字为DEBUG。
在CMake中我们也可以做类似的事情,对应的命令叫做add_definitions:
CMake add_definitions(-D宏名称) |
针对于上面的源文件编写一个CMakeLists.txt,内容如下:
CMake cmake_minimum_required(VERSION 3.0) project(TEST) # 自定义 DEBUG 宏 add_definitions(-DDEBUG) add_executable(app ./test.c) |
通过这种方式,上述代码中的第八行日志就能够被输出出来了。
预处理宏
一些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 $ tree . ├── build |—— CMakeLists.txt ├── calc │ ├── add.cpp │ ├── CMakeLists.txt │ ├── div.cpp │ ├── mult.cpp │ └── sub.cpp ├── CMakeLists.txt ├── include │ ├── calc.h │ └── sort.h ├── sort │ ├── CMakeLists.txt │ ├── insert.cpp #插入排序 │ └── select.cpp #选择排序 ├── test1 │ ├── calc.cpp │ └── CMakeLists.txt └── test2 ├── CMakeLists.txt └── sort.cpp
6 directories, 15 files |
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 add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) |
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 cmake_minimum_required(VERSION 3.0) project(test) # 定义变量 # 静态库生成的路径 set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) # 测试程序生成的路径 set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin) # 头文件目录 set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include) # 静态库的名字 set(CALC_LIB calc) set(SORT_LIB sort) # 可执行程序的名字 set(APP_NAME_1 test1) set(APP_NAME_2 test2) # 添加子目录 add_subdirectory(calc) add_subdirectory(sort) add_subdirectory(test1) add_subdirectory(test2) |
在根节点对应的文件中主要做了两件事情:定义全局变量和添加子目录。
- 定义的全局变量主要是给子节点使用,目的是为了提高子节点中的CMakeLists.txt文件的可读性和可维护性,避免冗余并降低出差的概率。
- 一共添加了四个子目录,每个子目录中都有一个CMakeLists.txt文件,这样它们的父子关系就被确定下来了。
10.3.2 calc 目录
calc 目录中的 CMakeLists.txt文件内容如下:
CMake cmake_minimum_required(VERSION 3.0) project(CALCLIB) aux_source_directory(./ SRC) include_directories(${HEAD_PATH}) set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) add_library(${CALC_LIB} STATIC ${SRC}) |
- 第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 cmake_minimum_required(VERSION 3.0) project(SORTLIB) aux_source_directory(./ SRC) include_directories(${HEAD_PATH}) set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) add_library(${SORT_LIB} SHARED ${SRC}) |
- 第6行add_library:生成动态库,动态库名字SORT_LIB是在根节点文件中定义的
这个文件中的内容和calc节点文件中的内容类似,只不过这次生成的是动态库。
10.3.4 test1 目录
test1 目录中的 CMakeLists.txt文件内容如下:
CMake cmake_minimum_required(VERSION 3.0) project(CALCTEST) aux_source_directory(./ SRC) # 搜索当前目录所有源代码 include_directories(${HEAD_PATH}) # 包括头文件的路径 link_directories(${LIB_PATH}) # 包括库的路径 link_libraries(${CALC_LIB}) # 链接静态库文件 set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) # 设置输出路径 add_executable(${APP_NAME_1} ${SRC}) #名称(在根目录定义了)和代码 |
- 第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 cmake_minimum_required(VERSION 3.0) project(SORTTEST) aux_source_directory(./ SRC) include_directories(${HEAD_PATH}) set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) link_directories(${LIB_PATH}) add_executable(${APP_NAME_2} ${SRC}) target_link_libraries(${APP_NAME_2} ${SORT_LIB}) |
- 第四行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 $ cmake .. -- The C compiler identification is GNU 5.4.0 -- The CXX compiler identification is GNU 5.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/robin/abc/cmake/calc/build |
可以看到在build目录中生成了一些文件和目录,如下所示:
CMake $ tree build -L 1 build ├── calc # 目录 ├── CMakeCache.txt # 文件 ├── CMakeFiles # 目录 ├── cmake_install.cmake # 文件 ├── Makefile # 文件 ├── sort # 目录 ├── test1 # 目录 └── test2 # 目录 |
然后在build 目录下执行make 命令:
![](https://img-blog.csdnimg.cn/direct/22a4444f6c7b4beba8177429203622b7.png)
通过上图可以得到如下信息:
- 在项目根目录的lib目录中生成了静态库libcalc.a
- 在项目根目录的lib目录中生成了动态库libsort.so
- 在项目根目录的bin目录中生成了可执行程序test1
- 在项目根目录的bin目录中生成了可执行程序test2
CMake $ tree bin/ lib/ bin/ ├── test1 └── test2 lib/ ├── libcalc.a └── libsort.so |
至此,项目构建完毕.
11.流程控制
https://www.subingwen.cn/cmake/CMake-advanced/
11.1 判断条件
11.2 循环