Linux——CMake的快速入门上手和保姆级使用介绍、一键执行shell脚本
目录
一、前言
二、CMake简介
三、CMake与其他常见的构建、编译工具的联系
四、CMake入门
1、CMake的使用注意事项
2、基本的概念和术语
3、CMake常用的预定义变量
4、CMakeLists.txt文件的基本结构
五、上手实操
1、示例
编辑
2、一个正式的工程构建
2.1基本构建
2.2静态库和动态库的编译控制
六、shell脚本一键自动化方式
一、前言
在我们刚开始学习Linux系统编程的时候,我们就接触到了 Makefile ,它是用于自动化构建软件项目的工具,主要用于编译源代码文件、管理项目依赖关系。
Makefile 是一个文件。它是一个工程文件的编译规则,它记录了原始码如何编译的详细信息、描述了整个工程的编译链接等规则。
Makefile 存在的意义就是为了构建依赖关系和依赖原理。
Makefile 带来的好处是“自动化编译”,一旦写好的话,只需要一个make命令,整个工程的完全自动编译,极大的提高了软件开发的效率。
且大多数的IDE软件都是集成了make,例如Visual C++的 nmake,Linux下的 GNU make,Qt的 qmake等等,不同的IDE所集成的make工具所遵循的规范和标准都不同,也就导致其语法、格式不同,也就不能很好的跨平台编译,如果软件想跨平台,必须要保证能够在不同的平台上编译,如果使用上面的make工具的话,就得为每一个标准写一个Makefile文件,会再次使得工作繁琐起来。
所以CMake的出现允许开发者指定整个工程的编译流程,再跟据编译平台,生成本地化的Makefile和工程文件,最后只需要make编译即可。
二、CMake简介
CMake是“Cross Platform Make”的缩写,CMake是开源的、跨平台的构建工具,可以让我们通过简单的配置文件去生成本地的 Makefile ,这个配置文件时独立于运行平台和编译器的,这样就不用亲自去编写Makefile了,而且配置文件可以直接拿到其他平台上去使用,不用修改,非常的奈斯。
CMake的安装我们就直接跳过了,在网上随便可以找到。
安装好之后,我们可以使用 cmake -version 命令查看安装成功的cmake的版本
三、CMake与其他常见的构建、编译工具的联系
CMake有着跨平台自动化构建系统的特性,其不直接调用编译器进行构建,而是生成适用于目标平台的构建文件。通过 CMake,开发者可以编写一套与平台无关的配置文件(CMakeLists.txt)然后根据目标平台的不同,生成相应的构建文件。如Makefile或者Visual Studio工程文件等,从而简化了构建过程。
在这个构建生态系统中,GCC作为编译器,负责将源代码转换为可执行文件或库;Make工具根据Makefile中的指令执行编译和链接;而CMake则作为构建配置的中心,生成Makefile或其他构建脚本。此外,Ninja作为注重速度的构建工具,与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 |
四、CMake入门
接下来我们将学习CMake地基本概念,其中包括项目定义、目标创建及如何通过CMakeLists.txt文件来控制整个的构建过程。
1、CMake的使用注意事项
- CMake构建专用定义文件,文件名严格区分大小写。在工程中使用 CMake 时,通常会在项目的根目录放置一个 CMakeLists.txt 文件来描述如何构建项目。这个文件名是固定的,不能随意更改。
- 工程存在多个目录的情况下,可以在每个目录下都放一个CMakeLists.txt文件,这有助于分层管理复杂的项目结构。通过这种方式,可以更方便地组织代码,并且可以对不同部分的源代码进行独立配置。
- 工程存在多个目录的情况下,也可以只使用一个CMakeLists.txt文件管理,这适用于较为简单的项目或者当开发者希望将所有构建逻辑集中在一个地方时。
- 严格区分大小写,在某些操作系统(如 Linux)上,文件系统是区分大小写的。这意味着 CMakeLists.txt和 cmakeLists.txt 会被视为两个不同的文件。因此,在这些平台上,必须确保文件名的大小写正确无误。然而,这条规则并不适用于文件内容中的变量命名或其他定义。
CMake支持大写、小写、混合大小写的命令。如果在编写CMakeLists.txt文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。
- 严格大小写相关,名称只能用字母、数字、下划线
- 使用 ${} 来引用变量
- 参数之间使用空格进行间隔
2、基本的概念和术语
- Project: 一个逻辑概念,定义了项目的名称和属性。
- Target: 构建系统中的一个目标,如可执行文件、库文件等。
- Source Files: 需要编译的源代码文件。
- Binary Directory: 存放编译生成的二进制文件的目录。
- Source Directory: 存放源代码的目录。
- Generator: 用于生成特定构建系统的程序,如Unix Makefiles。
- Variable: CMake中的变量,用于存储和传递配置信息。
- Command: CMake中的命令,用于执行构建配置的操作。
3、CMake常用的预定义变量
变量名 | 描述 | 示例值(取决于项目) |
---|---|---|
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、CMakeLists.txt文件的基本结构
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 文件]]
下面是对基本结构的解释
1、cmake_minimum_required(VERSION 3.10):指定CMake的最低版本要求,确保项目的构建依赖于特定版本的CMake特性
2、 project(MyProject CXX):指定项目名称为“MyProject”,并指定项目所使用的编程语言为C++
3、添加可执行文件
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、set的使用:
4.1定义变量
4.1.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)
4.1.2缓存变量
缓存变量是指那些被存储在
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)
4.1.3环境变量
用于设置或清除环境变量(Environment Variables)
set(ENV{<variable>} [<value>])# variable:只能有一个 # value:一般来说,只有一个,为空时,将清除之前设置的变量值,多个时,取值最近的一个,之后的值将被忽略
4.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)
4.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" )
5、搜索文件
如果在一个项目里的源文件很多,在编写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)
6、指定头文件的搜索路径
当使用 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 )
7、CMake制作库文件
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...]) set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)//设置库文件生成路径(放在哪里)
例子:
参数 含义 <name>
库名,是你为这个库指定的名称,比如 mylib
STATIC
表示这是一个静态库(.a 文件,在 Linux/macOS 上)或 (.lib 文件,在 Windows 上) SHARED
表示这是一个动态库(共享库,.so 文件在 Linux,.dll 在 Windows) MODULE
通常用于插件模块(不参与链接,仅加载),较少使用 EXCLUDE_FROM_ALL
可选参数,表示该库不会被默认构建,需要显式调用构建 source1 source2 ...
构建该库所需的源文件列表,如 .cpp
,.c
,.cc
等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)。8、链接库
target_link_libraries(<target> [PRIVATE|PUBLIC|INTERFACE] <item>...)
<target>
:你的目标名称,比如通过add_executable()
或add_library()
定义的名称。PRIVATE
:仅当前目标需要这个库;PUBLIC
:当前目标和依赖它的目标都需要这个库;INTERFACE
:仅依赖它的目标需要这个库。使用示例:
add_executable(myapp main.cpp) target_link_libraries(myapp PRIVATE mylib)
这样就只让
myapp
链接mylib
,不影响其他目标。9、在目录下查找所有的源文件
aux_source_directory(<dir> <varname>)
是一个用于自动收集目录中所有源文件路径的命令,常用于简化源码列表的定义。
参数 含义 <dir>
要扫描的目录(相对于当前 CMakeLists.txt
所在目录)<varname>
用于存储找到的所有源文件路径的变量名 功能说明
- 自动查找源文件:会递归查找指定目录下的所有
.c
,.cpp
,.cxx
,.m
,.mm
,.rc
,.inl
,.txx
等常见源代码文件;- 不包括子目录中的文件:只查找指定目录中的文件,不会进入其子目录;
- 生成一个包含所有源文件路径的列表:结果以相对路径形式保存在
<varname>
中;- 适用于小型项目或模块:对于结构简单、源文件数量不多的情况非常方便。
10、向当前工程添加存放源文件的子目录
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_BUILD])
用于将一个子目录添加到构建系统中,并处理该目录下的
CMakeLists.txt
文件。它是组织多模块、多层次项目的常用方式。
参数 含义 source_dir
必须参数,指定包含 CMakeLists.txt
的子目录路径(相对于当前CMakeLists.txt
所在目录)binary_dir
可选参数,指定该子目录构建输出的目标路径(默认与源路径一致) EXCLUDE_FROM_BUILD
可选参数,表示该子目录中的目标不会被默认构建(仅当被依赖时才会构建) 示例:
add_subdirectory(src) add_subdirectory(libs/utils)
这会依次进入
src/
和libs/utils/
目录,并执行其中的CMakeLists.txt
文件。11、打印输出信息
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
等相关设置启用时显示)12、搜索外部库
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的搜索路径。
13、列表操作
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按字母顺序排序
五、上手实操
学习了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)
注:set(CMAKE_BUILD_TYPE "Debug")
- 效果:
- 编译器会生成带有调试信息的二进制文件(例如 GCC 的
-g
参数); - 不会进行优化(或仅进行极低程度的优化),便于调试;
- 常见构建类型包括:
Debug
:带调试信息;Release
:优化级别高,不带调试信息;RelWithDebInfo
:优化后的调试信息;MinSizeRel
:最小体积优化。
- 编译器会生成带有调试信息的二进制文件(例如 GCC 的
1、示例
在项目目录下创建了 main.cpp 再在同级目录下创建一个 CMakeLists.txt 文件
#CMakeLists.txt文件cmake_minimum_required(VERSION 3.0)#要求的cmake的最低版本号
project(demo) #定义当前工程的名字
set(CMAKE_BUILD_TYPE "Debug") #设置DEBUG模式
add_executable(main main.cpp)
#include<iostream>
int main()
{std::cout<<"This is a cmake test!"<<std::endl;return 0;
}
执行 cmake .
使用 ls 查看当前目录下的文件 ,可以看到目录下多出了很多文件,除了CMake生成的中间文件,还生成了Makefile文件
接着我们再使用 make 进行编译 ,可以看到生成了可执行文件 main
运行文件如下
Makefile中也实现了clean,使用make clean命令清除生成的文件如下
但是如果需要编译的有多个源文件的话,可以都添加到 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})
2、一个正式的工程构建
2.1基本构建
一个正式的源码工程应该有这几个目录
- bin:存放最终的可执行文件
- bulid:放的是构建该项目cmake编译什么的中间文件
- include:存放该项目的头文件
- src:存放该项目的源代码文件
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目录里面
2.2静态库和动态库的编译控制
我们假设将上面的sum和minor源文件直接生成静态库或者动态库,让外部程序进行链接使用,结构如下
- lib:里面存放编译生成的库文件
- test:项目的测试代码
最外层的CMakeLists.txt是总控制编译,内容如下:
cmake_minimum_required (VERSION 2.8)
project (math)#进入到src和test目录下去执行CMakeLists.txt
add_subdirectory (test)
add_subdirectory (src)
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")
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) # 执行可执行文件需要依赖的库
然后在bulid目录下执行cmake ..命令,接着执行make,可以查看生成的可执行文件了。
检验可执行文件其链接的库有哪些
六、shell脚本一键自动化方式
有了上面的手动构建项目方式,我们还有shell脚本的一件自动方式
我们还是拿示例一来举例子,在示例一的基础上在同级目录下新增一个 一键执行shell脚本文件 ,如下:
#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脚本后就可以直接运行啦!
感谢阅读!
相关文章:

Linux——CMake的快速入门上手和保姆级使用介绍、一键执行shell脚本
目录 一、前言 二、CMake简介 三、CMake与其他常见的构建、编译工具的联系 四、CMake入门 1、CMake的使用注意事项 2、基本的概念和术语 3、CMake常用的预定义变量 4、CMakeLists.txt文件的基本结构 五、上手实操 1、示例 编辑 2、一个正式的工程构建 2.1基本构…...
Keil5 MDK 安装教程
## 简介 Keil MDK(Microcontroller Development Kit)是ARM开发的一款集成开发环境(IDE),主要用于ARM Cortex-M系列微控制器的开发。MDK包含了μVision IDE和调试器、ARM C/C编译器、中间件组件等工具。本教程将指导您完…...
深入浅出 IPFS 在 DApps 和 NFT 中的应用:以 Pinata 实战为例
目录 IPFS背景什么是 IPFS?IPFS 在 DApps 与 NFT 中的作用什么是 Pinata?为什么使用它?使用原生IPFS上传下载文件(HTML + JavaScript 示例)使用Pinata上传下载文件(HTML + JavaScript 示例)注册并创建APIKey使用 Pinata 上传文件和JSON(HTML + JavaScript 示例)总结IP…...

如何高效集成MySQL数据到金蝶云星空
MySQL数据集成到金蝶云星空:SC采购入库-深圳天一-OK案例分享 在企业信息化建设中,数据的高效流转和准确对接是实现业务流程自动化的关键。本文将聚焦于一个具体的系统对接集成案例——“SC采购入库-深圳天一-OK”,详细探讨如何通过轻易云数据…...

通过POI实现对word基于书签的内容替换、删除、插入
一、基本概念 POI:即Apache POI, 它是一个开源的 Java 库,主要用于读取 Microsoft Office 文档(Word、Excel、PowerPoint 等),修改 或 生成 Office 文档内容,保存 为对应的二进制或 XML 格式&a…...

FlashInfer - 测试的GPU H100 SXM、A100 PCIe、RTX 6000 Ada、RTX 4090
FlashInfer - 测试的GPU H100 SXM、A100 PCIe、RTX 6000 Ada、RTX 4090 flyfish GPU 技术参数术语 1. Memory bandwidth (GB/s) 中文:显存带宽(单位:GB/秒) 定义:显存(GPU 内存)与 GPU 核心…...
MCP:开启AI的“万物互联”时代
MCP:开启AI的“万物互联”时代 ——从协议标准到生态革命的技术跃迁 引言:AI的“最后一公里”困境 在2025年的AI技术浪潮中,大模型已从参数竞赛转向应用落地之争。尽管模型能生成流畅的对话、创作诗歌甚至编写代码,但用户逐渐发现…...
企业级IP代理解决方案:负载均衡与API接口集成实践
在全球化业务扩张与数据驱动决策的背景下,企业级IP代理解决方案通过负载均衡技术与API接口集成,可有效应对高频请求、反爬机制及合规风险。以下是基于企业级场景的核心实践要点: 一、负载均衡与IP代理的深度协同 动态IP池的负载均衡策略 轮询…...
Vector和list
一、Vector和list的区别——从“它们是什么”到“区别在哪儿” 1. 它们是什么? Vector:类似于一排排整齐的书架(数组),存放元素时,元素排成一条线,连续存储。可以很快通过编号(索引…...

MongoDB从入门到实战之Windows快速安装MongoDB
前言 本章节的主要内容是在 Windows 系统下快速安装 MongoDB 并使用 Navicat 工具快速连接。 MongoDB从入门到实战之MongoDB简介 MongoDB从入门到实战之MongoDB快速入门 MongoDB从入门到实战之Docker快速安装MongoDB 下载 MongoDB 安装包 打开 MongoDB 官网下载页面&…...

Excelize 开源基础库发布 2.9.1 版本更新
Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Excel、WPS、OpenOffice 等办公软件创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式…...
package-lock.json能否直接删除?
package-lock.json能否直接删除? package-lock.json 生成工具:由 npm 自动生成。 触发条件:当运行 npm install 时,如果不存在 package-lock.json,npm 会创建它;如果已存在,npm 会根据它精确安…...

Profibus DP主站转Modbus RTU/TCP网关接艾默生流量计与上位机通讯
Profibus DP主站转Modbus RTU/TCP网关接艾默生流量计与上位机通讯 艾默生流量计与Profibus DP主站转Modbus RTU/TCP网关的通讯,是现代工业自动化中的一个关键环节。为了实现这一过程,我们需要了解一些基础概念和具体操作方法。 在工业自动化系统中&…...
promise的说明
目录 1.说明 2.创建promise 3.处理promise结果 4.promise的链式调用 5.静态方法 6.错误处理及误区 7.then() 内部进行异步操作时,需返回新的 Promise 8.promise链式调用控制异步方法的执行顺序 9.总结 1.说明 Promise 是 JavaScript 中处理异步操作的核心对…...
Pass-the-Hash攻击原理与防御实战指南
当黑客说出"我知道你的密码"时,可能连他们自己都不知道你的真实密码。在Windows系统的攻防战场上,Pass-the-Hash(哈希传递攻击)就像一把可以复制的万能钥匙——攻击者不需要知道密码明文,仅凭密码的…...

Linux proc文件系统 内存影射
文章目录 常见的内存分配函数/proc/pid/ 目录解析 用户进程的内存空间分配算法mmap 分配大内存可能不在堆中换为 malloc 现象相同 常见的内存分配函数 malloc / calloc / realloc(来自 C 标准库) void *malloc(size_t size):分配 size 字节…...

五、Hadoop集群部署:从零搭建三节点Hadoop环境(保姆级教程)
作者:IvanCodes 日期:2025年5月7日 专栏:Hadoop教程 前言: 想玩转大数据,Hadoop集群是绕不开的一道坎。很多小伙伴一看到集群部署就头大,各种配置、各种坑。别慌!这篇教程就是你的“救生圈”。 …...

电流检测放大器的优质选择XBLW-INA180/INA181
前言: 在当前复杂的国际贸易环境下,关税的增加使得电子元器件的采购成本不断攀升,电子制造企业面临着巨大的成本压力。为了有效应对这一挑战,实现国产化替代已成为众多企业降低生产成本、保障供应链稳定的关键战略。对此芯伯乐推出…...
5.18-AI分析师
强化练习1 神经网络训练案例(SG) #划分数据集 #以下5行需要背 folder datasets.ImageFolder(rootC:/水果种类智能训练/水果图片, transformtrans_compose) n len(folder) n1 int(n*0.8) n2 n-n1 train, test random_split(folder, [n1, n2]) #训…...
毕业论文,如何区分研究内容和研究方法?
这个问题问得太好了!😎 “研究内容”和“研究方法”经常被初学者(甚至一些老油条)混淆,尤其写论文开题报告时,一不小心就“内容”和“方法”全混在一块儿,连导师都看懵。 今天就来给大家一文讲…...

# 深度剖析LLM的“大脑”:单层Transformer的思考模式探索
简单说一下哈 —— 咱们打算训练一个单层 Transformer 加上稀疏自编码器的小型百万参数大型语言模型(LLM),然后去调试它的思考过程,看看这个 LLM 的思考和人类思考到底有多像。 LLMs 是怎么思考的呢? 开源 LLM 出现之后…...
三种常见接口测试工具(Apipost、Apifox、Postman)
三种常见接口测试工具(Apipost、Apifox、Postman)的用法及优缺点对比总结: 🔧 一、Apipost ✅ 基本用法 支持 RESTful API、GraphQL、WebSocket 等接口调试自动生成接口文档支持环境变量、接口分组、接口测试用例编写可进行前置…...
EF Core 数据库迁移命令参考
在使用 Entity Framework Core 时,若你希望通过 Package Manager Console (PMC) 执行迁移相关命令,以下是常用的 EF Core 迁移命令: PMC 方式 ✅ 常用 EF Core PMC 命令(适用于迁移) 操作PMC 命令添加迁移Add-Migra…...
剖析提示词工程中的递归提示
递归提示:解码AI交互的本质,构建复杂推理链 递归提示的核心思想,正如示例所示,是将一个复杂任务分解为一系列更小、更易于管理、逻辑上前后关联的子任务。每个子任务由一个独立的提示来驱动,而前一个提示的输出(经过必要的解析和转换)则成为下一个提示的关键输入。这种…...
互联网大厂Java求职面试:AI内容生成平台下的高并发架构设计与性能优化
互联网大厂Java求职面试:AI内容生成平台下的高并发架构设计与性能优化 场景背景: 郑薪苦是一名经验丰富的Java开发者,他正在参加一家匿名互联网大厂的技术总监面试。这家公司专注于基于AI的内容生成平台,支持大规模用户请求和复杂…...
用Redis的List实现消息队列
介绍如何在 Spring Boot 中使用 Redis List 的 BRPOPLPUSH命令来实现一个线程安全且可靠的消息队列。 整合Redis 整合Redis 用Redis的List实现消息队列 Redis的List相关指令 **「LPUSH key element [element ...]」**把元素插入到 List 的首部,如果 List 不存在…...

【C++】类与对象【下】
文章目录 再谈构造函数构造函数的赋值构造函数体赋值:初始化列表explicit关键字 static成员概念特性 C11中成员初始化的新玩法友元友元类 内部类概念 再谈构造函数 构造函数的赋值 构造函数体赋值: 在创建对象时,编译器会通过调用构造函数…...
Python uv包管理器使用指南:从入门到精通
Python uv包管理器使用指南:从入门到精通 作为一名Python开发者,你是否曾经为虚拟环境管理和依赖包安装而头疼?今天我要向大家介绍一个强大的工具——uv包管理器,它将彻底改变你的Python开发体验。 什么是uv包管理器?…...

无人机避障——如何利用MinumSnap进行对速度、加速度进行优化的轨迹生成(附C++python代码)
🔥轨迹规划领域的 “YYDS”——minimum snap!作为基于优化的二次规划经典,它是无人机、自动驾驶轨迹规划论文必引的 “开山之作”。从优化目标函数到变量曲线表达,各路大神疯狂 “魔改”,衍生出无数创新方案。 &#…...
高德地图在Vue3中的使用方法
1.地图初始化 容器创建:通过 <div> 标签定义地图挂载点。 <div id"container" style"height: 300px; width: 100%; margin-top: 10px;"></div> 密钥配置:绑定高德地图安全密钥,确保 API 合法调用。 参…...