技术栈CMake的介绍和使用
目录
- 1. 学习前言
- 2. CMake 概述
- 3. CMake 安装
- 4. CMake入门
- 4.1 CMake 使用注意事项
- 4.2 基本的概念和术语
- 4.3 CMake 常用的预定义变量
- 4.4 CMakeLists.txt 文件的基本结构
- 4.4.1 set 使用
- 4.4.2 搜索文件
- 4.4.3 指定头文件的搜索路径
- 4.4.4 CMake 制作库文件
- 4.4.5 链接库
- 4.4.6 在目录下查找所有的源文件
- 4.4.7 向当前工程添加存放源文件的子目录
- 4.4.8 打印输出信息和搜索外部库
- 4.4.9 列表操作
- 5. 上手实战
- 5.1 简单示例
- 5.2 一个正式的项目构建
- 6. shell脚本一键自动化方式
- 7. CMake 总结
1. 学习前言
(1)在我们刚开始学习Linux系统编程的时候,我们就接触到了 Makefile ,它是用于自动化构建软件项目的工具,主要用于编译源代码文件、管理项目依赖关系。
- Makefile 是一个文件。它是一个工程文件的编译规则,它记录了原始码如何编译的详细信息、描述了整个工程的编译链接等规则。
- Makefile 存在的意义就是为了构建依赖关系和依赖原理。
- Makefile 带来的好处是“自动化编译”,一旦写好的话,只需要一个make命令,整个工程的完全自动编译,极大的提高了软件开发的效率。
- 且大多数的IDE软件都是集成了make,例如Visual C++的 nmake,Linux下的 GNU make,Qt的 qmake等等,不同的IDE所集成的make工具所遵循的规范和标准都不同,也就导致其语法、格式不同,也就不能很好的跨平台编译,如果软件想跨平台,必须要保证能够在不同的平台上编译,如果使用上面的make工具的话,就得为每一个标准写一个Makefile文件,会再次使得工作繁琐起来。
所以CMake的出现允许开发者指定整个工程的编译流程,再跟据编译平台,生成本地化的Makefile和工程文件,最后只需要make编译即可。
2. CMake 概述
(1)CMake 是一个开源、跨平台的构建系统,主要用于软件的构建、测试和打包。CMake 使用平台无关的配置文件 CMakeLists.txt 来控制软件的编译过程,并生成适用于不同编译器环境的项目文件。
- 例如,它可以生成 Unix 系统的 Makefile 、 Windows 下 的 Visual Studio 项目文件或 Mac 的 Xcode 工程文件,从而简化了跨平台和交叉编译的工作流程。CMake 并不直接构建软件,而是产生标准的构建文件,然后使用这些文件在各自的构建环境中构建软件。
- CMake是“Cross Platform Make”的缩写,CMake是开源的、跨平台的构建工具,可以让我们通过简单的配置文件去生成本地的 Makefile ,这个配置文件时独立于运行平台和编译器的,这样就不用亲自去编写Makefile了,而且配置文件可以直接拿到其他平台上去使用,不用修改。
(2)CMake 有以下几个特点:
- 开放源代码:使用类 BSD 许可发布
- 跨平台:并可生成编译配置文件,在 Linux/Unix 平台,生成 makefile;在苹果平台,可以生成 xcode;在 Windows 平台,可以生成 MSVC 的工程文件
- 能够管理大型项目:KDE4 就是最好的证明
- 简化编译构建过程和编译过程:Cmake 的工具链非常简单:cmake+make
- 高效率:按照 KDE 官方说法,CMake 构建 KDE4 的 kdelibs 要比使用autotools来构建 KDE3.5.6 的 kdelibs 快 40%,主要是因为 Cmake 在工具链中没有 libtool
- 可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能
(3)CMake与其他常见的构建、编译工具的联系。下面的表格清晰地展示了这些工具之间的相互关系及其在整个构建过程中地作用,表中还添加了其他相关工具的介绍。
工具名称 | 定义 | 使用场景 | 系统支持 | 使用实例 |
---|---|---|---|---|
CMake | 跨平台的自动化构建系统 | 多平台项目构建,生成 Makefile 等构建文件 | 跨平台 | cmake . 或 cmake --build . |
CMakeLists | CMake 项目配置文件 | 定义项目的构建规则和依赖关系 | 跨平台 | add_executable(myapp main.cpp) |
Make | 构建自动化工具 | 根据 Makefile 自动编译源代码 | Unix/Linux | make 或 make all |
Makefile | 构建规则文件 | 自定义编译指令和依赖关系 Unix/Linux | all: main.o foo.o | |
Ninja | 高效的构建系统 | 快速构建项目,支持并行编译 | 跨平台 | ninja 或 ninja -j 4 |
Autotools* | 包含 Autoconf、Automake 等工具 | 自动生成配置脚本和 Makefile | Unix/Linux | ./configure 然后 make |
configure | 配置脚本生成工具 | 检测系统环境,生成适合当前系统的构建脚本 | Unix/Linux | ./configure --prefix=/usr/local |
Autoconf | 配置脚本生成工具 | 检测系统特性,生成配置脚本 | Unix/Linux | autoconf 生成 configure 脚本 |
Automake | Makefile 生成工具 | 根据规则生成 Makefile | Unix/Linux | automake --add-missing |
GCC | GNU 编译器集合 | C、C++ 等多种语言的编译 | 跨平台 | gcc -o myapp main.c |
g++ | C/C++ 编译器 | 编译 C 或 C++ 源代码文件 | Unix/Linux | g++ -o myapp main.cpp |
c++ | C++ 编译器 | 编译 C++ 源代码文件 | Unix/Linux | c++ -o myapp main.cpp |
Clang | LLVM 项目的一部分 | C 语言家族的编译器前端,代码分析转换 | 跨平台 | clang -o myapp main.c |
GDB | GNU 调试器 | 调试 C/C++ 程序,提供断点、单步执行等功能 | Unix/Linux | gdb myapp |
ar | 静态库管理工具 | 创建、修改和提取静态库文件 | Unix/Linux | ar rcs libmyapp.a main.o |
ld | 链接器 | 将多个目标文件或库文件链接成可执行文件或库 | Unix/Linux | ld -o myapp main.o -L. -lmyib |
strip | 移除符号信息工具 | 减小可执行文件或库的大小 | Unix/Linux | strip myapp |
pkg-config | 库依赖管理工具 | 提供库的编译和链接参数 | Unix/Linux | pkg-config --cflags --libs gtk±3.0 |
3. CMake 安装
(1)Ubuntu 下安装 cmake:
sudo apt update
sudo apt install cmake
(2)确定 cmake 是否安装成功:
cmake --version
4. CMake入门
(1)接下来我们学习CMake的基本概念,其中包括项目定义、目标创建及如何通过CMakeLists.txt文件来控制整个的构建过程。
4.1 CMake 使用注意事项
(1)注意事项:
- CMake构建专用定义文件,文件名严格区分大小写。在工程中使用 CMake 时,通常会在项目的根目录放置一个 CMakeLists.txt 文件来描述如何构建项目。这个文件名是固定的,不能随意更改。
- 工程存在多个目录的情况下,可以在每个目录下都放一个CMakeLists.txt文件,这有助于分层管理复杂的项目结构。通过这种方式,可以更方便地组织代码,并且可以对不同部分的源代码进行独立配置。
- 工程存在多个目录的情况下,也可以只使用一个CMakeLists.txt文件管理,这适用于较为简单的项目或者当开发者希望将所有构建逻辑集中在一个地方时。
- 严格区分大小写,在某些操作系统(如 Linux)上,文件系统是区分大小写的。这意味着 CMakeLists.txt和 cmakeLists.txt 会被视为两个不同的文件。因此,在这些平台上,必须确保文件名的大小写正确无误。然而,这条规则并不适用于文件内容中的变量命名或其他定义。
- CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。
- 严格大小写相关,名称只能用字母、数字、下划线
- 使用 ${} 来引用变量
- 参数之间使用空格进行间隔
4.2 基本的概念和术语
(1)基本术语:
- Project:一个逻辑概念,定义了项目的名称和属性。
- Target:构建系统中的一个目标,如可执行文件、库文件等。
- Source Files:需要编译的源代码文件。
- Binary Directory:存放编译生成的二进制文件的目录。
- Source Directory:存放源代码的目录。
- Generator:用于生成特定构建系统的程序,如Unix Makefiles。
- Variable:CMake中的变量,用于存储和传递配置信息。
- Command:CMake中的命令,用于执行构建配置的操作。
4.3 CMake 常用的预定义变量
(1)预定义变量表格如下:
变量名 | 描述 | 示例值(取决于项目) |
---|---|---|
CMAKE_SOURCE_DIR | 顶层 CMakeLists.txt 所在的目录,即项目的根源代码目录。 | /home/user/myproject |
CMAKE_BINARY_DIR | 构建目录(运行 cmake 的目录)。 | /home/user/myproject/build |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的 CMakeLists.txt 所在的源代码目录。 | /home/user/myproject/src/module |
CMAKE_CURRENT_BINARY_DIR | 当前处理的 CMakeLists.txt 对应的构建目录。 | /home/user/myproject/build/src/module |
CMAKE_CURRENT_LIST_FILE | 当前正在处理的 CMakeLists.txt 文件的完整路径。 | /home/user/myproject/src/CMakeLists.txt |
CMAKE_CURRENT_LIST_LINE | 当前执行的 CMake 命令在文件中的行号。 | 15 |
CMAKE_PROJECT_NAME | 通过project()命令设置的项目名称。 | MyProject |
PROJECT_NAME | 同CMAKE_PROJECT_NAME,但在子项目中可能不同(由project()设置)。 | MyProject |
CMAKE_VERSION | 当前 CMake 的版本号。 | 3.25.1 |
CMAKE_SYSTEM | 目标系统的名称和版本。 | Linux-5.15.0 |
CMAKE_SYSTEM_NAME | 目标系统的名称(如 Linux、Windows、Darwin)。 | Linux |
CMAKE_SYSTEM_VERSION | 目标系统的版本号。 | 5.15.0 |
CMAKE_SYSTEM_PROCESSOR | 目标处理器架构(如 x86_64、arm64)。 | x86_64 |
CMAKE_C_COMPILER | C 编译器的完整路径。 | /usr/bin/gcc |
CMAKE_CXX_COMPILER | C++ 编译器的完整路径。 | /usr/bin/g++ |
CMAKE_BUILD_TYPE | 构建类型(如 Debug、Release、RelWithDebInfo)。 | Debug |
CMAKE_INSTALL_PREFIX | make install安装文件的默认前缀路径。 | /usr/local |
CMAKE_MODULE_PATH | CMake 模块文件(如 Find*.cmake)的搜索路径列表。 | [“/home/user/myproject/cmake/Modules”] |
EXECUTABLE_OUTPUT_PATH | 可执行文件的输出目录。 | /home/user/myproject/build/bin |
LIBRARY_OUTPUT_PATH | 库文件的输出目录。 | /home/user/myproject/build/lib |
CMAKE_INCLUDE_PATH | 查找头文件的附加路径(类似 CFLAGS 中的 - I)。 | [“/usr/include/mylib”] |
CMAKE_LIBRARY_PATH | 查找库文件的附加路径(类似 LDFLAGS 中的 - L)。 | [“/usr/lib/mylib”] |
CMAKE_PREFIX_PATH | 查找依赖项的前缀路径列表(用于find_package等)。 | [“/opt/myframework”, “/usr/local”] |
CMAKE_AR | 归档工具(如 ar)的完整路径。 | /usr/bin/ar |
CMAKE_RANLIB | ranlib 工具的完整路径。 | /usr/bin/ranlib |
CMAKE_BUILD_PARALLEL_LEVEL | 并行构建的线程数(由-j选项或环境变量设置)。 | 8 |
4.4 CMakeLists.txt 文件的基本结构
(1)CMakeLists.txt 是 CMake 的配置文件,用于定义项目的构建规则,下面是一个简单的示例:
# 指定 CMake 的最低版本要求(不是必须的,这个version需要比本地使用的低就行)
cmake_minimum_required(VERSION 3.10)# 定义项目名称和使用的编程语言(不写默认情况支持所有语言) project(<PROJECT-NAME> [<language-name>...])
project(MyProject CXX)# 添加可执行文件目标 add_executable(可执行程序名 所有源文件名称.cpp)
add_executable(MyProgram main.cpp utils.cpp)# 添加编译选项
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O2")# 添加库文件(如果需要)
target_link_libraries(MyProgram some_library)# 注释单行行(# ) 注释多行CMake 使用 #[[ ]] 形式进行块注释
#[[ 这是一个 CMakeLists.txt 文件。这是一个 CMakeLists.txt 文件这是一个 CMakeLists.txt 文件]]
(2)下面是对基本结构的解释:
- cmake_minimum_required(VERSION 3.10):指定CMake的最低版本要求,确保项目的构建依赖于特定版本的CMake特性。
- project(MyProject CXX):指定项目名称为“MyProject”,并指定项目所使用的编程语言为C++。
- 添加可执行文件:
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])
- 参数解释:用于定义一个可执行文件目标(Executable Target),即告诉 CMake 如何编译并链接一组源文件以生成最终的可执行程序。
参数 | 含义 |
---|---|
<name> | 可执行文件的目标名称(输出文件名由 CMake 决定,如 myapp,在 Windows 上会生成 myapp.exe) |
WIN32 | 有此参数时, WIN32_EXECUTABLE属性会被置为true, 此时在windows环境下创建的可执行文件将以WinMain函数代替main函数作为程序入口, 构建而成的可执行文件为GUI应用程序而不是控制台应用程序 |
MACOSX_BUNDLE | 有此参数时, MACOSX_BUNDLE属性会被置为true, 此时在macOS或者iOS上构建可执行文件目标时, 目标会成为一个从Finder启动的GUI可执行程序 |
EXCLUDE_FROM_ALL | 有此参数时, 此目标就会被排除在all target列表之外, 即在执行默认的make时, 不会构造此目标, 需要构造此目标的时候, 需要手动构建 |
source1 source2 … | 编译所需的源文件列表(C/C++/ObjC/ASM 等) |
4.4.1 set 使用
(1)定义变量:
- 一般变量:假设我们在工程中存在着许多的源文件(.cpp),这些源文件需要被反复使用,每次在使用的时候都需要将它们的名字写出来是很麻烦的,此时我们可以定义一个变量,将文件名对应的字符串存储起来。例如将生成可执行文件的源文件全都定义为一个变量,在后面使用时候直接使用该变量即可,就不用再输入一长串文件名了。
<variable>:要设置的变量名;
<value1> <value2> ...:一个或多个值,会被合并成一个列表(空格分隔);
[PARENT_SCOPE]:可选参数,表示将变量设置到父作用域中。
- 作用域说明:CMake 的变量作用域是 函数/目录层级的。也就是说:
- 在某个 CMakeLists.txt 或函数中定义的变量,默认只在当前作用域可见;
- 使用 PARENT_SCOPE 可以让变量“提升”一级,在父级作用域中可见。例如:
set(SRC_LIST add.c div.c main.c mult.c sub.c)
- 缓存变量:是指那些被存储在 CMakeCache.txt 文件中的变量。这些变量不仅影响当前的配置和构建过程,而且在后续运行 CMake 配置命令时也会被记住和重用。因此,它们被称为“缓存”变量,因为它们就像是保存在项目根目录下的一个持久化存储(缓存),供未来的 CMake 配置会话使用。
set(<variable> <value>... CACHE <type> <docstring> [FORCE])
//是用于**设置一个缓存变量(Cache Variable)**的标准语法。这类变量在 CMake 配置过程中会持久化
//保存到 CMakeCache.txt 文件中,非常适合用于配置选项、用户可修改的构建参数等。# variable:只能有一个
# value:可以有0个,1个或多个,当value值为空时,方法同unset,用于取消设置的值
# CACHE:关键字,说明是缓存变量设置## type(类型):必须为以下中的一种:## BOOL:有ON/OFF,两种取值## FILEPATH:文件的全路径## PATH:目录路径## STRING:字符串## INTERNAL:字符串## docstring:总结性文字(字符串)
# [FORCE]:变量名相同,第二次调用set方法时,第一次的value将会被覆盖
- 例如:强制覆盖已有的缓存变量。
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE)
- 环境变量:用于设置或清除环境变量(Environment Variables)
set(ENV{<variable>} [<value>])# variable:只能有一个
# value:一般来说,只有一个,为空时,将清除之前设置的变量值,多个时,取值最近的一个,之后的值将被忽略
(2)指定使用C++标准:
- 我们在编写程序的时候,可能会用到C++11、C++14、C++17、C++20等新特性,所以就需要在编译的时候指出来。
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)
#增加-std=c++14
set(CMAKE_CXX_STANDARD 14)
#增加-std=c++17
set(CMAKE_CXX_STANDARD 17)
(3)指定输出的路径:
- set还可以定义一个变量用于存储一个绝对路径,设置文件的输出路径。
# 定义一个变量用于存储一个绝对路径
set(HOME /home/robin/Linux/Sort)
# 拼接好的路径值设置给CMAKE_RUNTIME_OUTPUT_DIRECTORY 宏,设置可执行程序输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${HOME}/bin)# 设置库文件的输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "D:/learn/C++/test/code/lib")# 设置静态库文件的输出目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "D:/learn/C++/test/code/lib")# 定义可执行文件
add_executable(MyProgram main.cpp)
# 为特定目标设置输出目录
set_target_properties(MyProgram PROPERTIESRUNTIME_OUTPUT_DIRECTORY "D:/learn/C++/test/code/bin"
)
4.4.2 搜索文件
(1)如果在一个项目里的源文件很多,在编写CMakeLists.txt文件的时候不可能将项目目录中的各个文件一一列举出来,这样太过于麻烦,所以在CMake中为我们提供了搜索文件的命令:file。
# GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
# GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
file(GLOB SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) # CMAKE_CURRENT_SOURCE_DIR 这个存储路径就是cmakefile.txt所在的路径
# file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
4.4.3 指定头文件的搜索路径
(1)当使用 CMake 管理 C++ 项目时,如果头文件所在的目录发生了变化,你不需要手动修改每一个引用该头文件的 .cpp 文件。相反,你只需要调整 CMake 的配置文件(通常是 CMakeLists.txt),就可以更新头文件的搜索路径。这种方法极大地简化了维护工作,并减少了出错的可能性。
set(headpath ${PROJECT_SOURCE_DIR}/include)
include_directories(${headpath})
//告诉CMake是在${headpath}这个路径下寻找头文件,而不需要修改源代码中的 #include 指令。
- 但是这个是全局作用域,容易引起混乱,更推荐使用。
target_include_directories(mytargetPRIVATE src/PUBLIC include/INTERFACE ../third_party/include
)
4.4.4 CMake 制作库文件
(1)如下所示:
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)//设置库文件生成路径(放在哪里)
(2)参数解析:
参数 | 含义 |
---|---|
<name> | 库名,是你为这个库指定的名称,比如 mylib |
STATIC | 表示这是一个静态库(.a 文件,在 Linux/macOS 上)或 (.lib 文件,在 Windows 上) |
SHARED | 表示这是一个动态库(共享库,.so 文件在 Linux,.dll 在 Windows) |
MODULE | 通常用于插件模块(不参与链接,仅加载),较少使用 |
EXCLUDE_FROM_ALL | 可选参数,表示该库不会被默认构建,需要显式调用构建 |
source1 source2 … | 构建该库所需的源文件列表,如 .cpp, .c, .cc 等 |
(3)例子:
add_library(mylib STATIC src/mylib.cpp src/utils.cpp)
- 解释:
- 创建了一个名为 mylib 的静态库;
- 使用了两个源文件 mylib.cpp 和 utils.cpp;
- 编译完成后会生成类似 libmylib.a(Linux/macOS) 或 mylib.lib(Windows)的静态库文件。
add_library(mylib SHARED src/mylib.cpp src/utils.cpp)
- 解释:
- 创建的是一个动态库;
- 编译后生成 libmylib.so(Linux)、libmylib.dylib(macOS)或 mylib.dll(Windows)。
4.4.5 链接库
(1)定义如下:
target_link_libraries(<target> [PRIVATE|PUBLIC|INTERFACE] <item>...)
- 解释:
- <target>:你的目标名称,比如通过 add_executable() 或 add_library() 定义的名称。
- PRIVATE:仅当前目标需要这个库;
- PUBLIC:当前目标和依赖它的目标都需要这个库;
- INTERFACE:仅依赖它的目标需要这个库。
(2)使用示例:
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
- 这样就只让 myapp 链接 mylib,不影响其他目标。
4.4.6 在目录下查找所有的源文件
(1)定义如下:
aux_source_directory(<dir> <varname>)
- 是一个用于自动收集目录中所有源文件路径的命令,常用于简化源码列表的定义。
参数 | 含义 |
---|---|
<dir> | 要扫描的目录(相对于当前 CMakeLists.txt 所在目录) |
<varname> | 用于存储找到的所有源文件路径的变量名 |
(2)功能说明:
- 自动查找源文件:会递归查找指定目录下的所有 .c, .cpp, .cxx, .m, .mm, .rc, .inl, .txx 等常见源代码文件;
- 不包括子目录中的文件:只查找指定目录中的文件,不会进入其子目录;
- 生成一个包含所有源文件路径的列表:结果以相对路径形式保存在 中;
- 适用于小型项目或模块:对于结构简单、源文件数量不多的情况非常方便。
4.4.7 向当前工程添加存放源文件的子目录
(1)定义如下:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_BUILD])
- 用于将一个子目录添加到构建系统中,并处理该目录下的 CMakeLists.txt 文件。它是组织多模块、多层次项目的常用方式。
参数 | 含义 |
---|---|
source_dir | 必须参数,指定包含 CMakeLists.txt 的子目录路径(相对于当前 CMakeLists.txt 所在目录) |
binary_dir | 可选参数,指定该子目录构建输出的目标路径(默认与源路径一致) |
EXCLUDE_FROM_BUILD | 可选参数,表示该子目录中的目标不会被默认构建(仅当被依赖时才会构建) |
(2)示例:
add_subdirectory(src)
add_subdirectory(libs/utils)
- 这会依次进入 src/ 和 libs/utils/ 目录,并执行其中的 CMakeLists.txt 文件。
4.4.8 打印输出信息和搜索外部库
(1)打印输出信息:
message(mode "message text" )
- 参数解析:
- <mode>:可选参数,指定消息的类型;
- “message text”:要输出的消息内容(支持变量替换);
消息类型 | 行为说明 |
---|---|
无模式(默认) | 输出消息到标准输出(stdout),不带任何前缀 |
STATUS | 输出状态信息(通常以 – 开头,用于显示进度或状态) |
WARNING | 输出黄色警告信息(不影响配置继续执行) |
AUTHOR_WARNING | 类似 WARNING,但仅在启用 CMAKE_SUPPRESS_DEVELOPER_WARNINGS 时生效 |
SEND_ERROR | 输出红色错误信息,配置继续,但会提示问题 |
FATAL_ERROR | 输出红色严重错误信息,并立即终止配置过程 |
DEPRECATION | 输出弃用警告(仅当 CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION 等相关设置启用时显示) |
(2)搜索外部库:
find_package(<PackageName> [version] [EXACT] [QUIET] [REQUIRED] [COMPONENTS <components...>])
- 是用于查找和加载外部依赖包(库、工具等)的配置信息,在使用第三方库(如 Boost、OpenCV、Qt 等)时非常关键。采用两种模式(FindXXX.cmake和XXXConfig.cmake)搜索外部库。
参数 | 含义 |
---|---|
<PackageName> | 要查找的包名(如 Boost, OpenCV, Threads 等) |
[version] | 可选,指定所需版本号(如 3.4.1) |
[EXACT] | 可选,要求精确匹配版本号 |
[QUIET] | 可选,禁止输出警告信息(即使未找到也不报错) |
[REQUIRED] | 可选,表示该包必须存在,否则终止配置 |
[COMPONENTS …] | 可选,指定需要查找的子模块或组件 |
- 示例:
find_package( OpenCV 3.4 REQUIRED )
- 搜索有两种模式:
- Module模式:搜索 CMAKE_MODULE_PATH 指定路径下的 FindXXX.cmake 文件,执行该文件从而找到XXX库。其中,具体查找库并给XXX_INCLUDE_DIRS和XXX_LIBRARIES两个变量赋值的操作由FindXXX.cmake模块完成。
- Config模式:搜索XXX_DIR指定路径下的XXXConfig.cmake文件从而找到XXX库。其中具体查找库并给XXX_INCLUDE_DIRS和XXX_LIBRARIES两个变量赋值的操作由XXXConfig.cmake模块完成。
- 两种模式看起来似乎差不多,不过cmake默认采取Module模式,如果Module模式未找到库,才会采取Config模式。
- 如果XXX_DIR路径下找不到XXXConfig.cmake文件,则会找/usr/local/lib/cmake/XXX/中的XXXConfig.cmake文件。总之,Config模式是一个备选策略。通常,库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。
- 若XXX安装时没有安装到系统目录,则无法自动找到XXXConfig.cmake,需要在CMakeLists.txt最前面添加XXX的搜索路径。
4.4.9 列表操作
(1)操作如下:
LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules)
list(LENGTH <list><output variable>)
list(GET <list> <elementindex> [<element index> ...]<output variable>)
list(APPEND <list><element> [<element> ...])
list(FIND <list> <value><output variable>)
list(INSERT <list><element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value>[<value> ...])
list(REMOVE_AT <list><index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
list(SORT <list>)
- LENGTH:返回list的长度。
- GET:返回list中index的element到value中。
- APPEND:添加新element到list中。
- FIND:返回list中element的index,没有找到返回-1。
- INSERT:将新element插入到list中index的位置。
- REMOVE_ITEM:从list中删除某个element。
- REMOVE_AT:从list中删除指定index的element。
- REMOVE_DUPLICATES:从list中删除重复的element。
- REVERSE:将list的内容反转。
- SORT:将list按字母顺序排序。
5. 上手实战
(1)前面介绍了cmake的基本用法,我们知道cmake命令会执行目录下的 CMakeLists.txt 配置文件里面的配置项,我们在实际的工程项目中的一个基本的CMakeLists.txt的配置文件内容如下:
cmake_minimum_required (VERSION 2.8) #要求cmake最低的版本号
project (demo) # 定义当前工程名字#用于配置构建类型为 Debug 模式,以便在编译时生成带有调试信息的可执行文件,
#方便使用调试器(如 GDB)进行断点调试。
set(CMAKE_BUILD_TYPE "Debug")#设置当前项目的构建类型为 Debug
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)#手动将 -g 添加到 C++ 编译选项中add_executable(main main.c)
#进入子目录下执行 CMakeLists.txt文件 这里的lib和tests里面都有可编译的代码文件
add_subdirectory(lib)
add_subdirectory(tests)
(2)注意:
set(CMAKE_BUILD_TYPE "Debug")
- 效果:
- 编译器会生成带有调试信息的二进制文件(例如 GCC 的 -g 参数);
- 不会进行优化(或仅进行极低程度的优化),便于调试;
- 常见构建类型包括:
- Debug:带调试信息;
- Release:优化级别高,不带调试信息;
- RelWithDebInfo:优化后的调试信息;
- MinSizeRel:最小体积优化。
5.1 简单示例
(1)在项目目录下创建了 main.cpp 再在同级目录下创建一个 CMakeLists.txt 文件:
- main.cpp文件内容:
#include <iostream>int main()
{std::cout << "Hello World" << std::endl;return 0;
}
- CMakeLists.txt文件内容:
# CMakeLists.txt文件cmake_minimum_required(VERSION 3.0) # 要求的cmake的最低版本号
project(demo) # 定义当前工程的名字
set(CMAKE_BUILD_TYPE "Debug") # 设置DEBUG模式
add_executable(main main.cpp)
(2)创建build文件夹来执行cmake:
(3)使用 ls 查看当前目录下的文件 ,可以看到目录下多出了很多文件,除了CMake生成的中间文件,还生成了Makefile文件:
(4)接着我们再使用 make 进行编译 ,可以看到生成了可执行文件 main:
(5)运行文件如下:
(6)Makefile中也实现了clean,使用make clean命令清除生成的文件如下:
(7)但是如果需要编译的有多个源文件的话:
- 可以都添加到 add_executable(main main.cpp test.cpp) 列表当中,但是如果源文件太多,一个个添加到 add_executable(main main.cpp test.cpp) 的源文件列表中,就太过于麻烦了,此时可以用 aux_source_directory(dir var) 来定义源文件列表,使用如下:
cmake_minimum_required (VERSION 2.8)
project (demo)
aux_source_directory(. SRC_LIST) # 定义变量SRC_LIST,存储当前目录下(.)的所有源文件
add_executable(main ${SRC_LIST})
- 但是同样 aux_source_directory() 也存在着弊端,它会把指定目录下的所有源文件都加进来,可能会加入一些我们不需要的文件,此时我们可以使用 set 命令去新建变量来存放需要的源文件,如下
cmake_minimum_required (VERSION 2.8)
project (demo)
set( SRC_LIST
./main.cpp
./test.cpp)
add_executable(main ${SRC_LIST})
5.2 一个正式的项目构建
(1)一个正式的源码工程应该有这几个目录:
- bin:存放最终的可执行文件。
- bulid:放的是构建该项目cmake编译什么的中间文件。
- include:存放该项目的头文件。
- src:存放该项目的源代码文件。
(2)CMakeLists.txt如下:
cmake_minimum_required (VERSION 2.8)project (math)# 设置二进制可执行文件的输出位置为${PROJECT_SOURCE_DIR}/bin
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#添加头文件路径,相当于makefile里面的-I,这样编译器就能找到在 #include "xxx.h" 中引用的头文件。
include_directories(${PROJECT_SOURCE_DIR}/include)
#将 src/ 目录下的所有 .cpp 文件收集到变量 SRC_LIST 中。不包括子目录中的源文件;
#如果以后加入其他语言文件(如 .c)也不会包含;
aux_source_directory (src SRC_LIST)
add_executable (main main.cpp ${SRC_LIST})
- 然后在build目录里面执行cmake … 命令,其中 … 表示上一级目录,使用 cmake … 表示cmake需要使用上一层目录下的CMakeLists.txt文件来配置项目,并生成Makefile文件。这样所有的编译中间文件都会在build目录下,最终的可执行文件会在bin目录里面。
(3)静态库和动态库的编译控制:
- 我们假设将上面的sum和minor源文件直接生成静态库或者动态库,让外部程序进行链接使用,结构如下:
- lib:里面存放编译生成的库文件。
- test:项目的测试代码。
(4)最外层的CMakeLists.txt是总控制编译,内容如下:
cmake_minimum_required (VERSION 2.8)
project (math)#进入到src和test目录下去执行CMakeLists.txt
add_subdirectory (test)
add_subdirectory (src)
(5)src里面的源代码要生成静态库和动态库,CMakeLists.txt的內容如下:
#设置库文件的生成路径
set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)# 生成库,动态库是SHARED,静态库是STATIC
add_library (sum SHARED sum.cpp)
add_library (minor SHARED minor.cpp)
# set_target_properties用途是修改目标(target)在构建时生成的库文件名。修改库的名字
# set_target_properties (sum PROPERTIES OUTPUT_NAME "libsum")
# set_target_properties (minor PROPERTIES OUTPUT_NAME "libminor")
(6)test 里面的 CMakeLists.txt 內容如下:
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)include_directories (../include) # 头文件搜索路径link_directories (${PROJECT_SOURCE_DIR}/lib) # 库文件搜索路径add_executable (main main.cpp) # 指定生成的可执行文件target_link_libraries (main sum minor) # 执行可执行文件需要依赖的库
(7)然后在bulid目录下执行cmake …命令,接着执行make,可以查看生成的可执行文件了。
(8)项目文件的整体结构:
(9)检验可执行文件其链接的库有哪些:
6. shell脚本一键自动化方式
(1)有了上面的手动构建项目方式,我们还有shell脚本的一件自动方式。
- 我们还是拿上述的示例来举例子,在示例的基础上在同级目录下新增一个 一键执行shell脚本文件 ,如下:
(2)脚本具体实现如下:
#cmake_build.sh#!/bin/bash
export LC_ALL=C# 获取脚本所在目录的绝对路径
curDIR=$(cd $(dirname $0); pwd)
projectMainDir="${curDIR}/build"
product_main_exe_name="product_cmake_test"function printResultBanner()
{echo -e "\n=========================================================>"echo -e "$1!!!"echo -e "=========================================================<"
}# 检查并创建构建目录
if [ ! -d ${projectMainDir} ];thenmkdir -p ${projectMainDir}
fi# 生成可执行文件并检查构建是否成功
cd ${projectMainDir}
rm -rf * && cmake .. && cmake .. && make clean && make
if [ $? -ne 0 ];thenprintResultBanner "FAILED : make compile errors and not continue !"exit 1
fi# 运行可执行程序
printResultBanner "INFO : Start To Run The Executable!!!"
${projectMainDir}/${product_main_exe_name}
if [ $? -ne 0 ];thenprintResultBanner "FAILED : run [${projectMainDir}/${product_main_exe_name}] errors and not continue !"exit 1
fi# 给出成功提示
printResultBanner "SUCCESS : Congratulations, all test cases have passed successfully!!!"
exit 0
- 执行shell脚本后就可以直接运行了!!
7. CMake 总结
(1)CMake 工作流程:
(2)核心组件:
(3)基础语法与命令:
- 项目结构:
cmake_minimum_required(VERSION 3.10) # 最低 CMake 版本
project(MyProject VERSION 1.0 # 项目名称和版本DESCRIPTION "Sample Project"LANGUAGES CXX)
- 可执行文件与库:
# 添加可执行文件
add_executable(myapp main.cpp utils.cpp)# 添加静态库
add_library(mylib STATIC lib.cpp)# 添加动态库
add_library(mylib SHARED lib.cpp)# 链接库到可执行文件
target_link_libraries(myapp PRIVATE mylib)
- 变量与条件控制:
set(MY_VAR "value") # 设置变量
message(STATUS "Var: ${MY_VAR}") # 打印变量if(WIN32)# Windows 平台特定设置
elseif(UNIX)# Unix-like 平台设置
endif()
- 文件操作:
file(GLOB SOURCES "src/*.cpp") # 获取文件列表(谨慎使用)
file(COPY data/ DESTINATION ${CMAKE_BINARY_DIR}/data)
(4)高级特性:
- 目标属性(Modern CMake):
add_library(mylib lib.cpp)
target_include_directories(mylib PUBLIC include) # 头文件目录
target_compile_features(mylib PRIVATE cxx_std_17) # C++17 标准
target_compile_options(mylib PRIVATE -Wall) # 编译选项
target_link_libraries(mylib PUBLIC Threads::Threads) # 链接线程库
- 模块化设计:
# 子目录管理
add_subdirectory(src) # 包含子目录的 CMakeLists.txt# 查找依赖包
find_package(OpenCV REQUIRED)
target_link_libraries(myapp PRIVATE OpenCV::opencv_core)
- 生成器表达式:
# 条件编译选项
target_compile_definitions(mylibPRIVATE $<$<CONFIG:Debug>:DEBUG_MODE=1>
)# 跨平台路径处理
target_include_directories(mylibPRIVATE "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>""$<INSTALL_INTERFACE:include>")
- 安装规则:
install(TARGETS myapp mylibRUNTIME DESTINATION binLIBRARY DESTINATION libARCHIVE DESTINATION lib)install(DIRECTORY include/ DESTINATION include)
install(FILES LICENSE.txt DESTINATION doc)
(5)目录结构规范:
project_root/
├── CMakeLists.txt
├── cmake/ # 自定义 CMake 模块
├── include/ # 公共头文件
├── src/ # 源代码
│ ├── CMakeLists.txt
│ └── ...
├── tests/ # 测试代码
└── external/ # 第三方依赖
(6)资源推荐:
- 官方文档:https://cmake.org/documentation/。
- 书籍:《Professional CMake: A Practical Guide》。
- 在线教程:https://cmake.org/cmake/help/latest/guide/tutorial/index.html。
(7)总结:
- CMake 已成为 C/C++ 生态的事实标准构建工具,掌握其核心概念和现代用法,能显著提升项目的可维护性和跨平台兼容性。遵循 “Target-Based” 设计哲学,是构建高质量项目的基础。