一、介绍
CMake 是一个开源、跨平台的构建系统,主要用于软件的构建、测试和打包。
CMake 使用平台无关的配置文件 CMakeLists.txt 来控制软件的编译过程,并生成适用于不同编译器环境的项目文件。例如,它可以生成 Unix 系统的 Makefile、Windows 下的 Visual Studio 项目文件或 Mac 的 Xcode 工程文件,从而简化了跨平台和交叉编译的工作流程。CMake 并不直接构建软件,而是产生标准的构建文件,然后使用这些文件在各自的构建环境中构建软件。
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功能
安装命令如下所示:
sudo apt update
sudo apt install cmake
# 检验是否安装成功
cmake --version二、简单使用
创建如下文件和目录:
然后编写 CMakeLists.txt 文件:
# 声明所需的cmake版本
cmake_minimum_required(VERSION 3.0.0)
# 定义项目工程名称
project(test)
# 设置生成目标
add_executable(main main.cc) 其中 main.cc 文件简单写个 main 函数即可,然后进行下述操作:
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake$ mkdir build # 外部构建
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake$ cd build
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake/build$ cmake .. # cmake加上规则文件位置
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/liren/chat_platform/server/example/cmake/build
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake/build$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake/build$ make # 然后根据生成的文件进行make即可
[ 50%] Building CXX object CMakeFiles/main.dir/main.cc.o
[100%] Linking CXX executable main
[100%] Built target main
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake/build$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake main Makefile 我们来看一下 CMakeLists.txt 文件, 这个文件是 cmake 的构建定义文件, 其文件名是大小写相关的。下面依次介绍一下在该文件中添加的三个指令:
-
cmake_minimum_required(
版本):指定使用的cmake的最低版本。可选,如果不加会有警告 -
project(
项目名称):定义工程名称, 并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可。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(
可执行程序名源文件名):定义工程会生成一个可执行程序。- 注意:这里的可执行程序名和前面
project中的项目名没有任何关系 - 源文件名可以是一个也可以是多个,如有多个可用
空格或;间隔
- 注意:这里的可执行程序名和前面
-
cmake
CMakeLists.txt文件所在路径:将CMakeLists.txt文件编辑好之后,就可以执行cmake命令了- 当执行
cmake命令之后,CMakeLists.txt中的命令就会被执行,所以一定要注意给cmake命令指定路径的时候一定不能出错。
- 当执行
Ⅱ. CMake 的使用
⭐ 注释
CMake 使用 # 进行行注释, 它可以放在任何位置。
# 这是一个 CMakeLists.txt 文件
cmake_minimum_required(VERSION 3.0.0) CMake 支持大写、小写、混合大小写的命令。如果在编写 CMakeLists.txt 文件时使用的工具有对应的命令提示,那么大小写随缘即可,不要太过在意。
⭐ 内部构建与外部构建
对于前面的使用例子,我们观察一下当我们执行 cmake . 指令之后源文件所在的目录是否多了一些文件?
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake$ tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── cmake_install.cmake
├── CMakeLists.txt
├── main.cc
└── Makefile
1 directory, 5 files 我们可以看到如果在 CMakeLists.txt 文件所在目录执行了 cmake 命令之后就会生成一些目录和文件,如果再基于 makefile 文件执行 make 命令,程序在编译过程中还会生成一些中间文件和一个可执行文件,这样会导致整个项目目录看起来很混乱,不太容易管理和维护。这其实被称为 内部构建,但 CMake 强烈推荐的做法是 外部构建。
此时我们可以把生成的这些与项目源码无关的文件统一放到一个对应的目录里边,比如将这个目录命名为 build,这就叫做 外部构建。
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake$ mkdir build
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake$ cd build
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake/build$ cmake .. # 在build中构建
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/liren/chat_platform/server/example/cmake/build
liren@hcss-ecs-7ba8:~/chat_platform/server/example/cmake/build$ tree .. -L 2 # 可以看到整洁很多!
..
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ └── Makefile
├── CMakeLists.txt
└── main.cc
2 directories, 5 files 注意事项:现在 cmake 命令是在 build 目录中执行的,但是 CMakeLists.txt 文件是 build 目录的上一级目录中,所以 cmake 命令后指定的路径为 ..,即当前目录的上一级目录。
⭐ 定义变量 && 追加变量
假如我们的项目中存在多个源文件,并且这些源文件需要被反复使用,每次都直接将它们的名字写出来确实是很麻烦,此时我们就可以定义一个变量,将文件名对应的字符串存储起来,在 cmake 里定义变量需要使用 set 指令。
# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])VAR表示变量名VALUE表示变量值
举个例子:
set(SRC_LIST test1.c test2.c test3.c)
add_executable(app ${SRC_LIST}) 注意:变量使用 ${} 方式取值,但是在 if 控制语句中是直接使用变量名。
此外,我们可以用 list() 来追加变量,如下所示:
set(variable_name "")
list(APPEND variable_name content)⭐ 字符串内容替换 string(REPLACE ...)
string(REPLACE <find> <replace> <output_variable> <input_string>)<find>:要查找的子字符串<replace>:用于替换的字符串<output_variable>:替换后的结果将存储在该变量中<input_string>:输入字符串,CMake将在此字符串中执行替换操作
举个例子:假设你有一个字符串 "hello world, world!",并且你希望将其中所有的 "world" 替换为 "CMake"。
set(input_string "hello world, world!")
string(REPLACE "world" "CMake" output_string "${input_string}")
message(STATUS "${output_string}")
# 输出: hello CMake, CMake!⭐ 指定使用的 C++ 标准 CMAKE_CXX_STANDARD
在 CMake 中想要指定 C++ 标准有两种方式:
-
在
CMakeLists.txt中通过set命令指定CMake:(推荐)# 增加-std=c++11 set(CMAKE_CXX_STANDARD 11) # 增加-std=c++14 set(CMAKE_CXX_STANDARD 14) -
在执行
cmake命令的时候指定出这个宏的值:# 增加-std=c++11 cmake CMakeLists.txt 文件路径 -DCMAKE_CXX_STANDARD=11 # 增加-std=c++14 cmake CMakeLists.txt 文件路径 -DCMAKE_CXX_STANDARD=14
⭐ 指定可执行文件输出的路径 CMAKE_RUNTIME_OUTPUT_DIRECTORY
在 CMake 中指定可执行程序输出的路径,对应一个宏叫做 CMAKE_RUNTIME_OUTPUT_DIRECTORY,它的值也是通过 set 命令进行设置的。
# 定义 HOME 变量,存储一个绝对路径
set(HOME /home/xxx)
# 将拼接好的路径值设置给 CMAKE_RUNTIME_OUTPUT_DIRECTORY
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${HOME}/bin) 如果此处指定可执行程序生成路径的时候使用的是相对路径 ./xxx/xxx,那么这个路径中的 ./ 对应的就是 makefile 文件所在的那个目录。
⭐ 搜索文件 aux_source_directory()
如果一个项目里边的源文件很多,在编写 CMakeLists.txt 文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦了。所以在 CMake 中为我们提供了搜索文件的命令:
① aux_source_directory(推荐!!!)
aux_source_directory(< dir > < variable >)dir表示要搜索的目录variable表示将从dir目录下搜索到的源文件列表存储到该变量中
② file
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)-
GLOB:将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中 -
递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中
file(GLOB SRC_LISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) file(GLOB HEAD_LISTS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
注意:CMAKE_CURRENT_SOURCE_DIR 宏表示当前访问的 CMakeLists.txt 文件所在的路径!
⭐ 包含头文件路径 include_directories()
在编译项目源文件的时候,很多时候都需要将源文件对应的头文件路径指定出来,这样才能保证在编译过程中编译器能够找到这些头文件,并顺利通过编译。
命令如下所示:
include_directories(headpath)⭐ 生成静态库/动态库 add_library() && 指定库的输出路径 LIBRARY_OUTPUT_PATH
在 cmake 中,如果要制作静态库,需要使用的命令如下:
add_library(库名称 STATIC 源文件1 [源文件2] ...) 在 Linux 系统中,静态库名字分为三部分:lib + 库名称 + .a,所以库名称只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充!
在 cmake 中,如果要制作动态库,需要使用的命令如下:
add_library(库名称 SHARED 源文件1 [源文件2] ...) 在 Linux 系统中,动态库名字分为三部分:lib + 库名称 + .so,所以库名称也只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充!
然后我们可以使用 LIBRARY_OUTPUT_PATH 宏来指定库的输出路径。(一般存放在项目目录中的 lib 目录中)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)⭐ 链接静态库/动态库 target_link_libraries() && link_directories()
在编写程序的过程中,可能会用到一些系统提供的动态库或者自己制作出的动态库或者静态库文件,cmake 中也为我们提供了相关的加载静态库/动态库的命令。
target_link_libraries() 不仅可以用来链接动态库,也可以用来链接静态库。它是 CMake 中推荐的链接库的方式,适用于所有类型的库,包括静态库和动态库。
- 静态库(
.a、.lib)在生成可执行程序的链接阶段【会】被打包到可执行程序中,所以可执行程序启动,静态库就被加载到内存中了。- 动态库(
.so、.dll)在生成可执行程序的链接阶段【不会】被打包到可执行程序中,当可执行程序被启动并且调用了动态库中的函数的时候,动态库才会被加载到内存。
命令如下所示:
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...
)- target:指定要加载动态库文件的名字
- 可以是一个源文件
- 可以是一个动态库文件
- 可以是一个可执行文件
- PRIVATE | PUBLIC | INTERFACE:动态库的访问权限,默认为
PUBLIC- 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的
PUBLIC即可 - 动态库的链接具有传递性,如果动态库
A链接了动态库B、C;动态库D链接了动态库A;此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
- 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的
PRIVATE | PUBLIC | INTERFACE 的区别:
PUBLIC:在public后面的库会被链接到前面的target中,并且里面的符号也会被导出,提供给第三方使用PRIVATE:在private后面的库仅被链接到前面的target中,并且终结掉,第三方不能感知你调了啥库INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号
有些时候,当我们去链接第三方的动态库的时候, 如果不指定链接路径,会报错找不到动态库。此时,我们在生成可执行程序之前,通过命令指定出要链接的动态库的位置:
link_directories(path) 通过 link_directories(path) 指定了动态库的路径之后,在执行生成的可执行程序的时候,就不会出现找不到动态库的问题了。
注意事项:在
cmake中【链接库】的操作应写到【生成可执行文件】的操作之后:# 定义可执行文件 add_executable(main main.cpp) # 链接静态库和动态库(库名称当然可以不加后缀和前缀!) target_link_libraries(main /path/to/libmath.a /path/to/libutils.so) 当然如果你希望在链接时省略库的前缀和后缀,那么可以使用相对路径或不带扩展名的库名称,同时需要将库文件所在的路径通过
link_directories()或设置CMAKE_LIBRARY_PATH提供给CMake,以便它可以找到库:# 定义可执行文件 add_executable(main main.cpp) # 提供库所在的路径 link_directories(/path/to) # 链接静态库和动态库,去掉前缀和后缀 target_link_libraries(main math utils)
⭐ 将构建后的目标安装到系统的指定位置 install()
在 CMake 中,install() 指令用于定义如何将构建后的目标(例如可执行文件、库文件、头文件等)安装到系统的指定位置。它是 CMake 提供的一个非常重要的命令,广泛用于构建安装包或部署项目。
install(TARGETS <target>
[RUNTIME DESTINATION <path>] # 指定【可执行文件】的安装路径
[LIBRARY DESTINATION <path>] # 指定【动态库】的安装路径
[ARCHIVE DESTINATION <path>] # 指定【静态库】的安装路径
[PUBLIC_HEADER DESTINATION <path>] # 指定【公共头文件】的安装路径
[PRIVATE_HEADER DESTINATION <path>]
[INCLUDES DESTINATION <path>] # 指定【头文件】的安装路径
[COMPONENT <component>] # 定义该安装部分属于哪个组件
)TARGETS <target>:指定要安装的目标,可以是可执行文件、静态库、动态库等。- 后面的选项(
RUNTIME、LIBRARY、ARCHIVE、HEADER等)用于指定目标文件的安装类型和目标路径。
注意事项:DESTINATION 定义了安装的路径,如果路径以 / 开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 其实就无效了。如果你希望使用 CMAKE_INSTALL_PREFIX 来 定义安装路径,就要写成相对路径,即不要以 / 开头,那么安装后的路径就是 ${CMAKE_INSTALL_PREFIX}/...。
举个例子:
# 安装多个目标(依次处理)
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic)- 可执行二进制
myrun安装到${CMAKE_INSTALL_PREFIX}/bin目录 - 动态库
libmylib安装到${CMAKE_INSTALL_PREFIX}/lib目录 - 静态库
libmystaticlib安装到${CMAKE_INSTALL_PREFIX}/libstatic目录
可以使用
-D选项指定CMAKE_INSTALL_PREFIX参数, 如cmake .. -DCMAKE_INSTALL_PREFIX =/usr如果没有定义
CMAKE_INSTALL_PREFIX会安装到什么地⽅? 尝试一下会发现CMAKE_INSTALL_PREFIX的默认定义 是/usr/local
其它常见选项
-
PERMISSIONS:指定安装文件的权限。这在需要特别指定文件权限时非常有用,例如,在
Linux系统中,你可能希望设置文件为可执行或者只读。install(TARGETS myapp RUNTIME DESTINATION /usr/local/bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ) 这个命令会将
myapp安装到/usr/local/bin,并设置文件权限,使文件对文件拥有者可读、可写、可执行,对组可读,对其他用户可读。 -
EXPORT:将目标的
CMake配置导出为一个CMake配置文件(通常是.cmake文件),这样其他项目可以使用find_package()找到并链接该库。这对于库开发者来说很重要,因为它使得库易于被其他项目使用。install(TARGETS mylib EXPORT mylib-export LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/static) install(EXPORT mylib-export DESTINATION share/cmake/mylib) 这会将
mylib的CMake配置文件导出到share/cmake/mylib目录,其他项目可以通过find_package(mylib)使用该库。 -
FILES 和 DIRECTORY:
FILES:用于安装单个文件或多个文件DIRECTORY:用于安装整个目录及其内容
# 安装单个文件 install(FILES myconfig.h DESTINATION /usr/local/include) # 安装整个目录(例如头文件目录) install(DIRECTORY include/ DESTINATION /usr/local/include) 在
FILES选项中,通常用于安装配置文件、许可证文件等,而DIRECTORY则适用于安装多个文件或整个文件夹内容。 -
PROGRAMS: 用于安装可执行文件并保持其可执行属性,通常用于安装
shell脚本或其他可执行程序。c 复制编辑 install(PROGRAMS myscript.sh DESTINATION /usr/local/bin)这会将
myscript.sh安装到/usr/local/bin,并确保它保持可执行。 -
OPTIONAL:表示如果指定的文件或目标不存在,
CMake不会报错。通常用于可选的依赖或文件。install(FILES optional_config.h DESTINATION /usr/local/include OPTIONAL) 这会在文件
optional_config.h存在时进行安装,但如果文件不存在,CMake会忽略该操作并继续。 -
PATTERN:用于在安装目录时指定文件模式。可以通过它选择安装某些类型的文件(例如所有
.h文件)。install(DIRECTORY mydir/ DESTINATION /usr/local/include PATTERN "*.h" DESTINATION /usr/local/include) 这会将
mydir/目录下所有.h文件安装到/usr/local/include。
⭐ 提示或错误打印 message()
CMake 中可以使用命令打印消息,该命令的名字为 message。
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "信息内容")第一个参数通常不设置,表示重要消息。
- STATUS :非重要消息
- WARNING:
CMake警告,会继续执行 - AUTHOR_WARNING:
CMake警告 (dev),会继续执行 - SEND_ERROR:
CMake错误,继续执行,但是会跳过生成的步骤 - FATAL_ERROR:
CMake错误,终止所有处理过程
举个例子:
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")⭐ 宏定义 add_definitions()
在 CMake 中我们也可以使用 add_definitions 来定义宏:
cmake_minimum_required(VERSION 3.0)
project(TEST)
# 自定义 DEBUG 宏
add_definitions(-DDEBUG)
add_executable(app ./example.cpp) 下面的列表中为大家整理了一些 CMake 中已经给我们定义好的宏:
| 宏 | 功能 |
|---|---|
| PROJECT_SOURCE_DIR | 使用 cmake 命令后紧跟的目录,一般是工程的根目录 |
| PROJECT_BINARY_DIR | 执行 cmake 命令的目录 |
| CMAKE_CURRENT_SOURCE_DIR | 当前处理的 CMakeLists.txt 所在的路径 |
| CMAKE_CURRENT_BINARY_DIR | cmake 执行命令时所在的工作路径 |
| EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 |
| LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 |
| PROJECT_NAME | 返回通过 PROJECT 指令定义的项目名称 |
| CMAKE_BINARY_DIR | 项目实际构建路径,假设在 build 目录进行的构建,那么得到的就是这个目录的路径 |
| CMAKE_INSTALL_PREFIX | 默认安装路径 |
⭐ 判断文件或目录是否存在 if(EXISTS <path>)
在 CMake 中,你可以使用 if(EXISTS <path>) 来判断文件或目录是否存在。该语句会返回 TRUE 或 FALSE,根据指定的路径是否存在来决定。
EXISTS <path>:检查路径是否存在,路径可以是文件或目录。IS_FILE <path>:检查指定路径是否是一个文件。IS_DIRECTORY <path>:检查指定路径是否是一个目录。
判断文件是否存在:
set(file_path "/path/to/file.txt")
if(EXISTS ${file_path})
message(STATUS "File exists: ${file_path}")
else()
message(STATUS "File does not exist: ${file_path}")
endif() 你也可以使用 EXISTS 来判断目录是否存在:
set(dir_path "/path/to/directory")
if(EXISTS ${dir_path})
message(STATUS "Directory exists: ${dir_path}")
else()
message(STATUS "Directory does not exist: ${dir_path}")
endif() 如果你仅仅想判断路径是否是文件,可以使用 IS_FILE:
set(file_path "/path/to/file.txt")
if(IS_FILE ${file_path})
message(STATUS "It is a file: ${file_path}")
else()
message(STATUS "It is not a file: ${file_path}")
endif() 如果你想检查路径是否是目录,可以使用 IS_DIRECTORY:
set(dir_path "/path/to/directory")
if(IS_DIRECTORY ${dir_path})
message(STATUS "It is a directory: ${dir_path}")
else()
message(STATUS "It is not a directory: ${dir_path}")
endif()⭐ 循环遍历 foreach()
在 CMake 中,循环遍历通常是使用 foreach() 命令来实现的。foreach() 可以遍历列表中的每个元素,类似于其他编程语言中的 for 循环。你也可以使用其他一些命令来实现特定的遍历操作。
foreach(<variable> IN ITEMS <item1> <item2> ... <itemN>)
# 在每次迭代时执行的操作
endforeach() 下面举几个例子:
① 遍历一个列表
set(my_list "apple" "banana" "cherry")
foreach(fruit IN ITEMS ${my_list})
message(STATUS "Fruit: ${fruit}")
endforeach()
# 输出:
Fruit: apple
Fruit: banana
Fruit: cherry ② 遍历某目录中的文件
file(GLOB files_in_dir "/path/to/directory/*.cpp") # 获取文件放到files_in_dir中
foreach(file IN ITEMS ${files_in_dir})
message(STATUS "Found file: ${file}")
endforeach() ③ 遍历数字范围:使用 Range
foreach(i RANGE 1 5)
message(STATUS "Number: ${i}")
endforeach()
# 输出:
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5 ④ 遍历多个列表:使用 IN LISTS
set(list1 "apple" "banana" "cherry")
set(list2 "dog" "cat" "mouse")
foreach(a IN LISTS list1)
foreach(b IN LISTS list2)
message(STATUS "${a} and ${b}")
endforeach()
endforeach()
# 输出:
apple and dog
apple and cat
apple and mouse
banana and dog
banana and cat
banana and mouse
cherry and dog
cherry and cat
cherry and mouse ⑤ 使用 continue 和 break 控制循环:
set(my_list "apple" "banana" "cherry")
foreach(fruit IN ITEMS ${my_list})
if(${fruit} STREQUAL "banana")
continue() # 跳过 "banana"
endif()
message(STATUS "Fruit: ${fruit}")
endforeach()
# 输出:
Fruit: apple
Fruit: cherry⭐ 执行外部指令 add_custom_command()
add_custom_command() 是 CMake 中用于定义自定义构建步骤的命令,通常用于执行非标准的命令或脚本,或者创建自定义的目标(例如生成文件、执行外部命令等)。它的作用是将自定义的构建命令绑定到构建系统中,通常在构建过程中执行,而不会直接在 CMake 配置阶段执行。
add_custom_command(
PRE_BUILD
COMMAND <command> [args...]
DEPENDS <depend1> <depend2> ...
OUTPUT <output1> <output2> ...
COMMENT <comment>
)- PRE_BUILD:表示此自定义命令将在目标构建之前执行。它确保该命令是在编译任何源文件或生成目标文件之前运行的。
- COMMAND:必选项!要执行的外部命令或程序
- ARGS:可选项,上面命令的参数
- DEPENDS:可选项,指定命令的依赖项。如果这些依赖项发生变化,
CMake会重新执行该命令。 - OUTPUT:可选项,指定自定义命令的输出文件。
CMake通过检查这些文件来决定是否重新执行命令。 - COMMENT:可选项,提供一个注释信息,当命令执行时会打印到构建日志中。
比如我们前面 odb 使用的过程需要用指令来生成 pb 文件,此时就需要提前在 cmake 中指定,如下所示:
set(odb_path ${CMAKE_CURRENT_SOURCE_DIR}/entity)
set(odb_files student.hxx)
# 初始化 odb_hxx、odb_cxx 和 odb_srcs 为空字符串,后续会用到
set(odb_hxx "")
set(odb_cxx "")
set(odb_srcs "")
# 遍历 odb_files 列表中的每个文件(这里是 student.hxx)
foreach(odb_file ${odb_files})
string(REPLACE ".hxx" "-odb.hxx" odb_hxx ${odb_file})
string(REPLACE ".hxx" "-odb.cxx" odb_cxx ${odb_file})
# 判断生成的 .odb.cxx 文件是否已经存在
if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}${odb_cxx})
add_custom_command(
PRE_BUILD
COMMAND odb
ARGS -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time ${odb_path}/${odb_file}
DEPENDS ${odb_path}/${odb_file}
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
COMMENT "生成ODB框架代码文件:" ${CMAKE_CURRENT_BINARY_DIR}/${odb_cxx}
)
endif()
endforeach() ⭐ 嵌套 CMake add_subdirectory()
如果项目很大,或者项目中有很多的源码目录,在通过 CMake 管理项目的时候如果只使用一个 CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个 CMakeLists.txt 文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。
嵌套的 CMake 是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:
- 根节点
CMakeLists.txt中的变量全局有效 - 父节点
CMakeLists.txt中的变量可以在子节点中使用 - 子节点
CMakeLists.txt中的变量只能在当前节点中使用
我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])- source_dir:指定了
CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录 - binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
- EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的
ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。
通过这种方式 CMakeLists.txt 文件之间的父子关系就被构建出来了!