CMake之(1)基础使用
Author: Once Day Date: 2024年6月29日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: Linux实践记录_Once-Day的博客-CSDN博客
参考文章:
- CMake - Upgrade Your Software Build System
- CMake Tutorial — CMake 3.30.0-rc4 Documentation
- Mastering CMake — Mastering CMake
- Packaging With CPack — Mastering CMake
- CMake Documentation and Community
- cmake-language(7) — CMake 3.30.0-rc4 Documentation
- cmake-variables(7) — CMake 3.30.0-rc4 Documentation
- cmake-commands(7) — CMake 3.30.0-rc4 Documentation
- cmake-properties(7) — CMake 3.30.0-rc4 Documentation
- cmake-modules(7) — CMake 3.30.0-rc4 Documentation
文章目录
- CMake之(1)基础使用
- 1. 概述
- 1.1 CMake介绍
- 1.2 CMake简单使用示例
- 1.3 CMake辅助工具
- 1.4 CMake与Google Test
- 1.5 CMake特性
- 2. CMake使用
- 2.1 搭建CMake环境
- 2.2 常见cmake参数介绍
- 2.3 常见编译器参数
- 2.4 CMakeLists文件编写
- 2.5 CMake cache配置缓存
- 2.6 CMake关键概念
- 2.7 CMake策略
- 2.8 CMake模块
- 2.9 CMake安装文件(install)
1. 概述
1.1 CMake介绍
CMake是一个开源的跨平台自动化构建工具,它主要用于管理大型软件项目的构建、测试和打包过程。CMake通过编写简单的配置文件来描述项目的构建过程,然后根据不同平台的特性生成对应的本地化构建文件,如Unix下的Makefile或Windows下的Visual Studio项目文件。
使用CMake可以让开发者从复杂的构建细节中解放出来,专注于代码的编写和维护。它的主要优点包括:
-
跨平台性:CMake支持多种操作系统和编译器,使得项目可以轻松地在不同平台上构建。
-
简单易用:CMake使用简洁的语法编写配置文件,学习曲线相对平缓。
-
模块化设计:CMake提供了大量的内置模块和函数,可以方便地引入第三方库和管理复杂的依赖关系。
-
灵活性高:CMake支持各种自定义的构建选项和条件编译,可以根据需要调整构建过程。
CMake适用于各种规模的C/C++项目,尤其是那些需要跨平台构建、集成第三方库或进行自定义构建的项目。
但是,CMake也有一些不足之处:
-
语法和概念需要额外学习,对于简单的项目可能显得有些重。
-
生成的构建文件可读性较差,出错时调试困难。
-
缺乏对其他语言(如Java、Python等)的原生支持。
与CMake类似的构建工具还有GNU Make、Autotools、SCons、Meson等。其中,GNU Make是Unix下的传统构建工具,使用Makefile来描述构建过程,但缺乏跨平台支持。Autotools在跨平台方面做了改进,但配置过程较为复杂。SCons和Meson都使用Python作为配置语言,提供了更现代化的构建方式,但生态圈不如CMake成熟。
总的来说,CMake凭借其良好的跨平台性、易用性和灵活性,已经成为C/C++项目构建的首选工具之一。
1.2 CMake简单使用示例
下面是一个简单的CMake使用示例。
(1) 假设我们有一个名为hello
的项目,其目录结构如下:
hello/ # 项目根目录
├── CMakeLists.txt # CMake配置清单文件
├── include/ # 项目头文件目录
│ └── hello.h
└── src/ # 项目源代码目录├── hello.cpp└── main.cpp
其中,CMakeLists.txt
是CMake的配置文件,include/
目录下是头文件,src/
目录下是源文件。
(2) CMakeLists.txt
的内容填写如下:
# 最小CMake版本号限制
cmake_minimum_required(VERSION 3.10)
# 项目名称
project(hello)
# C++的版本号
set(CMAKE_CXX_STANDARD 11)
# 头文件搜索目录
include_directories(include)
# 可执行文件和其源代码文件
add_executable(hello src/main.cpp src/hello.cpp)
这个配置文件的作用是:
- 指定CMake的最小版本要求为3.10。
- 指定项目名称为
hello
。 - 设置C++标准为C++11。
- 将
include/
目录添加到头文件搜索路径中。 - 添加一个名为
hello
的可执行目标,其源文件为src/main.cpp
和src/hello.cpp
。
(3) 接下来,我们在项目根目录下创建一个build/
目录,并进入该目录:
# 在单独的子目录下编译, 避免编译中间文件干扰输出
mkdir build
cd build
(4) 然后,执行以下命令生成构建文件:
cmake ..
这个命令会根据上一级目录中的CMakeLists.txt
文件生成相应的构建文件(如Makefile)。
(5) 最后,执行构建命令:
cmake --build .
这个命令会调用相应的构建工具(如Make)来编译项目,生成可执行文件hello
。
(6) 执行可执行文件:
./hello
这样,我们就完成了一个简单的CMake项目的构建过程。对于更复杂的项目,我们可以在CMakeLists.txt
中添加更多的配置选项,如条件编译、子目录管理、第三方库链接等,以满足项目的特定需求。
1.3 CMake辅助工具
除了基本的构建和测试功能外,CMake还提供了一些辅助工具和功能,以进一步简化和自动化项目管理过程。下面介绍几个常见的CMake辅助工具和功能:
(1) CMake Cache,一个文本文件,用于存储CMake配置过程中的变量值和选项。
- 当首次运行CMake时,它会在构建目录下生成一个名为
CMakeCache.txt
的文件,记录了所有的缓存变量及其值。 - 缓存变量可以通过
set()
命令设置,并使用CACHE
关键字指定变量类型和描述信息。 - 使用缓存变量可以避免每次运行CMake时重复设置相同的变量值,提高配置效率。
- 可以使用
cmake -LAH
命令列出所有的缓存变量及其当前值。
(2) CPack,CMake内置的打包工具,用于生成各种格式的安装包,如ZIP、TGZ、RPM、DEB等。
- 通过在
CMakeLists.txt
中设置一些变量和指令,可以控制安装包的内容、版本、依赖等信息。 - 常用的CPack变量包括
CPACK_PACKAGE_NAME
、CPACK_PACKAGE_VERSION
、CPACK_PACKAGE_DESCRIPTION_SUMMARY
等。 - 可以使用
include(CPack)
命令启用CPack,并在构建完成后使用cpack
命令生成安装包。
(3) CTest,CMake内置的测试管理工具,用于自动化运行和管理项目的测试用例。
- 通过在
CMakeLists.txt
中使用add_test()
命令添加测试用例,并使用enable_testing()
命令启用测试功能。 - 可以使用
ctest
命令运行所有的测试用例,并生成测试报告。 - CTest支持多种测试属性和配置,如测试超时、资源锁定、依赖关系等。
- 可以与CDash等测试结果管理平台集成,实现测试结果的可视化和分析。
(4) ccmake和cmake-gui,CMake的交互式配置工具,提供了基于文本或图形界面的配置方式。
- 使用ccmake或cmake-gui可以方便地查看和修改CMake缓存变量,而无需直接编辑
CMakeCache.txt
文件。 - ccmake基于ncurses库,提供了基于文本的交互式界面。
- cmake-gui基于Qt库,提供了基于图形的交互式界面,支持多平台。
除此之外,还有一些其他的小特性:
- CMake支持条件编译和平台检测,可以根据不同的操作系统、编译器、架构等条件生成相应的构建文件。
- CMake提供了大量的模块和函数,用于查找和链接各种第三方库,如Boost、OpenCV、Qt等。
- CMake支持代码生成和自定义命令,可以在构建过程中自动生成代码文件或执行自定义的脚本。
- CMake支持多语言混合编译,可以在同一个项目中使用C、C++、Fortran、CUDA等不同的编程语言。
1.4 CMake与Google Test
下面以Google Test为例,演示如何在CMake中集成测试,在1.2节示例的基础上,为hello
项目添加单元测试。
(1) 首先,我们需要下载Google Test库,并将其解压到项目根目录下的googletest/
目录中,在项目根目录下的CMakeLists.txt
文件中添加以下内容。
cmake_minimum_required(VERSION 3.10)project(hello)set(CMAKE_CXX_STANDARD 11)include_directories(include)add_library(hello_lib src/hello.cpp)add_executable(hello src/main.cpp)
target_link_libraries(hello hello_lib)enable_testing()add_subdirectory(googletest)add_executable(hello_test test/hello_test.cpp)
target_link_libraries(hello_test hello_lib gtest_main)include(GoogleTest)
gtest_discover_tests(hello_test)
这个配置文件的主要变化有:
- 将
hello.cpp
编译为一个名为hello_lib
的库,而不是直接链接到可执行文件中。 - 启用测试功能,并添加
googletest/
子目录。 - 添加一个名为
hello_test
的测试可执行文件,其源文件为test/hello_test.cpp
,并链接hello_lib
库和gtest_main
库。 - 使用
GoogleTest
模块自动发现并运行测试。
(2) 接下来,在test/
目录下创建hello_test.cpp
文件,编写测试用例:
#include "gtest/gtest.h"
#include "hello.h"TEST(HelloTest, BasicAssertions) {EXPECT_EQ(hello(42), 42);
}
(3) 然后,按照与上一个示例相同的步骤,在build/
目录下执行以下命令:
# 生成cmake实例信息
cmake ..
# 开始以当前目录的cmake实例信息进行编译
cmake --build .
# 执行单元测试函数
ctest
ctest
命令会自动运行项目中的所有测试,并输出测试结果。
1.5 CMake特性
CMake是一个跨平台的自动化构建工具,它为多开发者协作和多目标平台的项目提供了许多优势,包括:
-
自动搜索软件构建所需的程序、库和头文件,包括考虑环境变量和Windows注册表设置。
-
支持在源码树外的目录树中构建,这是UNIX平台上常见的有用特性,CMake在Windows上也提供了这一特性。这允许开发者在不担心删除源文件的情况下删除整个构建目录。
-
能够为自动生成的文件(如Qt的moc()或SWIG包装器生成器)创建复杂的自定义命令。这些命令用于在构建过程中生成新的源文件,然后将其编译到软件中。
-
在配置时选择可选组件的能力。例如,VTK的几个库是可选的,CMake为用户提供了一种简单的方式来选择要构建的库。
-
能够从简单的文本文件自动生成工作区和项目。对于具有许多程序或测试用例的系统,这可能非常方便,每个程序或测试用例都需要单独的项目文件,通常使用IDE手动创建项目文件是一个繁琐的过程。
-
能够在静态和共享构建之间轻松切换。CMake知道如何在所有支持的平台上创建共享库和模块。处理复杂的特定于平台的链接器标志,并且在许多UNIX系统上支持共享库的内置运行时搜索路径等高级功能。
-
自动生成文件依赖关系并支持大多数平台上的并行构建。
在开发跨平台软件时,CMake还提供了许多额外的特性:
-
能够测试机器字节序和其他特定于硬件的特性。
-
一组适用于所有平台的构建配置文件。这避免了开发人员必须在项目内以多种不同格式维护相同信息的问题。
-
支持在所有支持它的平台上构建共享库。
-
能够使用系统相关信息(如数据文件的位置和其他信息)配置文件。CMake可以创建包含以#define宏形式存在的数据文件路径和其他信息的头文件。系统特定的标志也可以放在配置的头文件中。与编译器的命令行-D选项相比,这有优势,因为它允许其他构建系统使用CMake构建的库,而无需指定构建期间使用的完全相同的命令行选项。
2. CMake使用
2.1 搭建CMake环境
在Windows和Linux上搭建CMake环境的步骤有一些差异,但总体流程相似。
Windows:
-
下载CMake安装包,访问CMake官方网站(https://cmake.org/download/),选择适合的Windows版本的安装包(如cmake-3.20.0-windows-x86_64.msi)。
-
安装CMake,选择要添加到PATH环境变量的组件,建议选择"Add CMake to the system PATH for all users"。
-
验证安装,打开命令提示符(cmd.exe),输入
cmake --version
,如果显示CMake的版本信息,则说明安装成功。 -
配置编译器,CMake本身不编译代码,需要配合编译器使用,常用的编译器有Visual Studio、MinGW等,确保编译器已经安装,并且可执行文件所在目录已添加到PATH环境变量中。
Linux(以Ubuntu为例):
- 更新软件包列表,打开终端,输入
sudo apt update
,更新软件包列表。 - 安装CMake,在终端中输入
sudo apt install cmake
,安装CMake,等待安装完成。 - 验证安装,在终端中输入
cmake --version
,如果显示CMake的版本信息,则说明安装成功。 - Linux上常用的编译器有GCC和Clang,大多数Linux发行版默认安装了GCC,可以通过
gcc --version
命令验证。如果没有安装,可以使用sudo apt install build-essential
命令安装GCC。
需要注意的是,CMake生成的构建文件取决于所使用的编译器和平台。在Windows上,如果安装了Visual Studio,CMake默认生成Visual Studio项目文件。如果使用MinGW,则生成Makefile。在Linux上,CMake默认生成Makefile。
2.2 常见cmake参数介绍
参数 | 作用 | 示例 |
---|---|---|
-S \<path\> | 指定源代码目录的路径 | cmake -S . -B build |
-B \<path\> | 指定构建目录的路径 | cmake -S . -B build |
-G \<generator\> | 指定生成器 | cmake -G "Unix Makefiles" |
-D \<var\>=\<value\> | 设置CMake缓存变量的值 | cmake -DCMAKE_BUILD_TYPE=Release |
-U \<globbing_expr\> | 取消设置匹配表达式的CMake缓存变量 | cmake -U CMAKE_BUILD_TYPE |
-C \<initial-cache\> | 预加载脚本以填充CMake缓存 | cmake -C config.cmake |
-P \<script\> | 在脚本模式下运行CMake,不生成构建系统 | cmake -P script.cmake |
-T \<toolset\> | 指定构建时使用的工具集 | cmake -T clang |
-A \<platform\> | 指定构建时使用的平台名称 | cmake -A x64 |
--graphviz=\<file\> | 生成graphviz dot文件 | cmake --graphviz=project.dot |
--system-information \<file\> | 将系统信息转储到文件中 | cmake --system-information info.txt |
--debug-output | 启用调试输出 | cmake --debug-output |
--trace | 将每个执行的cmake命令的调用跟踪到stderr | cmake --trace |
--trace-expand | 类似–trace,但也展开变量引用 | cmake --trace-expand |
--trace-source=\<file\> | 将每个执行的cmake命令的调用跟踪到指定文件 | cmake --trace-source=trace.txt |
--warn-uninitialized | 在引用未初始化的变量时发出警告 | cmake --warn-uninitialized |
--no-warn-unused-cli | 不警告未使用的命令行参数 | cmake --no-warn-unused-cli |
--check-system-vars | 在引用未定义的环境变量时发出警告 | cmake --check-system-vars |
--help | 显示帮助信息 | cmake --help |
--version | 显示CMake版本信息 | cmake --version |
2.3 常见编译器参数
CMake配置项目时提供了三种方法来指定编译器:通过生成器、环境变量或缓存条目。
-
通过生成器指定编译器,某些生成器与特定的编译器绑定,例如Visual Studio 19生成器总是使用Microsoft Visual Studio 19编译器。对于基于Makefile的生成器,CMake将尝试一系列常用的编译器,直到找到一个可用的编译器。
-
通过环境变量指定编译器,可以在运行CMake之前设置环境变量来指定编译器,
CC
环境变量指定C编译器,CXX
环境变量指定C++编译器。export CC=/usr/bin/clang export CXX=/usr/bin/clang++
-
通过缓存条目指定编译器,可以在CMake命令行中使用
-D
选项直接指定编译器cmake -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ ..
注意,如果在运行CMake后需要更改编译器,应该重新创建一个空的构建目录并重新运行CMake。
通过环境变量设置编译器和链接器标志:
- 可以通过设置环境变量来初始化编译器和链接器的标志。
CFLAGS
环境变量初始化CMAKE_C_FLAGS
缓存值,用于设置C编译器标志。CXXFLAGS
环境变量初始化CMAKE_CXX_FLAGS
缓存值,用于设置C++编译器标志。LDFLAGS
环境变量初始化链接器标志的缓存值。
export CFLAGS="-Wall -Werror"
export CXXFLAGS="-std=c++11 -O2"
export LDFLAGS="-L/path/to/lib -lmylib"
通过环境变量或缓存条目指定编译器和标志的方法适用于所有生成器,但在跨平台开发时,使用环境变量或缓存条目指定编译器和标志可以提高可移植性。
2.4 CMakeLists文件编写
CMake语言由注释、命令和变量组成:
-
注释以
#
开头,直到行尾,如下所示:set(Foo a) # 1 unquoted arg -> value is "a"
-
变量采用
${VAR}
语法引用,变量名区分大小写,可包含字母、数字和下划线,多个值用分号隔开存储为字符串。变量作用域为当前目录及其子目录。使用set()
命令定义变量,unset()
命令取消变量定义。此外,还可以直接访问环境变量$ENV{VAR}
和Windows注册表项。set(Foo "") # 1 quoted arg -> value is "" set(Foo a) # 1 unquoted arg -> value is "a" set(Foo "a b c") # 1 quoted arg -> value is "a b c" set(Foo a b c) # 3 unquoted args -> value is "a;b;c"
变量引用时,如果未定义,则返回空字符串。如果引用时存在双引号,则当作字符串展开,否则作为值展开(即展开为多个值)。
set(Foo a b c) # 3 unquoted args -> value is "a;b;c" command(${Foo}) # unquoted arg replaced by a;b;c# and expands to three arguments command("${Foo}") # quoted arg value is "a;b;c" set(Foo "") # 1 quoted arg -> value is empty string command(${Foo}) # unquoted arg replaced by empty string# and expands to zero arguments command("${Foo}") # quoted arg value is empty string
变量在函数或者子目录修改时,一个新的变量会产生并等于改变后的值,这意味着不会影响到外部原变量的值,如下:
function(foo)message(${test}) # test is 1 hereset(test 2)message(${test}) # test is 2 here, but only in this scope endfunction()set(test 1) foo() message(${test}) # test will still be 1 here
如果想在子目录或者函数里改变外部值(或者称返回值),可以如下操作:
function(foo)message(${test}) # test is 1 hereset(test 2 PARENT_SCOPE)message(${test}) # test still 1 in this scope endfunction()set(test 1) foo() message(${test}) # test will now be 2 here
-
命令由命令名、左括号、参数列表和右括号组成,参数以空格分隔,可用引号括起,命令名字不区分大小写。
除了分割变量之外,其余空白字符全部被忽略掉,对于存在双引号的变量,总是被当成一个变量处理。
command("") # 1 quoted argument command("a b c") # 1 quoted argument command("a;b;c") # 1 quoted argument command("a" "b" "c") # 3 quoted arguments command(a b c) # 3 unquoted arguments command(a;b;c) # 1 unquoted argument expands to 3
流程控制命令分为三类:
-
条件语句,例如if()、elseif()、else()和endif(),根据条件执行不同的命令。
if(MSVC80)# do something here elseif(MSVC90)# do something else elseif(APPLE)# do something else endif()
-
循环语句,例如foreach()和while(),用于重复执行一组命令,break()用于提前跳出循环。
foreach(tfileTestAnisotropicDiffusion2DTestButterworthLowPassTestButterworthHighPassTestCityBlockDistanceTestConvolve)add_test(${tfile}-image ${VTK_EXECUTABLE}${VTK_SOURCE_DIR}/Tests/rtImageTest.tcl${VTK_SOURCE_DIR}/Tests/${tfile}.tcl-D ${VTK_DATA_ROOT}-V Baseline/Imaging/${tfile}.png-A ${VTK_SOURCE_DIR}/Wrapping/Tcl) endforeach()
foreach第一个值tfile是循环变量,后续的Test*值是循环集合,逐一赋值给tfile来遍历执行。
##################################################### # run paraview and ctest test dashboards for 6 hours # while(${CTEST_ELAPSED_TIME} LESS 36000)set(START_TIME ${CTEST_ELAPSED_TIME})ctest_run_script("dash1_ParaView_vs71continuous.cmake")ctest_run_script("dash1_cmake_vs71continuous.cmake") endwhile()
-
过程定义语句,function()定义函数,可以接受参数并设置PARENT_SCOPE变量将结果返回给调用方。macro()定义宏,类似函数但不引入新的变量作用域,参数也不会被当作变量对待。return()从函数、目录或文件中返回。
function(DetermineTime _time)# pass the result up to whatever invoked thisset(${_time} "1:23:45" PARENT_SCOPE) endfunction()# now use the function we just defined DetermineTime(current_time)if(DEFINED current_time)message(STATUS "The time is now: ${current_time}") endif()# define a simple macro macro(assert TEST COMMENT)if(NOT ${TEST})message("Assertion failed: ${COMMENT}")endif() endmacro()# use the macro find_library(FOO_LIB foo /usr/local/lib) assert(${FOO_LIB} "Unable to find library foo")
此外,一些命令支持使用正则表达式作为参数,CMake使用Texas Instruments的开源正则表达式类进行解析。
2.5 CMake cache配置缓存
CMake Cache是CMake构建系统的一个重要概念,可以看作是一个配置文件。
-
存储用户的选择和设置,避免每次运行CMake时重复输入信息。第一次在一个项目上运行CMake时,会在构建树的顶层目录下生成CMakeCache.txt文件,后续运行时会读取这个文件。
例如,option()命令可以创建一个布尔型缓存变量:
option(USE_JPEG "Do you want to use the jpeg library")
这会在CMakeCache.txt中创建一个USE_JPEG变量,用户可以在CMake GUI中设置它的值,后续运行时会保留用户的设置。
-
存储CMake自己确定的一些系统相关的变量值,在多次运行之间复用,提高效率。这些变量通常需要编译和运行一些程序来确定,得到后就可以存入缓存,避免重复计算。这些变量对用户不可见,如果系统环境发生了重大变化(如更换操作系统或编译器),需要删除缓存文件。
-
处理复杂项目中选项之间的依赖关系。有的项目中,设置某个选项后,下次运行CMake时会出现新的相关选项。CMake通过缓存机制,让用户逐步设置所有相关选项,直到不再出现新选项为止。
可以通过set()命令将变量存入缓存:
set(USE_JPEG ON CACHE BOOL "include jpeg support?")
CACHE选项指定将变量存入缓存,还可以指定变量类型和说明字符串。类型会影响CMake GUI如何显示和设置变量,但在缓存文件中总是以字符串形式存储。
一般不建议直接编辑CMakeCache.txt,因为其语法可能发生变化,而且包含了很多绝对路径,不适合在不同构建目录间移动。
一旦变量进入缓存,就不能在CMakeLists中直接修改其"缓存"值,否则会覆盖用户的设置。如果一定要修改,需要使用set()的FORCE选项,没有进入缓存的变量仍然可以在当前作用域内被修改。
可以用mark_as_advanced()
将缓存条目标记为高级,默认情况下在CMake GUI中不显示,避免干扰普通用户。
有时需要在没有GUI的情况下初始化缓存,如在夜间构建或批量生成构建目录时,可使用下述方法:
# 用-D选项传递键值对
cmake -DBUILD_TESTING:BOOL=ON ...
# 用`-C`选项指定一个初始化文件,其中可以包含set()命令来设置缓存变量
set(VTK_USE_HYBRID ON CACHE BOOL "doc string")
# 用FORCE强制设置
set(VTK_USE_HYBRID ON CACHE BOOL "doc" FORCE)
# 用INTERNAL类型在设置的同时隐藏选项
set(VTK_USE_HYBRID ON CACHE INTERNAL "doc")
2.6 CMake关键概念
参考文档: [Key Concepts — Mastering CMake](https://cmake.org/cmake/help/book/mastering-cmake/chapter/Key Concepts.html)
CMake具有一些关键概念,包括target、property、source file、directory等。
Target代表了CMake构建的可执行文件、库和工具。
-
add_library
、add_executable
等命令都会创建target。 -
Target有name和type等属性。
-
库类型可以是STATIC、SHARED或MODULE。
add_library(foo STATIC foo1.c foo2.c)
-
可执行文件入口点可以是main或WinMain。
-
可以用
set_target_properties
和get_target_property
命令设置和读取target的属性,如LINK_FLAGS等。 -
target_link_libraries
命令可以指定target依赖的库,CMake会自动传递这些库的使用要求(如包含目录等)。add_library(foo foo.cxx) target_link_libraries(foo bar)add_executable(foobar foobar.cxx) target_link_libraries(foobar foo)
尽管只有foo被显示指定链接到foobar,但是bar同样也会被链接,因为foo和bar之间的关联可以传递到foobar和foo之间。
-
在Windows上,debug库要和debug库链接,release要和release链接,
target_link_libraries
支持debug和optimized关键字来实现这一点。add_executable(foo foo.c) target_link_libraries(foo debug libdebug optimized libopt)
-
Object Library是一种特殊的库,包含编译后的目标文件,但不会被链接成库文件。
add_library(A OBJECT a.cpp) add_library(B OBJECT b.cpp) add_library(Combined $<TARGET_OBJECTS:A> $<TARGET_OBJECTS:B>)
-
其他target可以用
$<TARGET_OBJECTS:name>
引用这些目标文件,主要用于对复杂工程的源代码进行分组管理。
Source File代表源文件,和target类似也有一些属性可以设置和读取,可以用set_source_files_properties
命令进行操作。
此外还有Directory、Test等对象,也可以通过set/get_xxx_properties
命令来操作其属性。
2.7 CMake策略
每当CMake引入了一个新特性或改变,而这些变更不能完全向后兼容旧版本时,就可能会出现问题。比如当有人尝试用新版CMake构建使用旧版CMakeLists文件的项目时,Policies机制可以帮助最终用户和开发者应对这些问题。
CMake中所有的policy都以CMPNNNN的形式命名,其中NNNN是一个整数值。Policy通常会同时支持旧行为(与早期CMake版本兼容)和新行为(被认为是正确的,推荐新项目使用)。每个policy都有文档详细说明变更的动机,以及新旧行为。
项目可以通过设置每个policy来请求使用旧行为或新行为。当CMake遇到可能受特定policy影响的用户代码时,它会检查项目是否设置了该policy。如果设置了(为OLD或NEW),则CMake遵循指定的行为。如果未设置,则使用旧行为,但会发出警告,告知项目作者设置该policy。
设置policy行为有几种方式:
-
最快的方式是将所有policy设置为与编写项目时使用的CMake发布版本相对应的版本。这可以通过cmake_policy命令的VERSION签名来实现,
cmake_minimum_required
命令会要求最低版本的CMake并调用cmake_policy。项目应该总是以下面的行开始:cmake_minimum_required(VERSION 3.20) project(MyProject)# ...使用 CMake 3.20 policy 的代码
-
单独设置每个 policy(增量式)。这有时对于想要逐步将项目转换为使用新行为,或抑制有关依赖旧行为的警告的项目作者很有帮助。可以使用cmake_policy命令的SET选项来明确请求特定policy的旧行为或新行为。
Policy设置使用堆栈进行范围控制,进入项目的新子目录(使用add_subdirectory)时会压入堆栈的新级别,离开时会弹出。因此,在项目的一个目录中设置policy不会影响父目录或同级目录,但会影响子目录。
当 CMake 新版本引入新的policy时,可能会为某些现有项目生成警告,这些警告表明可能需要更改项目以处理新的 policy。虽然项目的旧版本可以继续在警告下构建,但项目开发树应该更新以考虑新的policy。
2.8 CMake模块
CMake提供了一种代码复用的机制,称为modules(模块)。通过 modules,CMakeLists 文件可以复用其他地方的代码片段。这使得整个社区能够共享可复用的代码。在CMake中,这些代码片段被称为cmake-modules,通常可以在CMake安装目录的Modules子目录下找到。
模块的位置可以使用模块文件的完整路径指定,也可以让CMake自己去查找模块。CMake 会在CMAKE_MODULE_PATH指定的目录中查找模块;如果找不到,它会在Modules子目录中查找。这样项目就可以覆盖CMake提供的模块,并根据自己的需要对其进行定制。
CMake的模块主要可以分为两类:
-
Find Modules (查找模块),这些模块支持
find_package
命令,用于确定属于给定包的软件元素(如头文件或库)的位置。不要直接包含它们,而是使用 find_package 命令。每个模块都附有文档,描述它查找的包以及它提供结果的变量。 -
Utility Modules (工具模块),工具模块只是将一些 CMake 命令放到一个文件中,然后可以使用include命令将其包含到其他CMakeLists文件中。例如,以下命令将包含CMake的CheckTypeSize模块,然后使用它定义的宏:
include(CheckTypeSize) check_type_size(long SIZEOF_LONG)
这些模块测试系统以提供有关目标平台或编译器的信息,例如 float 的大小或对 ANSI C++ 流的支持。
许多这样的模块都有以Test或Check为前缀的名称,例如 TestBigEndian 和 CheckTypeSize。
其中一些模块会尝试编译代码以确定正确的结果。在这些情况下,源代码通常与模块同名,但带有 .c 或 .cxx 扩展名。工具模块还提供了使用CMake语言实现的有用的宏和函数,旨在用于特定的、常见的用例。
2.9 CMake安装文件(install)
CMake提供了功能强大的安装机制,通过在CMakeLists.txt中使用install命令,可以方便地指定如何安装项目生成的文件。使用 install命令主要涉及以下几个方面:
-
安装目标文件(Targets),使用
install(TARGETS ...)
可以安装项目构建生成的二进制文件,包括可执行文件和库文件。(1) 可执行文件(RUNTIME),通过add_executable生成,在Windows上后缀为.exe,Unix上没有后缀。
(2) 动态库(LIBRARY),Unix通过add_library并使用SHARED选项生成,后缀一般为.so,Windows通过add_library并使用MODULE选项生成,后缀为.dll。
(3) 静态库(ARCHIVE),通过add_library并使用STATIC选项生成,Windows 上为.lib,Unix上为.a。
(4) 导入库(ARCHIVE),在Windows上动态库同时会生成一个.lib的导入库文件。
install(TARGETS myExecutable DESTINATION bin) install(TARGETS myStaticLib DESTINATION lib/myproject) install(TARGETS myPlugin DESTINATION lib) # 根据平台的区别,不同的库安装目录不一样 install(TARGETS mySharedLibRUNTIME DESTINATION binLIBRARY DESTINATION libARCHIVE DESTINATION lib/myproject)
-
安装普通文件(Files) ,使用
install(FILES ...)
可安装项目中的任意文件,如头文件、配置文件、文档等。相对路径会相对于当前源码目录进行解析,可以通过 RENAME 参数重命名安装后的文件名。install(FILES my-api.h ${CMAKE_CURRENT_BINARY_DIR}/my-config.hDESTINATION include) install(FILES my-rc DESTINATION /etcPERMISSIONS OWNER_WRITE OWNER_READ) install(FILES version.h DESTINATION include RENAME my-version.h)
-
安装程序(Programs),使用
install(PROGRAMS ...)
可安装非目标的可执行程序,如脚本文件。与**install(FILES)**类似,不同之处是默认会添加可执行权限。install(PROGRAMS my-util.py DESTINATION bin)
-
安装目录(Directory),使用
install(DIRECTORY ...)
可以安装整个目录,会递归安装目录中的内容。如果目录路径以/
结尾,则只安装目录内容,不包含目录本身。可以使用PATTERN和REGEX参数过滤要安装的文件,USE_SOURCE_PERMISSIONS
参数会沿用源目录的权限设置。install(DIRECTORY data/icons DESTINATION share/myproject) install(DIRECTORY doc/html/ DESTINATION doc/myproject) install(DIRECTORY DESTINATION share/myproject/user) install(DIRECTORY data/scripts DESTINATION share/myprojectFILE_PERMISSIONSOWNER_READ OWNER_EXECUTE OWNER_WRITEGROUP_READ GROUP_EXECUTEWORLD_READ WORLD_EXECUTEDIRECTORY_PERMISSIONSOWNER_READ OWNER_EXECUTE OWNER_WRITEGROUP_READ GROUP_EXECUTE GROUP_WRITEWORLD_READ WORLD_EXECUTE) install(DIRECTORY data/icons DESTINATION share/myprojectPATTERN ".git" EXCLUDEPATTERN "*.txt" EXCLUDE) install(DIRECTORY data/icons DESTINATION share/myprojectREGEX "/.git$" EXCLUDEREGEX "/[^/]*.txt$" EXCLUDE)
-
安装时脚本(Script),使用
install(SCRIPT ...)
指定一个脚本,会在安装时执行。脚本不是在CMake处理CMakeLists.txt时执行,而是在安装时执行。脚本中可以使用一些预定义变量,如CMAKE_INSTALL_PREFIX
、CMAKE_INSTALL_CONFIG_NAME
等。install(SCRIPT message.cmake)
-
安装时代码(Code),使用
install(CODE ...)
可以直接在CMakeLists.txt中指定安装时要执行的CMake代码。install(CODE "MESSAGE(\"Installing My Project\")")
-
安装第三方依赖库(Fixup Bundle),项目经常会依赖一些第三方动态库,在安装可执行文件时,需要将这些依赖库拷贝并修复路径后一并安装。CMake 提供了BundleUtilities模块的fixup_bundle函数来自动处理这个过程。在 Mac 上,会将动态库拷贝到bundle内部并修复install_name。在 Windows 上,会将动态库拷贝到可执行文件所在目录。
在 CMake 的install命令中,可以使用一些参数来控制安装的细节,下面介绍几个常用的参数。
(1) DESTINATION用于指定文件安装的目标路径。
- 如果指定的是绝对路径,安装时会直接使用该路径。
- 如果指定的是相对路径,安装时会相对于CMAKE_INSTALL_PREFIX 进行解析。
CMAKE_INSTALL_PREFIX
变量可以由用户设置,CMake 也提供了默认值,Unix 上默认为/usr/local
,Windows 上默认为c:/Program Files/${PROJECT_NAME}
。
(2) PERMISSIONS,用于指定安装文件的权限。
-
默认情况下,不同类型(如TARGETS/FILES/PROGRAMS/DIRECTORY)的文件,CMake 会设置一些默认权限,通过该参数可以覆盖默认权限设置。
-
可供设置的权限有:OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, WORLD_READ, WORLD_WRITE, WORLD_EXECUTE, SETUID, SETGID。
(3) CONFIGURATIONS,用于指定安装规则适用的构建配置,如 Debug, Release 等。CMake 支持多配置构建,对于不同的配置,安装规则可能会有所不同。
- 对于Makefile生成器,配置名通过
CMAKE_BUILD_TYPE
变量设置。 - 对于VS, Xcode等IDE工程生成器,配置名在生成安装目标时选择。
- 只有当前安装配置名与此处设置的名字列表匹配,对应的安装规则才会生效。配置名的大小写不敏感。
(4) COMPONENT,用于指定安装规则适用的安装组件。项目可以将安装内容划分成若干个组件,从而可以选择性地进行安装。例如划分成 Runtime, Development, Documentation 等组件。
(5) OPTIONAL,用于指定安装文件缺失时不报错。默认情况下,如果 install 命令指定的文件不存在,会报错。设置该参数后,如果文件存在就安装,不存在也不会报错。
Once Day
也信美人终作土,不堪幽梦太匆匆......
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注,再加上一个小小的收藏⭐!
(。◕‿◕。)感谢您的阅读与支持~~~