目录
1.CMake概述
CMake 是一款跨平台的开源构建工具,通过编写 CMakeLists.txt
配置文件描述项目结构、编译选项和依赖关系,自动生成不同平台的原生构建文件(如 Makefile、Visual Studio 项目),实现 “一次配置,多平台编译”,大幅简化了复杂项目的构建流程,同时支持测试、打包、安装等全生命周期管理,广泛应用于 C/C++ 等语言的大型项目开发。
2. CMakeDemo
2.1注释
2.1.1 注释行
CMake 使用 # 进行行注释,可以放在任何位置。
# 这是一个 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.0.0)
2.1.2 注释块
CMake 使用 #[[ ]] 形式进行块注释。
#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
cmake_minimum_required(VERSION 3.0.0)
2.2编写一个简单的CMakeLists.txt文件
当前目录结构:a b c d分别代表加减乘除demo,e包含四个函数声明,f是测试代码。
2.2.1添加CMakeLists.txt文件
cmake_minimum_required(VERSION 3.15)
project(Test)
add_executable(demo a.cpp b.cpp c.cpp d.cpp f.cpp)
cmake_minimum_required:指定使用的 cmake 的最低版本。可选,非必须,如果不加可能会有警告。
project:定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。
# PROJECT 指令的语法是:
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
add_executable:定义工程会生成一个可执行程序,源文件名可以是一个也可以是多个,如有多个可用空格或;间隔.
2.2.2 cmake
将 CMakeLists.txt 文件编辑好之后,就可以执行 cmake命令了。
# cmake 命令原型
$ cmake CMakeLists.txt文件所在路径
2.2.3 make
当cmake后,目录下生成了一个makefile文件,此时再执行make命令,就可以对项目进行构建得到所需的可执行程序了。
2.2.4 优化
如果在CMakeLists.txt文件所在目录执行了cmake命令之后就会生成一些目录和文件(包括 makefile 文件),如果再基于makefile文件执行make命令,程序在编译过程中还会生成一些中间文件和一个可执行文件,这样会导致整个项目目录看起来很混乱,不太容易管理和维护,此时我们就可以把生成的这些与项目源码无关的文件统一放到一个对应的目录里边,比如将这个目录命名为build,去这个目录下执行相关指令:
3.set
set可以用来定义变量或属性,set指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
VAR:变量名
VALUE:变量值
例如用来定义项目配置:
set(PROJECT_NAME "MyApp")
set(CMAKE_CXX_STANDARD 11) # C++ 标准版本
set(CMAKE_BUILD_TYPE Debug) # 编译类型
4.搜索文件
如果一个项目里边的源文件很多,在编写CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦也不现实。所以,在CMake中为我们提供了搜索文件的命令,可以使用aux_source_directory命令或者file命令。
在 CMake 中使用aux_source_directory 命令可以查找某个路径下的所有源文件,命令格式为:
aux_source_directory(< dir > < variable >)
dir:要搜索的目录
variable:将从dir目录下搜索到的源文件列表存储到该变量中
file命令:
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
demo:
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
5.包含头文件
在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。在CMake中设置要包含的目录也很简单,通过一个命令就可以搞定了,它就是include_directories:
include_directories(headpath)
其可以简化 #include
语句,让代码中可以直接写 #include "xxx.h"
,而无需写完整路径。
6.制作静态库或动态库
6.1制作库
在cmake中,如果要制作静/动态库,需要使用的命令如下:
add_library(库名称 STATIC/SHARED 源文件1 [源文件2] ...)
在Linux中,静态库名字分为三部分:lib+库名字+.a,动态库名字分为三部分:lib+库名字+.so。此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充。
静态库编译时被直接链接到可执行文件中,本质是多个目标文件(.o)的集合,不包含可执行代码,仅提供函数定义和数据,因此无需执行权限。动态库运行时被加载,包含可执行代码,但执行权限并非必需(系统通过动态链接器加载,而非直接执行)。
可执行权限主要针对可执行文件(如.out
),用于控制文件是否能被直接运行。库文件(无论静态还是动态)不需要可执行权限,因为它们不会被直接执行,而是被链接器或动态链接器调用。
对于生成的库文件来说和可执行程序一样都可以指定输出路径。LIBRARY_OUTPUT_PATH
是 CMake 专门为库文件设计的路径宏,同时兼容静态库和动态库。
set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
6.3链接库
在 CMake 中,链接静态库和动态库的语法完全相同,均使用 target_link_libraries 命令。
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
- target:指定要加载的库的文件的名字
- 该文件可能是一个源文件
- 该文件可能是一个动态库/静态库文件
- 该文件可能是一个可执行文件
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
- PRIVATE:仅目标自身使用该依赖,不传递给依赖此目标的其他目标。
- PUBLIC:目标和依赖此目标的其他目标都使用该依赖(传递)。
- INTERFACE:仅依赖此目标的其他目标使用该依赖,目标自身不使用。
target_link_directories给指定目标设置 “链接阶段的库搜索路径”,让链接器在链接该目标时,到这些路径里找依赖的库文件。
target_link_directories(<target>
[BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items...]
[<INTERFACE|PUBLIC|PRIVATE> [items...] ...]
)
区别于全局命令 link_directories
,target_link_directories
只影响指定的 <target>
,不会 “污染” 其他目标的链接路径,更符合现代 CMake “目标驱动” 的设计理念。
动态库的链接和静态库是完全不同的:
动态库仅记录依赖关系,运行时动态加载。因此:依赖传递是逻辑上的:依赖链中的所有动态库必须在运行时可被找到,但不会合并代码。
静态库会将代码直接嵌入到最终可执行文件中。因此:依赖传递是物理上的:链接时会递归合并所有依赖的静态库代码。
静态库会在生成可执行程序的链接阶段被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。
动态库在生成可执行程序的链接阶段不会被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存。
7.日志
在CMake里面,可以用message来显示日志输出信息:
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警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。
8.变量操作
8.1追加
有时候项目中的源文件并不一定都在同一个目录中,但是这些源文件最终却需要一起进行编译来生成最终的可执行文件或者库文件。如果我们通过file命令对各个目录下的源文件进行搜索,最后还需要做一个字符串拼接的操作,关于字符串拼接可以使用set命令也可以使用list命令。
如果使用set进行字符串拼接,对应的命令格式如下:
set(变量名1 ${变量名1} ${变量名2} ...)
关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。
如果使用list进行字符串拼接,对应的命令格式如下:
list(APPEND <list> [<element> ...])
list命令的功能比set要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND表示进行数据追加,后边的参数和set就一样了。
8.2 移除
我们在通过file搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,比如:
在当前这么目录有五个源文件,其中f.cpp是一个测试文件。如果我们想要把计算器相关的源文件生成一个动态库给别人使用,那么只需要其他四个源文件就可以了。此时,就需要将main.cpp从搜索到的数据中剔除出去,想要实现这个功能,也可以使用list 。
list(REMOVE_ITEM <list> <value> [<value> ...])
demo:
set(Test_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/f.cpp)
file(GLOB LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
list(REMOVE_ITEM LIB_SRC ${Test_SRC})
关于list命令还有其它功能,但是并不常用,在此就不一一进行举例介绍了。
9.宏定义
在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效,对应的命令叫做add_definitions:
add_definitions(-D宏名称)
demo:
add_definitions(-DDEBUG)后终端就会输出DEBUG!!!.
常见的宏定义包括:系统预定义宏,用户自定义功能开关,版本 / 配置信息等等。
10.预定义宏
下面的列表中是一些CMake中常用的宏:
🫠🫠🫠