CMake学习笔记,原文链接如下(原文更详细):
点此处直达原文
1、CMake的使用
- CMake支持大写、小写、混合大小写的命令。
1.1 CMake中的注释
- CMake 使用’ # '进行行注释.
# 这是一个CMake注释
- CMake 使用’ #[[ ]] '进行块注释。
#[[
块注释内容
]]
1.2 CMake中的命令
- 1、
cmake_minimum_required
:指定使用的 cmake 的最低版本
cmake_minimum_required(VERSION 3.0) # 表示CMake最低使用3.0版本
- 2、
project
:定义工程名称
project(MyProject) # 定义工程名为"MyProject"
- 3、
add_executable
:生成可执行程序文件
add_executable(run 源文件名称) # 生成一个名为"run"的可执行文件,这里可执行文件名和工程名并无关系,且源文件可以是多个如:add_executable(run test.cpp test.h main.cpp)
- 4、
set
: 定义变量
set(FILE_LIST test.h test.cpp file.cpp file.h) # 定义了"FILE_LIST"变量,其值为:"test.h test.cpp file.cpp file.h"多个变量之前使用空格
add_executable(run ${FILE_LIST}) # 使用"FILE_LIST"变量等同于"add_executable(run test.h test.cpp file.cpp file.h)"
- 5、
aux_source_directory
:搜索某个路径下的所有源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST) # 搜索src文件夹下的所有源文件,并保存在"SRC_LIST"变量中若:此文件夹下有如下源文件:test.cc test.h ama.cc ama.h,"SRC_LIST"值就是"test.cc test.h ama.cc ama.h"
- 6、
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
:搜索指定文件
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) # 搜索目录下所有的.cpp文件并将其存储在'MAIN_SRC'变量中
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h) # 搜索目录下所有的.h文件并将其存储在'MAIN_HEAD'变量中
GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
- 7、
include_directories
:包含头文件
include_directories(头文件存储路径) # 包含头文件路径后可以找到源文件对应的头文件
- 8、
add_library(库名称 STATIC 源文件)
:生成静态库
add_library(test STATIC test.cc object.cc) # 将会生成库名为"libtest.a"的静态库
- 9、
add_library(库名称 SHARED 源文件)
:生成动态库
add_library(test SHARED test.cc object.cc) # 将会生成库名为"libtest.so"的动态库
- 10、
link_directories(库存放路径)
:指定库存放路径
link_directories(static_lib_path) # 将会在"static_lib_path"目录下查找要链接的库
- 11、
link_libraries(静态库名)
:链接静态库
link_libraries(test) # 链接"libtest.a",可以链接多个静态库,库名之前使用空格隔开即可
- 12、
target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <name>... )
# 链接动态库,其中"target":要连接库的项目名称,“PRIVATE|PUBLIC|INTERFACE”:表示动态库的访问权限,“name”:要链接的动态库名 - 动态库链接访问权限说明:
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。
target_link_libraries(main public pthread test) # 表示将libpthread.so、libtest.so动态库链接到main,并且动态库的访问权限是PUBLIC
- 13、
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
:在cmake中打印输出
(无) :重要消息
STATUS :非重要消息
WARNING:CMake 警告, 会继续执行
AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR:CMake 错误, 终止所有处理过程
- 14、
add_definitions(-D宏名称)
:自定义宏
add_definitions(-DMESSAGE) # 自定义了名为:'MESSAGE'的宏。自定义宏默认是关闭的,使用:" cmake -DMESSAGE=ON .. "则会在编译时打开自定义宏,使用" cmake .. "则不会打开自定义宏
// 自定义宏使用说明:
#include <iostream>
int main()
{
#ifdef MESSAGE
std::cout<<"hello cpp!"<<std::endl;
#endif
std::cout<<"hello world!"<<std::endl;
return 0;
}
// 在自定义了宏之后如果编译时将其打开怎会输出:
// hello cpp!
// hello world!
// 如果不打开则输出:
// hello world!
1.3 CMake中的宏
- 1、
CMAKE_CXX_STANDARD
:指定使用的C++标准
set(CMAKE_CXX_STANDARD 11) # 将"CMAKE_CXX_STANDARD"赋值为11,表示使用C++11
- 2、
EXECUTABLE_OUTPUT_PATH
:指定可执行程序生成路径
set(EXECUTABLE_OUTPUT_PATH "./bin") # 表示可执行程序或动态库将存放在当前目录下的bin文件夹中,如果bin文件夹不存在会自动创建
- 3、
CMAKE_CURRENT_SOURCE_DIR
: 宏表示当前访问的 CMakeLists.txt 文件所在的路径。 - 4、
PROJECT_SOURCE_DIR
:使用cmake时后面的目录,一般是工程根目录 - 5、
LIBRARY_OUTPUT_PATH
:指定静态库或动态库的生成路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) #设置静态库或动态库的生成路径
- 6、
PROJECT_BINARY_DIR
: 执行cmake命令的目录 - 7、
CMAKE_CURRENT_BINARY_DIR
target 编译目录 - 8、
PROJECT_NAME
: 返回通过PROJECT指令定义的项目名称 - 9、
CMAKE_BINARY_DIR
: 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径
1.4 CMake中的嵌套
- 1、对于大型项目,不会是所有源文件在一个文件夹,所有的头文件在一个文件夹,一般都是根据其功能将其存放在不同的文件夹中,当然每个文件夹里也会嵌套其它的文件夹,那么CMake是如何管理这样的项目呢?一起看看吧。
- 2、如下目录结构
├── build
├── 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
目录说明:
include 目录:头文件目录
calc 目录:目录中的四个源文件对应的加、减、乘、除算法,对应的头文件是include中的calc.h
sort 目录 :目录中的两个源文件对应的是插入排序和选择排序算法,对应的头文件是include中的sort.h
test1 目录:测试目录,对加、减、乘、除算法进行测试
test2 目录:测试目录,对排序算法进行测试
- 3、节点关系:Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:
(1)、根节点CMakeLists.txt中的变量全局有效
(2)、父节点CMakeLists.txt中的变量可以在子节点中使用
(3)、子节点CMakeLists.txt中的变量只能在当前节点中使用 - 4、接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
:指定子目录
source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录
binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。
通过这种方式CMakeLists.txt文件之间的父子关系就被构建出来了。
- 5、在上面的目录中我们要做如下事情:
通过 test1 目录中的测试文件进行计算器相关的测试
通过 test2 目录中的测试文件进行排序相关的测试
现在相当于是要进行模块化测试,对于calc和sort目录中的源文件来说,可以将它们先编译成库文件(可以是静态库也可以是动态库)然后在提供给测试文件使用即可。库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式。
- 6、根目录中的 CMakeLists.txt文件内容如下:
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文件,这样它们的父子关系就被确定下来了。
- 7、calc 目录中的 CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC) # 搜索当前目录(calc目录)下的所有源文件
include_directories(${HEAD_PATH}) # 包含头文件路径,HEAD_PATH是在根节点文件中定义的
set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) # 设置库的生成的路径,LIB_PATH是在根节点文件中定义的
add_library(${CALC_LIB} STATIC ${SRC}) # 生成静态库,静态库名字CALC_LIB是在根节点文件中定义的
- 8、sort 目录中的 CMakeLists.txt文件内容如下:
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}) # 生成动态库,动态库名字SORT_LIB是在根节点文件中定义的
- 9、test1 目录中的 CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH}) # 指定头文件路径,HEAD_PATH变量是在根节点文件中定义的
link_directories(${LIB_PATH})
link_libraries(${CALC_LIB}) # 指定可执行程序要链接的静态库,CALC_LIB变量是在根节点文件中定义的
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) # 指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
add_executable(${APP_NAME_1} ${SRC}) # 生成可执行程序,APP_NAME_1变量是在根节点文件中定义的
- 10、test2 目录中的 CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.0)
project(SORTTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH}) # 包含头文件路径,HEAD_PATH变量是在根节点文件中定义的
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) # 指定可执行程序生成的路径,EXEC_PATH变量是在根节点文件中定义的
link_directories(${LIB_PATH}) # 指定可执行程序要链接的动态库的路径,LIB_PATH变量是在根节点文件中定义的
add_executable(${APP_NAME_2} ${SRC}) # 生成可执行程序,APP_NAME_2变量是在根节点文件中定义的
target_link_libraries(${APP_NAME_2} ${SORT_LIB}) # 指定可执行程序要链接的动态库的名字
- 11、构建项目:一切准备就绪之后,开始构建项目,进入到根节点目录的build 目录中,执行cmake 命令,如下:
cmake ..
cmake --build . (或:make)
- 12、可以看到在build目录中生成了一些文件和目录,静态库、动态库、可执行程序会生成在对应目录下,如下所示:
build
├── calc # 目录
├── CMakeCache.txt # 文件
├── CMakeFiles # 目录
├── cmake_install.cmake # 文件
├── Makefile # 文件
├── sort # 目录
├── test1 # 目录
└── test2 # 目录
- 13、注意:在项目中,如果将程序中的某个模块制作成了动态库或者静态库并且在CMakeLists.txt 中指定了库的输出目录,而后其它模块又需要加载这个生成的库文件,此时直接使用就可以了,如果没有指定库的输出路径或者需要直接加载外部提供的库文件,此时就需要使用 link_directories 将库文件路径指定出来。
1.5 CMake中的流程控制
- 在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断和循环。
1、条件判断:关于条件判断其语法格式如下:
if(<condition>)
<commands>
elseif(<condition>) # 可选快, 可以重复
<commands>
else() # 可选快
<commands>
endif()
-
- 在进行条件判断的时候,如果有多个条件,那么可以写多个elseif,最后一个条件可以使用else,但是开始和结束是必须要成对出现的,分别为:if和endif。
- 2、基本表达式
if(<expression>)
如果是基本表达式,expression 有以下三种情况:常量、变量、字符串。
如果是1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回True
如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串时,条件判断返回False
- 3、逻辑判断
NOT
if(NOT <condition>)
其实这就是一个取反操作,如果条件condition为True将返回False,如果条件condition为False将返回True。
AND
if(<cond1> AND <cond2>)
如果cond1和cond2同时为True,返回True否则返回False。
OR
if(<cond1> OR <cond2>)
如果cond1和cond2两个条件中至少有一个为True,返回True,如果两个条件都为False则返回False。
- 4、 比较
(1)、基于数值的比较:
if(<variable|string> LESS <variable|string>)
if(<variable|string> GREATER <variable|string>)
if(<variable|string> EQUAL <variable|string>)
if(<variable|string> LESS_EQUAL <variable|string>)
if(<variable|string> GREATER_EQUAL <variable|string>)
LESS:如果左侧数值小于右侧,返回True
GREATER:如果左侧数值大于右侧,返回True
EQUAL:如果左侧数值等于右侧,返回True
LESS_EQUAL:如果左侧数值小于等于右侧,返回True
GREATER_EQUAL:如果左侧数值大于等于右侧,返回True
(2)、基于字符串的比较
if(<variable|string> STRLESS <variable|string>)
if(<variable|string> STRGREATER <variable|string>)
if(<variable|string> STREQUAL <variable|string>)
if(<variable|string> STRLESS_EQUAL <variable|string>)
if(<variable|string> STRGREATER_EQUAL <variable|string>)
STRLESS:如果左侧字符串小于右侧,返回True
STRGREATER:如果左侧字符串大于右侧,返回True
STREQUAL:如果左侧字符串等于右侧,返回True
STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回True
STRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回True
- 5、文件操作
(1)、判断文件或者目录是否存在:
if(EXISTS path-to-file-or-directory)
如果文件或者目录存在返回True,否则返回False。
(2)、判断是不是目录:
if(IS_DIRECTORY path)
此处目录的 path 必须是绝对路径
如果目录存在返回True,目录不存在返回False。
(3)、判断是不是软连接:
if(IS_SYMLINK file-name)
此处的 file-name 对应的路径必须是绝对路径
如果软链接存在返回True,软链接不存在返回False。
软链接相当于 Windows 里的快捷方式
(4)、判断是不是绝对路径:
if(IS_ABSOLUTE path)
关于绝对路径:
如果是Linux,该路径需要从根目录开始描述
如果是Windows,该路径需要从盘符开始描述
如果是绝对路径返回True,如果不是绝对路径返回False。
- 6、 循环
-
- 在 CMake 中循环有两种方式,分别是:foreach和while。
- (1)、foreach
使用 foreach 进行循环,语法格式如下:
foreach(<loop_var> <items>)
<commands>
endforeach()
通过foreach我们就可以对items中的数据进行遍历,然后通过loop_var将遍历到的当前的值取出,在取值的时候有以下几种用法:
(1)、方法1
foreach(<loop_var> RANGE <stop>)
RANGE:关键字,表示要遍历范围
stop:这是一个正整数,表示范围的结束值,在遍历的时候从 0 开始,最大值为 stop。
loop_var:存储每次循环取出的值
举例说明:
cmake_minimum_required(VERSION 3.2)
project(test)
# 循环
foreach(item RANGE 10)
message(STATUS "当前遍历的值为: ${item}" )
endforeach()
(2)、方法2
foreach(<loop_var> RANGE <start> <stop> [<step>])
这是上面方法1的加强版,我们在遍历一个整数区间的时候,除了可以指定起始范围,还可以指定步长。
RANGE:关键字,表示要遍历范围
start:这是一个正整数,表示范围的起始值,也就是说最小值为 start
stop:这是一个正整数,表示范围的结束值,也就是说最大值为 stop
step:控制每次遍历的时候以怎样的步长增长,默认为1,可以不设置
loop_var:存储每次循环取出的值
举例说明:
cmake_minimum_required(VERSION 3.2)
project(test)
foreach(item RANGE 10 30 2)
message(STATUS "当前遍历的值为: ${item}" )
endforeach()
(3)、方法3
foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])
这是foreach的另一个变体,通过这种方式我们可以对更加复杂的数据进行遍历,前两种方式只适用于对某个正整数范围内的遍历。
IN:关键字,表示在 xxx 里边
LISTS:关键字,对应的是列表list,通过set、list可以获得
ITEMS:关键字,对应的也是列表
loop_var:存储每次循环取出的值
举例说明:
cmake_minimum_required(VERSION 3.2)
project(test)
# 创建 list
set(WORD a b c d)
set(NAME ace sabo luffy)
# 遍历 list
foreach(item IN LISTS WORD NAME)
message(STATUS "当前遍历的值为: ${item}" )
endforeach()
-(2)、while
-
- 除了使用foreach也可以使用 while 进行循环,关于循环结束对应的条件判断的书写格式和if/elseif 是一样的。while的语法格式如下:
while(<condition>)
<commands>
endwhile()
while循环比较简单,只需要指定出循环结束的条件即可:
举例说明:
cmake_minimum_required(VERSION 3.5)
project(test)
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER 0)
message(STATUS "names = ${NAME}")
# 弹出列表头部元素
list(POP_FRONT NAME)
# 更新列表长度
list(LENGTH NAME LEN)
endwhile()