概述
CMake是一个跨平台的项目构建工具。CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。
具体的项目构建流程如下图所示:

CMake中的预定义宏
宏 | 含义 |
---|---|
PROJECT_SOURCE_DIR | 一般是工程根目录 |
PROJECT_BINARY_DIR | 执行cmake命令的目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的CMakeList.txt所在的目录 |
CMAKE_CURRENT_BINARY_DIR | target编译目录 |
EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制的存放位置 |
LIBRARY_OUTPUT_PATH | 重新定义目标链接文件的存放位置 |
PROJECT_NAME | 返回通过PROJECT指令定义的项目名称 |
CMAKE_BINARY_DIR | 项目实际构建路径,比如在build目录进行的构建,那么就是build目录的路径 |
CMake的使用
- 注释
shell
# 这是一个 CMakeLists.txt 文件,
# CMake使用#来注释,可以放在任意位置
cmake_minimum_required(VERSION 3.10.0)
- 注释块
lua
#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
# 使用#[[]]进行块注释
cmake_minimum_required(VERSION 3.10.0)
假设有以下文件a.cpp、b.cpp、main.cpp,可以再上述源文件所在目录添加一个新的CMakeList.txt文件。在文件中写:
scss
cmake_minimum_required(VERSION 3.10) #指定最低的CMake版本,可选,不写可能会报错
project(test_project_name) #定义工程的名称
add_executable(TEST a.cpp b.cpp main.cpp) #定义工程会生成一个可执行程序名字
通过project来设置工程属性:
ini
# PROJECT 指令的语法是:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]] #工程版本
[DESCRIPTION <project-description-string>] #工程描述
[HOMEPAGE_URL <url-string>] #web主页地址
[LANGUAGES <language-name>...]) #支持语言(默认支持所有语言)
#工程名称是必须的,其他属性是非必须的
通过add_executable来生成可执行程序:
css
add_executable(可执行程序名 源文件名称) #可执行程序名和工程名并没有直接关系,多个源文件可以使用空格或者;隔开,比如
add_executable(TEST a.cpp b.cpp main.cpp)
add_executable(TEST a.cpp;b.cpp;main.cpp)
执行cmake指令之后,会生成makefile文件,再执行make指令,就会生成可执行程序TEST。
一般项目中有很多源文件,在编写CMakeLists时需要使用到这些文件的名称,这样一个个添加文件名称是一件很麻烦的事情。因此,可以定义一个变量,将文件名对应的字符串存储到变量中,比如:
bash
# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写,var为变量名,value为变量值
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
# 方式1: 各个源文件之间使用空格或者;间隔
set(SRC_LIST add.c div.c main.c mult.c sub.c)
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
# 通过变量名来使用所有源文件
add_executable(app ${SRC_LIST})
#通过set指定C++标准
#CMake中CMAKE_CXX_STANDARD是对应的C++标准的宏
set(CMAKE_CXX_STANDARD 11)#指定C++11
set(CMAKE_CXX_STANDARD 14)#。。。
set(CMAKE_CXX_STANDARD 17)#。。。
#或者,在执行cmake指令编译时也可以添加宏
cmake -DCMAKE_CXX_STANDARD=11
#通过set来设置输出路径
#CMake中输出路径对应的宏为EXECUTABLE_OUTPUT_PATH
set(HOME /home/work/test) #定义变量HOME,代表路径/home/work/test
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin) #指定输出路径为/home/work/test/bin,如果指定路径不存在,会自动创建并输出到此处
- 搜索文件
如果一个项目中有很多源文件,这样将文件一个个罗列出来是一件很麻烦的事,可以通过一些命令来自动查找指定文件。
通过aux_source_directory关键字搜索文件:
bash
aux_source_directory(<dir> <var>) #在dir路径下搜索源文件,并存储到变量var中
#比如
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
#CMAKE_CURRENT_SOURCE_DIR是CMake中的宏,表示当前CMakeList.txt文件所在的绝对路径
#表示在当前CMakeList所在路径的src文件夹中查找所有的源文件,并保存到SRC_LIST变量中,后续可以通过SRC_LIST来访问所有的源文件,而不需要一个个罗列出来
通过file关键字搜索文件:
bash
file(GLOB/GLOB_RECURSE 变量名 路径和文件类型)
#GLOB表示将搜索到的文件生成列表保存到变量中,GLOB_RECURSE表示递归搜索并保存
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) #查找当前CMakeList所在目录的src下的所有cpp文件,保存到MAIN_SRC中
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) #查找当前CMakeList所在目录的include下的所有h文件,保存到MAIN_HEAD中
#对于要搜索的文件路径和类型,可加双引号也可不加,比如
file(GLOB MAIN_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
- 包含头文件
通过include_directories关键字设置头文件路径:
scss
include_directories(文件路径)
include_directories(${PROJECT_SOURCE_DIR}/include) #设置头文件的路径是当前工程根目录的include文件夹下
#PROJECT_SOURCE_DIR是如果首次使用,代表工程的根目录,如果在其他路径下的CMakeList中使用project(),会更新为对应的路径
- 制作动态库或者静态库
scss
#制作库的时候通过STATIC、SHARED来区别静、动态库
add_library(库名称 STATIC 源文件1、2、3) #制作静态库
add_library(库名称 SHARED 源文件1、2、3) #制作动态库
#对于动态库而言,Linux下生成的动态库是默认有执行权限的,所以可以通过EXECUTABLE_OUTPUT_PATH宏指定动态库的输出路径,比如
set(EXECUTABLE_OUTPUT_PATH output_path)
#对于静态库而言,默认是没有可执行权限的,所以不能使用EXECUTABLE_OUTPUT_PATH宏,而应该使用LIBRARY_OUTPUT_PATH
set( LIBRARY_OUTPUT_PATH output_path)
add_library(lib_name STATIC 源文件1、2、3)
add_library(lib_name SHARED 源文件1、2、3)#动态库同样适用这种方式
- 链接库
库制作好之后,其他程序通过链接库,来使用库提供的方法:
scss
link_libraries(静态库1、2、3) #可以使用全名libxxx.a或者xxx
#如果链接的库不是系统提供的,是自定义的或者第三方提供的,可能找不到库,可以指定静态库的路径
link_directories(库文件路径)
vbnet
target_link_libraries(链接目标 动态库1、2、3)
#链接目标是可执行文件、库或者源文件
#链接动态库可以单独设置访问权限PUBLIC\PRIVATE\INTERFACE,默认为PUBLIC,
#PUBLIC表示其后的库会被链接到target中,并且里面的符号也会被导出,提供给第三方使用,依赖项不仅用于当前目标的实现,也会传递给以来当前目标的其他目标
#PRIVATE表示其后的库会被链接到target中,但是第三方不能感知到调用了什么库,依赖项仅用于当前目标,若是其他目标引用了当前目标中某些函数,会导致错误
target_link_library(test PRIVATE lib_a)
-
静态库会在生成可执程序的链接阶段被打包到可执行程序中,所在可执行程序启动,静态库就被加载到内存中。
-
动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序启动并且调用了动态库中的函数时,才会白加载到内存中。
因此,在cmake中指定链接的动态库时,应该将命令写到生成可执行文件之后:
scss
add_executable(app ${SRC_LIST}) #生成可执行程序
target_link_libraries(app pthread) #链接动态库
暂时无法在飞书文档外展示此内容
- 宏定义
在代码中会经常添加一些宏定义,通过这些宏来控制对应的代码是否生效,比如以下代码:
c
int main()
{
#ifdef TEST
cout<<"1111111"<<endl;
#endif
cout<<"2222222"<<endl;
}
//如果宏TEST被定义了,就会输出111111 2222222,否则,111111哪行代码不会被执行
//可以通过命令行来定义这个宏,比如编译时 g++ test.cpp -DTEST -o test
//-D参数指定要定义的红,即TEST
bash
#通过CMakeLists.txt来定义宏
add_definitions(-DTEST) #定义宏TEST
-
嵌套的CMake
在日常学习、工作中,很多大型项目为了模块化,都是分模块来进行开发。一个大型项目中,会定义各种功能模块,每个功能模块之间是相对独立的,可以独立进行编译,这样项目的健壮性、可维护性都会更好。在这种情况下,可以通过嵌套的CMake来进行管理。
Linux的目录是树状结构,嵌套的CMake也是一个树状结构,最顶层的CMakeLists.txt是根节点,其次都是子节点。 根节点中的变量全局有效,父节点的变量可以在子节点中使用,子节点中的变量只能在当前节点使用。
举例说明,假如有以下目录结构,lib1和lib2是分别提供一些方法,导出为库,test1和test2链接库
objectivec
├── build
├── CMakeLists.txt
├── include
│ └── test.h
├── lib1
│ ├── lib1.cpp
│ ├── lib1.h
│ └── CMakeLists.txt
├── lib2
│ ├── lib2.cpp
│ ├── lib2.h
│ └── CMakeLists.txt
├── test1
│ ├── test1.cpp
│ └── CMakeLists.txt
└── test2
├── test2.cpp
└── CMakeLists.txt
- 添加子目录
less
add_subdirectory(source_dir binary_dir EXCLUDE_FROM_ALL)
#source_dir指定了CMakeList源文件,也就是指定了子目录
#binary_dir指定了输出文件的路径,一般不需要指定
#EXCLUDE_FROM_ALL在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标
顶层CMakelists.txt编写:
scss
cmake_minimum_required(VERSION 3.10)
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(LIB_1 lib1) #设置静态库名字
set(LIB_2 lib2)
set(APP_1 test1) #设置可执行程序名字
set(APP_2 test2)
add_subdirectory(lib1) #设置子目录
add_subdirectory(lib2)
add_subdirectory(test1)
add_subdirectory(test2)
将lib1制作为静态库:
scss
cmake_minimum_required(VERSION 3.10)
project(LIB_1)
aux_source_directory(./ SRC) #搜索当前目录下的所有文件,存到变量SRC
include_directories(${HEAD_PATH}) #设置头文件目录,在根目录下的CMakeList中定义了改变量
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${LIB_1} STATIC ${SRC}) #生成静态库,名字也是在根目录的CMakeList中定义的
target_include_directories(${LIB_1} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) #指定头文件目录,同时设置为PUBLIC,其他连接到lib1的程序可以在这个路径下找到lib1的头文件
#lib2的CMakeLists写法类似,不过制作为动态库
test1目录下的CMakelists.txt编写:
scss
cmake_minimum_required(VERSION 3.0)
project(TEST_1)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
link_directories(${LIB_PATH})
link_libraries(${LIB_1})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
add_executable(${APP_1} ${SRC})
target_link_libraries(${APP_1} ${LIB_2})
#test2的CMakeLists写法类似
在test1.cpp文件中,需要引入lib1.h这个头文件,即可使用lib1中提供的方法与变量,使用cmake、make命令编译项目,会生成lib、bin目录,lib目录下有lib1.a、lib2.so文件,在bin目录下会有TEST_1、TEST_2可执行程序。
流程控制
scss
#条件判断
if......else......elseif......endif语句
#逻辑判断
NOT......AND......OR
#基于数值比较
LESS(小于)......GREATER(大于)......EQUAL(等于)......LESS_EQUAL(小于等于)......GREATER_EQUAL(大于等于)
#基于字符串比较
STRLESS(小于)......STRGREATER(大于)......STREQUAL(等于)......STRLESS_EQUAL(小于等于)......STRGREATER_EQUAL(大于等于)
#文件操作
if(EXISTS file_path) #文件存在返回True,不存在返回False
if(IS_DIRECTORY dir_path) #判断是不是目录,必须是绝对路径
if(IS_SYMLINK file_name) #判断是否是软连接,必须是绝对路径
if(IS_ABSOLUTE path) #判断是否是绝对路径
#其他
if(item IN_LIST list_name) #判断元素是否在列表中
if(path1 PATH_EQUAL path2) #判断两个路径是否相等,本质是两个字符串的比较,所以=
if(path1 STREQUAL path2)
#不过,等路径中不小心写多个分隔符,比如a//b//c与a/b/c,如果是通过STAEQUAL去比较,这两个路径肯定不相等,但是通过PATH_EQUAL来判断,这两个路径是相等的,PATH_EQUAL会自动剔除多余的分割线
#循环
foreach( loop_item items) #遍历items中的元素,通过loop_item访问
endforeach()
#有如下例子:
foreach(loop_item RANGE stop_val) #表示从0开始遍历,递增到stop_val停止
foreach(loop_item RANGE start_val stop_val step)#表示从start_val开始遍历到stop_val,步长为step,步长默认为1