文章目录
前言
之前学习CMake是通过官方教程文档进行学习的,在学习中发现,CMake官方的教程文档没有详细介绍每个命令和变量的用法,这也导致学一半时会感觉一头雾水。但它也并不无优点,官方教程文档采用了渐进式的实战教程方式,非常注重学习过程中的实践,这一点是很可贵的。为了加深命令和变量的理解,我查阅了很多api文档才搞明白。因此,为了跟大家分享更好的CMake学习文档,本教程诞生了。
本教程基于CMake官方最新版(3.30)的教程文档翻译而成,并加入了很多命令和变量的解释,从上往下跟着完成每个练习,你将学会如何使用CMake并运用到自己的项目中。
注意: 本教程需要有Makefile的前置知识,如果不懂Makefile,会对里面提到的依赖、目标等概念感到一头雾水,建议先掌握Makefile后再学习本教程。
前置准备
在进行正式的学习之旅之前,需要将练习的源码下载到本地,请使用git命令:
git clone https://e.coding.net/g-fgva2961/personal/cmake-tutorial.git
请确保你的CMake版本在 3.12 以上。
练习源码一共包含12个步骤,对应12个文件夹,还有一个已完成练习的文件夹Complete。
接下来正式进入教程,祝大家有一个不错的收获~
第一步:基础起点
本章节将介绍一些 CMake 的基本语法、命令和变量。我们将在介绍这些概念的过程中完成三个练习并创建一个简单的 CMake 项目。
每个练习都将从一些背景信息开始。然后,会提供一个目标和一些有用的资源列表。编辑的文件部分中的每个文件都在Step1
目录下,并包含一个或多个TODO
注释。每个TODO
代表一两行需要修改或添加的代码。TODO
按数字顺序完成,先完成TODO 1
,再完成TODO 2
,依此类推。Getting Started
部分将提供一些有用的提示并指导你完成练习。然后Build and Run
部分将逐步讲解如何构建和测试练习。最后,在每个练习结束时会讨论预期的解决方案。
还要注意,本教程的每一步都建立在前一步的基础上。例如,Step2
的起始代码是Step1
的完整解决方案。
练习1 - 构建一个基本项目
最基本的 CMake 项目是由单个源代码文件构建的可执行文件。对于这样的简单项目,只需要包含三个命令的CMakeLists.txt
文件。
注意: 虽然 CMake 支持大写、小写和混合大小写的命令,但建议使用小写命令,本教程将全程使用小写命令。
任何项目的顶级 CMakeLists.txt 文件必须首先使用cmake_minimum_required
命令指定最低 CMake 版本。这会建立策略设置,并确保以下 CMake 函数在兼容的 CMake 版本下运行。
要启动一个项目,我们使用project
命令设置项目名称。每个项目都需要调用此命令,并且应该在cmake_minimum_required
之后尽快调用。正如我们稍后将看到的,该命令还可以用来指定其他项目级别的信息,如语言或版本号。
最后,add_executable
命令告诉 CMake 使用指定的源代码文件创建可执行文件。
目标
了解如何创建一个简单的 CMake 项目。
有用的资源
add_executable
cmake_minimum_required
project
1. add_executable
定义:
add_executable
是一个 CMake 命令,用于定义一个可执行文件。
作用:
它指定要编译的源文件,并生成一个可执行目标。
用法:
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
<name>
:可执行文件的名称。[WIN32]
:对于 Windows 平台,生成一个窗口应用程序而不是控制台应用程序(可选)。[MACOSX_BUNDLE]
:对于 macOS 平台,生成一个 macOS bundle(可选)。[EXCLUDE_FROM_ALL]
:不将该目标包含在默认生成目标中(可选)。source1 source2 ... sourceN
:构成该可执行文件的源文件列表。
示例:
add_executable(myApp main.cpp utils.cpp)
2. cmake_minimum_required
定义:
cmake_minimum_required
是一个 CMake 命令,用于指定最低版本的 CMake,以确保CMakeLists.txt 文件能够正常解析和构建。
作用:
它通过指定最低版本号来保证 CMakeLists.txt 的兼容性,并且如果运行的 CMake 版本低于指定版本,则会发出错误。
用法:
cmake_minimum_required(VERSION <min_version>...<max_version> [FATAL_ERROR])
VERSION <min_version>
:指定最低的 CMake 版本号。<max_version>
:指定最高的 CMake 版本号(可选)。[FATAL_ERROR]
:在版本不符合时产生一个致命错误。
示例:
cmake_minimum_required(VERSION 3.10)
3. project
定义:
project
是一个 CMake 命令,用于定义项目名称、支持的语言以及相关的项目属性。
作用:
它可以设置项目名称,并可以指定该项目使用的编程语言,例如 C、C++、Fortran 等。
用法:
project(<project-name> [<language-name> ...])
<project-name>
:项目的名称。<language-name>
:项目中使用的语言,多个语言之间用空格分隔。
示例:
project(MyProject C CXX)
在这个例子中,项目名称为 MyProject,且使用 C 和 C++ 两种语言。
编辑的文件
CMakeLists.txt
入门
tutorial.cxx
的源代码位于Step1
目录中,可以用来计算一个数字的平方根。此文件在本步骤中无需编辑。
在同一目录中有一个CMakeLists.txt
文件,你需要完成它。从TODO 1
开始,依次完成TODO 3
。
构建和运行
完成TODO 1
到TODO 3
后,我们就可以构建和运行我们的项目了!首先,运行cmake
可执行文件或cmake-gui
来配置项目,然后使用你选择的构建工具来构建它。
例如,从命令行中,我们进入根目录并创建一个构建目录:
mkdir Step1_build
接下来,导航到该构建目录并运行cmake
来配置项目并生成本机构建系统:
cd Step1_build
cmake ../Step1
然后调用该构建系统来实际编译/链接项目:
cmake --build .
对于多配置生成器(例如 Visual Studio),首先导航到适当的子目录,例如:
cd Debug
最后,尝试使用新构建的Tutorial
:
Tutorial 4294967296
Tutorial 10
Tutorial
注意: 根据不同的 shell,正确的语法可能是Tutorial
、./Tutorial
或.\Tutorial
。为了简单起见,练习中将统一使用Tutorial
。
解决方案
如上所述,三行CMakeLists.txt
文件就足够我们开始运行。第一行使用cmake_minimum_required
设置 CMake 版本,如下所示:
cmake_minimum_required(VERSION 3.10)
接下来,使用project
命令设置项目名称:
project(Tutorial)
最后一步是调用add_executable
。我们如下调用它:
add_executable(Tutorial tutorial.cxx)
练习2 - 指定C++标准
CMake 有一些特殊变量,这些变量要么在后台创建,要么在项目代码设置时对 CMake 有意义。这些变量中许多以CMAKE_
开头。创建项目变量时应避免使用这种命名约定。两个这样的用户可设置变量是CMAKE_CXX_STANDARD
和CMAKE_CXX_STANDARD_REQUIRED
。这些变量可以一起使用来指定构建项目所需的 C++ 标准。
目标
添加需要 C++11 的功能。
有用的资源
CMAKE_CXX_STANDARD
CMAKE_CXX_STANDARD_REQUIRED
set
1. CMAKE_CXX_STANDARD
定义:
CMAKE_CXX_STANDARD
是一个 CMake 变量,用于指定项目使用的 C++ 标准。
作用:
它设置编译器使用的 C++ 标准版本,如 C++11、C++14、C++17 等。
用法:
直接在 CMakeLists.txt 文件中设置该变量的值。
示例:
set(CMAKE_CXX_STANDARD 17)
在这个例子中,指定项目使用 C++17 标准进行编译。
2. CMAKE_CXX_STANDARD_REQUIRED
定义:
CMAKE_CXX_STANDARD_REQUIRED
是一个 CMake 变量,用于指定是否严格要求使用 CMAKE_CXX_STANDARD
指定的 C++ 标准。
作用:
它决定是否严格要求编译器支持指定的 C++ 标准版本。如果设置为 ON
,则如果编译器不支持该标准,将导致配置失败。
用法:
直接在 CMakeLists.txt 文件中设置该变量的值。
示例:
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
在这个例子中,指定项目使用 C++17 标准进行编译,并且严格要求编译器支持该标准。
3. set
定义:
set
是一个 CMake 命令,用于设置变量的值。
作用:
它可以定义和修改 CMake 变量的值,支持字符串、列表等多种数据类型。
用法:
set(<variable> <value> [CACHE <type> <docstring> [FORCE]])
<variable>
:变量名称。<value>
:变量值。[CACHE <type> <docstring> [FORCE]]
:将变量存储在缓存中(可选)。<type>
:缓存变量的类型,如BOOL
、STRING
、PATH
等。<docstring>
:变量的描述信息。[FORCE]
:强制覆盖已有的缓存值。
示例:
# 设置普通变量
set(MY_VARIABLE "Hello, World!")
# 设置列表变量
set(SOURCES main.cpp util.cpp)
# 设置缓存变量
set(MY_OPTION ON CACHE BOOL "An example option" FORCE)
在这些例子中,MY_VARIABLE
被设置为字符串 “Hello, World!”,SOURCES
被设置为包含两个源文件的列表,MY_OPTION
被设置为布尔型缓存变量,并强制覆盖已有的值。
编辑的文件
CMakeLists.txt
tutorial.cxx
入门
继续编辑Step1
目录中的文件。从TODO 4
开始,完成TODO 6
。
首先,通过添加一个需要 C++11 的功能来编辑tutorial.cxx
。然后更新CMakeLists.txt
以要求 C++11。
构建和运行
让我们再次构建项目。由于我们已经为练习1创建了构建目录并运行了 CMake,我们可以跳到构建步骤:
cd Step1_build
cmake --build .
现在我们可以尝试使用新构建的Tutorial
,使用之前的相同命令:
Tutorial 4294967296
Tutorial 10
Tutorial
解决方案
我们首先通过在tutorial.cxx
中用std::stod
替换atof
来向项目添加一些 C++11 特性。如下所示:
// convert input to double
const double inputValue = std::stod(input);
完成TODO 5
,只需删除#include <cstdlib>
。
我们需要在 CMake 代码中明确指出它应使用正确的标志。启用特定 C++ 标准支持的一种方法是使用CMAKE_CXX_STANDARD
变量。对于本教程,在CMakeLists.txt
文件中将CMAKE_CXX_STANDARD
变量设置为11
,将CMAKE_CXX_STANDARD_REQUIRED
设置为True
。确保在调用add_executable
之前添加CMAKE_CXX_STANDARD
声明。
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
练习3 - 添加版本号和配置头文件
有时,将CMakeLists.txt
文件中定义的变量也可用于源代码中是很有用的。在这种情况下,我们希望打印项目版本。
实现这一目标的一种方法是使用配置头文件。我们创建一个包含一个或多个要替换变量的输入文件。这些变量具有类似于@VAR@
的特殊语法。然后,我们使用configure_file
命令将输入文件复制到给定的输出文件,并用CMakeLists.txt
文件中的当前变量值替换这些变量。
虽然我们可以直接在源代码中编辑版本,但使用此功能是首选,因为它创建了一个单一的数据源,避免了重复。
目标
定义并报告项目的版本号。
有用的资源
<PROJECT-NAME>_VERSION_MAJOR
<PROJECT-NAME>_VERSION_MINOR
configure_file
target_include_directories
1. <PROJECT-NAME>_VERSION_MAJOR
定义:
<PROJECT-NAME>_VERSION_MAJOR
是一个 CMake 变量,用于表示项目的主版本号。
作用:
这个变量通常用于版本控制和管理,以便在项目配置过程中可以引用和显示主版本号。
用法:
在定义项目时可以使用 project
命令来自动设置这些变量。
示例:
project(MyProject VERSION 1.2)
在这个例子中,MyProject_VERSION_MAJOR
的值为 1
。
2. <PROJECT-NAME>_VERSION_MINOR
定义:
<PROJECT-NAME>_VERSION_MINOR
是一个 CMake 变量,用于表示项目的次版本号。
作用:
这个变量也用于版本控制和管理,以便在项目配置过程中可以引用和显示次版本号。
用法:
同样可以通过 project
命令来自动设置。
示例:
project(MyProject VERSION 1.2)
在这个例子中,MyProject_VERSION_MINOR
的值为 2
。
3. configure_file
定义:
configure_file
是一个 CMake 命令,用于从输入文件生成输出文件,并在此过程中替换输入文件中的变量。
作用:
它可以用于将 CMake 配置时的变量值写入到生成文件中,常用于生成配置头文件或其他需要动态生成的文件。
用法:
configure_file(<input> <output> [COPYONLY] [ESCAPE_QUOTES] [@ONLY])
<input>
:输入文件路径。<output>
:输出文件路径。[COPYONLY]
:仅复制文件而不替换任何变量(可选)。[ESCAPE_QUOTES]
:在替换变量时转义双引号(可选)。[@ONLY]
:仅替换@VAR@
格式的变量,不替换${VAR}
格式的变量(可选)。
示例:
configure_file(config.h.in config.h)
假设 config.h.in
中有以下内容:
#define PROJECT_VERSION_MAJOR @MyProject_VERSION_MAJOR@
#define PROJECT_VERSION_MINOR @MyProject_VERSION_MINOR@
运行 configure_file
后,会生成包含实际版本号的 config.h
文件。
4. target_include_directories
定义:
target_include_directories
是一个 CMake 命令,用于为指定目标添加包含目录。
作用:
它指定编译该目标时应搜索的包含文件目录,常用于添加头文件路径。
用法:
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...])
<target>
:目标名称。[SYSTEM]
:表示这些目录是系统目录,可能影响警告的产生(可选)。[AFTER|BEFORE]
:指定包含目录的搜索顺序(可选)。<INTERFACE|PUBLIC|PRIVATE>
:指定包含目录的使用范围。INTERFACE
:仅用于链接此目标的其他目标。PUBLIC
:用于链接此目标及其依赖的其他目标。PRIVATE
:仅用于编译此目标。
[items1...]
:包含目录列表。
示例:
target_include_directories(MyApp PUBLIC ${CMAKE_SOURCE_DIR}/include)
在这个例子中,MyApp
目标将会在 ${CMAKE_SOURCE_DIR}/include
目录中查找包含文件。
编辑的文件
CMakeLists.txt
tutorial.cxx
入门
继续编辑Step1
中的文件。从TODO 7
开始,完成TODO 12
。在此练习中,我们首先在CMakeLists.txt
中添加项目版本号。在同一个文件中,使用configure_file
将给定的输入文件复制到输出文件,并替换输入文件内容中的一些变量值。
接下来,创建一个定义版本号的输入头文件TutorialConfig.h.in
,它将接受从configure_file
传递的变量。
最后,更新tutorial.cxx
以打印其版本号。
构建和运行
让我们再次构建项目。和之前一样,我们已经创建了构建目录并运行了 CMake,因此可以跳到构建步骤:
cd Step1_build
cmake --build .
验证在不带任何参数运行可执行文件时,是否会报告版本号。
解决方案
在本练习中,我们通过打印版本号来改进可执行文件。虽然我们可以完全在源代码中完成此操作,但使用CMakeLists.txt
让我们维护一个版本号的单一数据源。
首先,我们修改CMakeLists.txt
文件,使用project
命令同时设置项目名称和版本号。当调用project
命令时,CMake 在后台定义了Tutorial_VERSION_MAJOR
和Tutorial_VERSION_MINOR
。
project(Tutorial VERSION 1.0)
然后我们使用configure_file
命令将输入文件复制到指定路径,并替换输入文件中的 CMake 变量:
configure_file(TutorialConfig.h.in TutorialConfig.h)
由于配置文件将写入项目二进制目录,因此我们必须将该目录添加到搜索包含文件的路径列表中。
注意: 在本教程中,我们将项目构建目录和项目二进制目录互换使用。这两个术语相同,并且不指代bin/
目录。
我们使用target_include_directories
来指定可执行目标应查找包含文件的位置。
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
TutorialConfig.h.in
是要配置的输入头文件。当从我们的CMakeLists.txt
调用configure_file
时,@Tutorial_VERSION_MAJOR@
和@Tutorial_VERSION_MINOR@
的值将被项目中的对应版本号替换到TutorialConfig.h
中。
#cmakedefine Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#cmakedefine Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
接下来,我们需要修改tutorial.cxx
以包含配置的头文件TutorialConfig.h
。
#include "TutorialConfig.h"
最后,通过更新tutorial.cxx
来打印可执行文件名称和版本号:
std::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."
<< Tutorial_VERSION_MINOR << std::endl;
第二步:添加库
到目前为止,我们已经了解了如何使用 CMake 创建一个基本项目。在本步骤中,我们将学习如何在项目中创建和使用库。我们还将了解如何使库的使用变为可选。
练习1 - 创建库
在 CMake 中添加库,使用add_library
命令并指定哪些源文件组成该库。
我们可以通过组织项目文件,将所有源文件放置在一个目录中,或使用一个或多个子目录。在这种情况下,我们将专门为库创建一个子目录。在这里,我们可以添加一个新的CMakeLists.txt
文件和一个或多个源文件。在顶级CMakeLists.txt
文件中,我们使用add_subdirectory
命令添加该子目录到构建中。
库创建后,通过target_include_directories
和target_link_libraries
将其连接到我们的可执行目标。
目标
添加并使用库。
有用的资源
add_library
add_subdirectory
target_include_directories
target_link_libraries
PROJECT_SOURCE_DIR
1. add_library
定义:
add_library
是一个 CMake 命令,用于定义一个库目标,可以是静态库、共享库或者模块库。
作用:
它指定要编译的源文件,并生成一个库目标。
用法:
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
<name>
:库的名称。[STATIC | SHARED | MODULE]
:指定库的类型,默认是STATIC
。STATIC
:静态库。SHARED
:共享库。MODULE
:模块库,通常用于插件。
[EXCLUDE_FROM_ALL]
:不将该目标包含在默认生成目标中(可选)。source1 source2 ... sourceN
:构成该库的源文件列表。
示例:
add_library(MyLibrary STATIC mylib.cpp mylib.h)
2. add_subdirectory
定义:
add_subdirectory
是一个 CMake 命令,用于在构建过程中添加一个子目录。
作用:
它可以递归地处理子目录中的 CMakeLists.txt
文件,使其包含在构建过程中。
用法:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
source_dir
:源代码子目录。[binary_dir]
:构建输出目录(可选)。[EXCLUDE_FROM_ALL]
:不将该子目录包含在默认生成目标中(可选)。
示例:
add_subdirectory(src)
3. target_include_directories
定义:
target_include_directories
是一个 CMake 命令,用于为指定目标添加包含目录。
作用:
它指定编译该目标时应搜索的包含文件目录,常用于添加头文件路径。
用法:
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...])
<target>
:目标名称。[SYSTEM]
:表示这些目录是系统目录,可能影响警告的产生(可选)。[AFTER|BEFORE]
:指定包含目录的搜索顺序(可选)。<INTERFACE|PUBLIC|PRIVATE>
:指定包含目录的使用范围。INTERFACE
:仅用于链接此目标的其他目标。PUBLIC
:用于链接此目标及其依赖的其他目标。PRIVATE
:仅用于编译此目标。
[items1...]
:包含目录列表。
示例:
target_include_directories(MyApp PUBLIC ${CMAKE_SOURCE_DIR}/include)
在这个例子中,MyApp
目标将会在 ${CMAKE_SOURCE_DIR}/include
目录中查找包含文件。
4. target_link_libraries
定义:
target_link_libraries
是一个 CMake 命令,用于为指定目标添加库依赖。
作用:
它指定链接时需要的库,可以是其他 CMake 目标或系统库。
用法:
target_link_libraries(<target> [<INTERFACE|PUBLIC|PRIVATE>] <item>...)
<target>
:目标名称。[<INTERFACE|PUBLIC|PRIVATE>]
:指定库的使用范围。INTERFACE
:仅用于链接此目标的其他目标。PUBLIC
:用于链接此目标及其依赖的其他目标。PRIVATE
:仅用于编译此目标。
<item>...
:库的名称列表。
示例:
target_link_libraries(MyApp PRIVATE MyLibrary)
在这个例子中,MyApp
目标将链接 MyLibrary
库。
5. PROJECT_SOURCE_DIR
定义:
PROJECT_SOURCE_DIR
是一个 CMake 变量,表示顶级 CMakeLists.txt 文件所在的目录。
作用:
它用于在项目中引用源代码的根目录,通常用于设置包含路径或其他相对于项目根目录的路径。
用法:
直接在 CMakeLists.txt 文件中引用。
示例:
include_directories(${PROJECT_SOURCE_DIR}/include)
在这个例子中,包含目录设置为项目根目录下的 include
目录。
编辑的文件
CMakeLists.txt
tutorial.cxx
MathFunctions/CMakeLists.txt
入门
在本练习中,我们将向项目添加一个库,其中包含我们自己实现的计算平方根的函数。可执行文件可以使用此库而不是编译器提供的标准平方根函数。
对于本教程,我们将库放入一个名为MathFunctions
的子目录中。该目录已包含头文件MathFunctions.h
和mysqrt.h
。它们各自的源文件MathFunctions.cxx
和mysqrt.cxx
也已提供。我们不需要修改这些文件。mysqrt.cxx
包含一个名为mysqrt
的函数,其功能类似于编译器的sqrt
函数。MathFunctions.cxx
包含一个sqrt
函数,用于隐藏sqrt
的实现细节。
从Step2
目录开始,从TODO 1
开始,完成到TODO 6
。
首先,填写MathFunctions
子目录中的单行CMakeLists.txt
。
接下来,编辑顶级CMakeLists.txt
。
最后,在tutorial.cxx
中使用新创建的MathFunctions
库。
构建和运行
运行cmake
可执行文件或cmake-gui
来配置项目,然后使用你选择的构建工具构建它。
以下是命令行的示例:
mkdir Step2_build
cd Step2_build
cmake ../Step2
cmake --build .
尝试使用新构建的Tutorial
并确保它仍然生成正确的平方根值。
解决方案
在MathFunctions
目录的CMakeLists.txt
文件中,我们使用add_library
创建一个名为MathFunctions
的库目标。库的源文件作为参数传递给add_library
。这一行代码如下所示:
add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)
要使用新库,我们将在顶级CMakeLists.txt
文件中添加add_subdirectory
调用,以便库将被构建。
add_subdirectory(MathFunctions)
接下来,使用target_link_libraries
将新库目标链接到可执行目标。
target_link_libraries(Tutorial PUBLIC MathFunctions)
最后,我们需要指定库的头文件位置。修改现有的target_include_directories
调用,添加MathFunctions
子目录作为包含目录,以便找到MathFunctions.h
头文件。
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/MathFunctions"
)
现在我们可以使用我们的库。在tutorial.cxx
中包含MathFunctions.h
:
#include "MathFunctions.h"
最后,用包装函数mathfunctions::sqrt
替换sqrt
。
const double outputValue = mathfunctions::sqrt(inputValue);
练习2 - 添加一个选项
现在让我们在MathFunctions
库中添加一个选项,允许开发人员选择使用自定义平方根实现或内置的标准实现。虽然在本教程中没有必要这样做,但对于较大的项目,这是很常见的做法。
CMake 可以使用option
命令实现这一点。这给用户提供了一个变量,可以在配置 cmake 构建时更改。此设置将存储在缓存中,因此用户不需要每次在构建目录上运行 CMake 时都设置该值。
目标
添加选项以在没有MathFunctions
的情况下进行构建。
有用的资源
if
option
target_compile_definitions
1. if
定义:
if
是一个 CMake 命令,用于条件判断。
作用:
它可以根据某个条件是否成立来决定是否执行某些命令,从而实现条件化配置。
用法:
if(expression)
# commands
endif()
if(expression)
# commands
else()
# commands
endif()
if(expression)
# commands
elseif(expression)
# commands
else()
# commands
endif()
expression
:条件表达式,可以是变量、比较运算或逻辑运算。
示例:
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Building in Debug mode")
endif()
2. option
定义:
option
是一个 CMake 命令,用于定义一个布尔型选项变量。
作用:
它可以让用户在配置项目时启用或禁用某些特性,提供一种简单的开关机制。
用法:
option(<option_variable> "description" [initial_value])
<option_variable>
:选项变量的名称。"description"
:选项的描述。[initial_value]
:初始值,可以是ON
或OFF
,默认为OFF
。
示例:
option(BUILD_TESTS "Build the tests" ON)
在这个例子中,定义了一个名为 BUILD_TESTS
的选项变量,初始值为 ON
。
3. target_compile_definitions
定义:
target_compile_definitions
是一个 CMake 命令,用于为指定目标添加编译定义。
作用:
它可以向编译器传递预处理器宏定义,常用于控制编译过程中的条件编译。
用法:
target_compile_definitions(<target> [<INTERFACE|PUBLIC|PRIVATE>] <definition>...)
<target>
:目标名称。[<INTERFACE|PUBLIC|PRIVATE>]
:指定定义的使用范围。INTERFACE
:仅用于链接此目标的其他目标。PUBLIC
:用于链接此目标及其依赖的其他目标。PRIVATE
:仅用于编译此目标。
<definition>...
:预处理器宏定义,可以是NAME
或NAME=value
的形式。
示例:
target_compile_definitions(MyApp PRIVATE VERSION_MAJOR=1)
在这个例子中,MyApp
目标将使用 VERSION_MAJOR=1
作为编译定义。
编辑的文件
MathFunctions/CMakeLists.txt
MathFunctions/MathFunctions.cxx
入门
从练习1的结果文件开始。完成TODO 7
到TODO 14
。
首先在MathFunctions/CMakeLists.txt
中使用option
命令创建变量USE_MYMATH
。在同一文件中,使用该选项向MathFunctions
库传递编译定义。
然后,更新MathFunctions.cxx
以基于USE_MYMATH
重定向编译。
最后,在USE_MYMATH
关闭时阻止编译mysqrt.cxx
,通过在MathFunctions/CMakeLists.txt
的USE_MYMATH
块中使其成为单独的库。
构建和运行
由于我们已经从练习1配置了构建目录,只需调用以下命令重新构建:
cd ../Step2_build
cmake --build .
接下来,在几个数字上运行Tutorial
可执行文件,以验证它是否仍然正确。
现在让我们将USE_MYMATH
的值更新为OFF
。最简单的方法是使用cmake-gui
或ccmake
,如果你在终端中。或者,作为替代方案,如果你想从命令行更改选项,请尝试:
cmake ../Step2 -DUSE_MYMATH=OFF
现在,使用以下命令重新构建代码:
cmake --build .
然后,再次运行可执行文件以确保在USE_MYMATH
设置为OFF
时它仍然有效。哪种函数给出的结果更好,sqrt
还是mysqrt
?
解决方案
第一步是在MathFunctions/CMakeLists.txt
中添加一个选项。此选项将在cmake-gui
和ccmake
中显示,默认值为ON
,用户可以更改该值。
option(USE_MYMATH "Use tutorial provided math implementation" ON)
接下来,使用这个新选项有条件地构建和链接我们的库。
创建一个if
语句来检查USE_MYMATH
的值。在if
块中,放置target_compile_definitions
命令和编译定义USE_MYMATH
。
if (USE_MYMATH)
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif()
当USE_MYMATH
为ON
时,编译定义USE_MYMATH
将被设置。然后我们可以使用这个编译定义来启用或禁用源代码的部分。
相应的源代码更改相对简单。在MathFunctions.cxx
中,使用USE_MYMATH
控制使用哪个平方根函数:
#ifdef
USE_MYMATH
return mysqrt(x);
#else
return std::sqrt(x);
#endif
接下来,我们需要在定义USE_MYMATH
时包含mysqrt.h
。
#ifdef USE_MYMATH
#include "mysqrt.h"
#endif
最后,由于我们现在使用std::sqrt
,需要包含cmath
。
#include "cmath"
到此为止,如果USE_MYMATH
为OFF
,mysqrt.cxx
将不会被使用,但它仍将被编译,因为MathFunctions
目标将mysqrt.cxx
列为源文件。
有几种方法可以解决这个问题。第一种方法是使用target_sources
在USE_MYMATH
块中添加mysqrt.cxx
。另一种方法是在USE_MYMATH
块内创建一个额外的库,该库负责编译mysqrt.cxx
。在本教程中,我们将创建一个额外的库。
首先,在USE_MYMATH
内创建一个名为SqrtLibrary
的库,其源文件为mysqrt.cxx
。
if (USE_MYMATH)
add_library(SqrtLibrary mysqrt.cxx)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
接下来,当USE_MYMATH
启用时,将SqrtLibrary
链接到MathFunctions
。
if (USE_MYMATH)
add_library(SqrtLibrary mysqrt.cxx)
target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
最后,我们可以从MathFunctions
库源列表中删除mysqrt.cxx
,因为当包含SqrtLibrary
时,它将被引入。
add_library(MathFunctions MathFunctions.cxx)
通过这些更改,现在构建和使用MathFunctions
库时,mysqrt
函数完全是可选的。用户可以切换USE_MYMATH
以操控构建中使用的库。
第三步:为库添加使用要求
库或可执行文件的使用要求参数允许更好地控制库或可执行文件的链接和包含路径,同时也可以更好地控制 CMake 内部目标的传递属性。主要使用要求的命令有:
target_compile_definitions
target_compile_options
target_include_directories
target_link_directories
target_link_options
target_precompile_headers
target_sources
目标
为库添加使用要求。
有用的资源
CMAKE_CURRENT_SOURCE_DIR
1. CMAKE_CURRENT_SOURCE_DIR
定义:
CMAKE_CURRENT_SOURCE_DIR
是一个 CMake 变量,表示当前处理的 CMakeLists.txt 文件所在的目录。
作用:
它用于在当前 CMakeLists.txt 文件中引用当前目录的路径,常用于设置相对路径或包含目录。
用法:
直接在 CMakeLists.txt 文件中引用该变量。
示例:
# 设置包含目录为当前目录的 include 子目录
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
# 添加子目录
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)
在这个例子中,include_directories
和 add_subdirectory
命令都使用 CMAKE_CURRENT_SOURCE_DIR
变量来指定相对于当前 CMakeLists.txt 文件目录的路径。
这个变量在处理复杂项目结构时特别有用,因为它允许你编写相对独立的 CMakeLists.txt 文件,而无需硬编码绝对路径。
编辑的文件
MathFunctions/CMakeLists.txt
CMakeLists.txt
入门
在本练习中,我们将重构代码,使其使用现代 CMake 方法。我们将让库定义其自己的使用要求,以便根据需要传递给其他目标。在本例中,MathFunctions
将自己指定任何需要的包含目录。然后,消费目标Tutorial
只需链接到MathFunctions
,无需担心任何额外的包含目录。
起始源代码位于Step3
目录中。在本练习中,完成TODO 1
到TODO 3
。
首先,在MathFunctions/CMakeLists.txt
中添加target_include_directories
调用。请记住,CMAKE_CURRENT_SOURCE_DIR
是当前正在处理的源目录的路径。
然后,更新(并简化!)顶级CMakeLists.txt
中的target_include_directories
调用。
构建和运行
创建一个名为Step3_build
的新目录,运行cmake
可执行文件或cmake-gui
来配置项目,然后使用你选择的构建工具或通过在构建目录中使用cmake --build .
来构建项目。以下是命令行的示例:
mkdir Step3_build
cd Step3_build
cmake ../Step3
cmake --build .
接下来,使用新构建的Tutorial
并验证其是否按预期工作。
解决方案
让我们更新上一步的代码,使用现代 CMake 方法来设置使用要求。
我们要声明任何链接到MathFunctions
的目标需要包含当前源目录,而MathFunctions
本身不需要。这可以通过INTERFACE
使用要求表示。记住,INTERFACE
表示使用者需要但生产者不需要的东西。
在MathFunctions/CMakeLists.txt
的末尾,使用INTERFACE
关键字调用target_include_directories
,如下所示:
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
现在我们已经为MathFunctions
指定了使用要求,我们可以安全地从顶级CMakeLists.txt
中删除对EXTRA_INCLUDES
变量的使用。
删除这一行:
# list(APPEND EXTRA_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/MathFunctions)
并从target_include_directories
中删除EXTRA_INCLUDES
:
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
请注意,通过这种技术,我们的可执行目标使用我们的库时唯一要做的就是调用target_link_libraries
并传入库目标的名称。在较大的项目中,手动指定库依赖关系的传统方法会很快变得非常复杂。
练习2 - 使用接口库设置C++标准
现在我们已经将代码切换到更现代的方法,让我们演示一种使用INTERFACE
库设置属性的现代技术。
让我们重构现有代码,使用INTERFACE
库来指定所需的 C++ 标准。
目标
添加一个INTERFACE
库目标,以指定所需的 C++ 标准。
有用的资源
add_library
target_compile_features
target_link_libraries
1. target_compile_features
定义:
target_compile_features
是一个 CMake 命令,用于为指定目标添加编译特性(例如 C++11、C++14 的支持)。
作用:
它可以确保目标在编译时使用特定的编译器特性或标准,从而保证代码的兼容性和功能。
用法:
target_compile_features(<target> [INTERFACE|PUBLIC|PRIVATE] <feature>...)
<target>
:目标名称。[INTERFACE|PUBLIC|PRIVATE]
:指定特性的使用范围。INTERFACE
:仅用于链接此目标的其他目标。PUBLIC
:用于链接此目标及其依赖的其他目标。PRIVATE
:仅用于编译此目标。
<feature>...
:需要的编译特性列表,如cxx_std_11
、cxx_std_14
等。
示例:
target_compile_features(MyApp PUBLIC cxx_std_14)
在这个例子中,MyApp
目标将使用 C++14 标准进行编译。
编辑的文件
CMakeLists.txt
MathFunctions/CMakeLists.txt
入门
在本练习中,我们将重构代码,使用INTERFACE
库来指定 C++ 标准。
从我们在 Step3 练习1结束时留下的代码开始。你需要完成TODO 4
到TODO 7
。
首先,编辑顶级CMakeLists.txt
文件。构建一个名为tutorial_compiler_flags
的INTERFACE
库目标,并指定cxx_std_11
作为目标编译特性。
修改CMakeLists.txt
和MathFunctions/CMakeLists.txt
,使所有目标都有一个调用target_link_libraries
指向tutorial_compiler_flags
。
构建和运行
由于我们已经从练习1配置了构建目录,只需调用以下命令重新构建:
cd Step3_build
cmake --build .
接下来,使用新构建的Tutorial
并验证其是否按预期工作。
解决方案
让我们更新上一步的代码,使用接口库设置我们的 C++ 要求。
首先,我们需要删除两个set
调用,分别是CMAKE_CXX_STANDARD
和CMAKE_CXX_STANDARD_REQUIRED
。需要删除的具体行如下:
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
接下来,我们需要创建一个接口库tutorial_compiler_flags
。然后使用target_compile_features
添加编译特性cxx_std_11
。
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
最后,使用接口库设置好后,我们需要将可执行文件Tutorial
、SqrtLibrary
库和MathFunctions
库链接到新的tutorial_compiler_flags
库。分别如下所示:
target_link_libraries(Tutorial PUBLIC
tutorial_compiler_flags
MathFunctions
)
target_link_libraries(SqrtLibrary INTERFACE tutorial_compiler_flags)
target_link_libraries(MathFunctions PUBLIC tutorial_compiler_flags)
通过这种方式,我们所有的代码仍然需要 C++ 11 来构建。请注意,这种方法使我们能够明确指定哪些目标获得特定要求。此外,我们在接口库中创建了单一数据源。
第四步:添加生成器表达式
生成器表达式在生成构建系统时进行评估,以生成特定于每个构建配置的信息。
生成器表达式允许在许多目标属性的上下文中使用,例如LINK_LIBRARIES
、INCLUDE_DIRECTORIES
、COMPILE_DEFINITIONS
等。它们也可以在使用这些属性的命令时使用,例如target_link_libraries
、target_include_directories
、target_compile_definitions
等。
生成器表达式可用于启用条件链接、条件定义编译、条件包含目录等。这些条件可以基于构建配置、目标属性、平台信息或任何其他可查询的信息。
生成器表达式包括逻辑表达式、信息表达式和输出表达式。
逻辑表达式用于创建条件输出。基本表达式是0
和1
表达式。$<0:…>
生成空字符串,$<1:…>
生成…
的内容。它们也可以嵌套。
练习1 - 使用生成器表达式添加编译器警告标志
生成器表达式的一个常见用法是有条件地添加编译器标志,例如用于语言级别或警告的标志。一个不错的模式是将此信息与一个INTERFACE
目标相关联,允许此信息传播。
目标
在构建时添加编译器警告标志,但不用于已安装的版本。
有用的资源
cmake-generator-expressions
cmake_minimum_required
set
target_compile_options
1. cmake-generator-expressions
定义:
CMake 生成器表达式(Generator Expressions)是在生成时求值的表达式,通常用于配置目标属性或其他需要在生成时才确定的值。
作用:
生成器表达式允许在生成时动态决定某些属性或变量的值,从而使构建配置更加灵活和强大。
用法:
生成器表达式使用 $<...>
语法,支持各种条件和逻辑运算。
示例:
add_library(MyLibrary STATIC mylib.cpp)
target_compile_definitions(MyLibrary PRIVATE $<$<CONFIG:Debug>:DEBUG_BUILD>)
在这个例子中,DEBUG_BUILD
定义只会在 Debug 配置下添加。
常见生成器表达式:
$<CONFIG:cfg>
:当当前配置为cfg
时为真。$<TARGET_FILE:tgt>
:生成目标文件的路径。$<STREQUAL:string1,string2>
:当string1
和string2
相等时为真。
2. target_compile_options
定义:
target_compile_options
是一个 CMake 命令,用于为指定目标添加编译选项。
作用:
它可以向编译器传递特定的编译选项或标志,控制编译行为。
用法:
target_compile_options(<target> [INTERFACE|PUBLIC|PRIVATE] <options>...)
<target>
:目标名称。[INTERFACE|PUBLIC|PRIVATE]
:指定选项的使用范围。INTERFACE
:仅用于链接此目标的其他目标。PUBLIC
:用于链接此目标及其依赖的其他目标。PRIVATE
:仅用于编译此目标。
<options>...
:编译选项列表。
示例:
target_compile_options(MyApp PRIVATE -Wall -Wextra)
在这个例子中,MyApp
目标将使用 -Wall
和 -Wextra
编译选项。
编辑的文件
CMakeLists.txt
入门
打开文件Step4/CMakeLists.txt
并完成TODO 1
到TODO 4
。
首先,在顶级CMakeLists.txt
文件中,我们需要将cmake_minimum_required
设置为3.15
。在本练习中,我们将使用一个在 CMake 3.15 中引入的生成器表达式。
接下来,添加我们项目所需的编译器警告标志。由于警告标志因编译器而异,我们使用COMPILE_LANG_AND_ID
生成器表达式来控制根据语言和一组编译器 ID 应用哪些标志。
构建和运行
创建一个名为Step4_build
的新目录,运行cmake
可执行文件或cmake-gui
来配置项目,然后使用你选择的构建工具或通过在构建目录中使用cmake --build .
来构建项目。
mkdir Step4_build
cd Step4_build
cmake ../Step4
cmake --build .
解决方案
将cmake_minimum_required
更新为至少需要 CMake 版本3.15
:
cmake_minimum_required(VERSION 3.15)
接下来,我们确定系统当前使用的编译器,因为警告标志因使用的编译器而异。这是通过COMPILE_LANG_AND_ID
生成器表达式完成的。我们将结果设置在变量gcc_like_cxx
和msvc_cxx
中,如下所示:
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,GNU,Clang,AppleClang>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
接下来,添加我们项目所需的编译器警告标志。使用变量gcc_like_cxx
和msvc_cxx
,我们可以使用另一个生成器表达式在这些变量为真时应用相应的标志。我们使用target_compile_options
将这些标志应用到我们的接口库。
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
"$<${msvc_cxx}:-W3>"
)
最后,我们只希望这些警告标志在构建期间使用。我们安装的项目的使用者不应继承我们的警告标志。为此,我们使用BUILD_INTERFACE
条件将标志从TODO 3
中的生成器表达式包裹起来。结果完整代码如下所示:
target_compile_options(tutorial_compiler_flags INTERFACE
"$<BUILD_INTERFACE:$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<BUILD_INTERFACE:$<${msvc_cxx}:-W3>>"
)
第五步:安装和测试
练习1 - 安装规则
通常,仅构建可执行文件是不够的,还应能够安装它。使用 CMake,我们可以使用install
命令指定安装规则。支持在 CMake 中进行本地安装通常只需指定安装位置以及要安装的目标和文件。
目标
安装Tutorial
可执行文件和MathFunctions
库。
有用的资源
install
install
定义:
install
是一个 CMake 命令,用于定义安装规则。它指定构建目标、文件、目录等在安装时应该被复制到的目标位置。
作用:
install
命令允许你定义如何将编译生成的文件和其他相关文件安装到特定的目录,便于分发和部署项目。
用法:
install
命令有多种形式,可以用于安装目标、文件、目录等。常见的用法包括:
安装目标
install(TARGETS <targets> [...])
TARGETS
:指定要安装的目标(如库或可执行文件)。<targets>
:一个或多个目标名称。- 可以添加其他选项如
RUNTIME
,LIBRARY
,ARCHIVE
,DESTINATION
等。
示例:
install(TARGETS MyApp
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static)
安装文件
install(FILES <file>... DESTINATION <dir> [...])
FILES
:指定要安装的文件。<file>
:一个或多个文件路径。DESTINATION
:目标安装目录。
示例:
install(FILES README.md LICENSE DESTINATION share/doc/MyApp)
安装目录
install(DIRECTORY <dir>... DESTINATION <dir> [...])
DIRECTORY
:指定要安装的目录。<dir>
:一个或多个目录路径。DESTINATION
:目标安装目录。
示例:
install(DIRECTORY include/ DESTINATION include)
安装代码
install(CODE "<code>")
install(SCRIPT <file>)
CODE
:直接在安装时执行的 CMake 代码。SCRIPT
:在安装时执行的 CMake 脚本文件。
示例:
install(CODE "MESSAGE(\"Installing MyApp...\")")
install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake_install_script.cmake")
安装配置文件
用于生成和安装导出目标配置文件,例如 MyAppConfig.cmake
。
示例:
install(EXPORT MyAppTargets
FILE MyAppTargets.cmake
DESTINATION lib/cmake/MyApp)
编辑的文件
MathFunctions/CMakeLists.txt
CMakeLists.txt
入门
起始代码位于Step5
目录中。在本练习中,完成TODO 1
到TODO 4
。
首先,更新MathFunctions/CMakeLists.txt
以将MathFunctions
和tutorial_compiler_flags
库安装到lib
目录。在同一文件中,指定安装规则,将MathFunctions.h
安装到include
目录。
然后,更新顶级CMakeLists.txt
以将Tutorial
可执行文件安装到bin
目录。最后,任何头文件应安装到include
目录。请记住,TutorialConfig.h
位于PROJECT_BINARY_DIR
。
构建和运行
创建一个名为Step5_build
的新目录。运行cmake
可执行文件或cmake-gui
来配置项目,然后使用你选择的构建工具构建项目。
然后,通过在命令行中使用cmake --install
选项运行安装步骤。此步骤将安装适当的头文件、库和可执行文件。例如:
cmake --install .
对于多配置工具,不要忘记使用--config
参数指定配置。
cmake --install . --config Release
如果使用 IDE,只需构建INSTALL
目标。你可以通过命令行构建相同的安装目标,如下所示:
cmake --build . --target install --config Debug
CMake 变量CMAKE_INSTALL_PREFIX
用于确定文件安装的根目录。如果使用cmake --install
命令,可以通过--prefix
参数覆盖安装前缀。例如:
cmake --install . --prefix "/home/myuser/installdir"
导航到安装目录,验证安装的Tutorial
是否可以运行。
解决方案
我们项目的安装规则相对简单:
- 对于
MathFunctions
,我们希望将库和头文件安装到lib
和include
目录。 - 对于
Tutorial
可执行文件,我们希望将可执行文件和配置头文件安装到bin
和include
目录。
因此,在MathFunctions/CMakeLists.txt
的末尾添加:
install(TARGETS MathFunctions tutorial_compiler_flags
DESTINATION lib)
和
install(FILES MathFunctions.h
DESTINATION include)
Tutorial
可执行文件和配置头文件的安装规则类似。在顶级CMakeLists.txt
的末尾添加:
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include)
这就是创建一个基本本地安装教程所需的全部内容。
练习2 - 测试支持
CTest 提供了一种轻松管理项目测试的方法。可以通过add_test
命令添加测试。虽然本教程没有明确涵盖,但 CTest 与其他测试框架(如 GoogleTest)有很好的兼容性。
目标
使用 CTest 为我们的可执行文件创建单元测试。
有用的资源
enable_testing
add_test
function
set_tests_properties
ctest
1. enable_testing
定义:
enable_testing
是一个 CMake 命令,用于启用测试功能。
作用:
它初始化测试子系统,使后续可以添加测试并运行它们。通常在顶级 CMakeLists.txt
文件中调用。
用法:
enable_testing()
示例:
project(MyProject)
enable_testing()
在这个例子中,测试子系统在 MyProject
中被启用。
2. add_test
定义:
add_test
是一个 CMake 命令,用于添加一个测试。
作用:
它定义了一个测试命令,该命令将在测试运行时执行。可以为测试指定名称和要运行的命令。
用法:
add_test(NAME <name> COMMAND <command> [<arg>...])
NAME <name>
:测试的名称。COMMAND <command> [<arg>...]
:要运行的命令及其参数。
示例:
add_executable(MyTest mytest.cpp)
add_test(NAME MyTest COMMAND MyTest)
在这个例子中,定义了一个名为 MyTest
的测试,它将运行生成的 MyTest
可执行文件。
3. function
定义:
function
是一个 CMake 命令,用于定义一个函数。
作用:
它允许你定义一个可以重复使用的命令序列,类似于编程语言中的函数。
用法:
function(<name> [arg1 [arg2 ...]])
# commands
endfunction()
<name>
:函数的名称。[arg1 [arg2 ...]]
:函数的参数列表。
示例:
function(hello name)
message("Hello, ${name}!")
endfunction()
hello(World)
在这个例子中,定义了一个名为 hello
的函数,并调用它输出 Hello, World!
。
4. set_tests_properties
定义:
set_tests_properties
是一个 CMake 命令,用于设置一个或多个测试的属性。
作用:
它允许你为测试定义属性,例如超时时间、工作目录等。
用法:
set_tests_properties(test1 [test2 ...] PROPERTIES prop1 value1 [prop2 value2 ...])
test1 [test2 ...]
:一个或多个测试名称。PROPERTIES prop1 value1 [prop2 value2 ...]
:属性和值对。
示例:
add_test(NAME MyTest COMMAND MyTest)
set_tests_properties(MyTest PROPERTIES TIMEOUT 10)
在这个例子中,为 MyTest
测试设置了一个 10 秒的超时时间。
5. ctest
定义:
ctest
是一个跨平台测试驱动程序,通常与 CMake 一起使用,用于构建、执行和汇总测试。
作用:
它提供了一个命令行工具来运行由 add_test
添加的测试,并生成测试报告。
用法:
ctest [options]
常用选项:
-C <config>
:指定配置(如 Debug、Release)。-R <regex>
:只运行匹配正则表达式的测试。-V
:详细模式,显示测试输出。--output-on-failure
:在测试失败时显示输出。
示例:
ctest -C Debug -V
在这个例子中,运行 Debug 配置下的所有测试,并以详细模式显示输出。
编辑的文件
CMakeLists.txt
入门
起始源代码位于Step5
目录中。在本练习中,完成TODO 5
到TODO 9
。
首先,我们需要启用测试。接下来,开始使用add_test
向项目添加测试。我们将添加3个简单的测试,然后你可以根据需要添加更多的测试。
构建和运行
导航到构建目录并重新构建应用程序。然后,运行ctest
可执行文件:ctest -N
和ctest -VV
。对于多配置生成器(例如 Visual Studio),必须使用-C <mode>
标志指定配置类型。例如,要在 Debug 模式下运行测试,请使用ctest -C Debug -VV
,在构建目录中运行(而不是 Debug 子目录中)。Release 模式也可以在相同位置运行,但使用-C Release
。或者,从 IDE 构建RUN_TESTS
目标。
解决方案
让我们测试我们的应用程序。在顶级CMakeLists.txt
文件的末尾,我们首先需要使用enable_testing
命令启用测试。
enable_testing()
启用测试后,我们将添加一些基本测试,以验证应用程序是否正常工作。首先,我们使用add_test
创建一个测试,该测试运行Tutorial
可执行文件并传入参数25。对于此测试,我们不检查可执行文件的计算答案。此测试将验证应用程序是否运行,不会发生段错误或其他崩溃,并且返回值为零。这是 CTest 测试的基本形式。
add_test(NAME Runs COMMAND Tutorial 25)
接下来,我们使用`
PASS_REGULAR_EXPRESSION
测试属性,验证测试输出是否包含某些字符串。在本例中,验证当提供不正确数量的参数时,是否打印了用法消息。
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
接下来的测试将验证计算值确实是平方根。
add_test(NAME StandardUse COMMAND Tutorial 4)
set_tests_properties(StandardUse
PROPERTIES PASS_REGULAR_EXPRESSION "4 is 2"
)
一个测试不足以让我们确信它对传入的所有值都有效。我们应该添加更多的测试来验证这一点。为了方便添加更多测试,我们创建一个名为do_test
的函数,该函数运行应用程序并验证给定输入的计算平方根是否正确。对于每次调用do_test
,将向项目添加一个测试,测试名称、输入和预期结果基于传递的参数。
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction()
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 16 "16 is 4")
do_test(Tutorial 25 "25 is 5")
第六步:添加测试仪表板支持
添加将我们的测试结果提交到仪表板的支持很简单。我们已经在测试支持中为项目定义了一些测试。现在我们只需运行这些测试并将它们提交到 CDash。
练习1 - 将结果发送到测试仪表板
目标
使用 CDash 显示我们的 CTest 结果。
有用的资源
- ctest
include
CTest
1. include
定义:
include
是一个 CMake 命令,用于读取并执行另一个 CMake 脚本文件的内容。
作用:
它可以将外部的 CMake 脚本文件引入当前的 CMakeLists.txt 文件中,从而重用代码片段或加载模块。
用法:
include(<file> [OPTIONAL] [RESULT_VARIABLE <variable>] [NO_POLICY_SCOPE])
<file>
:要包含的 CMake 脚本文件路径,可以是相对路径或绝对路径。[OPTIONAL]
:如果文件不存在,不会报错。[RESULT_VARIABLE <variable>]
:将操作结果存储在<variable>
中,如果文件包含成功则为TRUE
,否则为FALSE
。[NO_POLICY_SCOPE]
:不创建新的策略范围。
示例:
include(MyCMakeFile.cmake)
在这个例子中,MyCMakeFile.cmake
文件的内容将被包含到当前 CMakeLists.txt 文件中。
编辑的文件
CMakeLists.txt
入门
对于本练习,在顶级CMakeLists.txt
中完成TODO 1
,包括CTest
模块。这将启用 CTest 测试以及 CDash 仪表板提交,因此我们可以安全地删除对enable_testing
的调用。
我们还需要获取一个CTestConfig.cmake
文件,放置在顶级目录中。运行ctest
可执行文件时将读取此文件,以收集有关测试仪表板的信息。它包含:
- 项目名称
- 项目"夜间"开始时间
- 此项目24小时"日"开始的时间。
- 提交生成的文档的 CDash 实例的 URL
对于本教程,使用一个公共仪表板服务器,并在此步骤的根目录中提供相应的CTestConfig.cmake
文件。在实践中,此文件将从 CDash 实例的项目"设置"页面下载,作为主机托管测试结果的文件。下载后,不应在本地修改该文件。
set(CTEST_PROJECT_NAME "CMakeTutorial")
set(CTEST_NIGHTLY_START_TIME "00:00:00 UTC")
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=CMakeTutorial")
set(CTEST_DROP_SITE_CDASH TRUE)
构建和运行
请注意,作为 CDash 提交的一部分,可能会公开显示有关开发系统的一些信息(例如站点名称或完整路径名)。
要创建一个简单的测试仪表板,运行cmake
可执行文件或cmake-gui
配置项目,但不要构建它。相反,导航到构建目录并运行:
ctest [-VV] -D Experimental
请记住,对于多配置生成器(例如 Visual Studio),必须指定配置类型:
ctest [-VV] -C Debug -D Experimental
或者,从 IDE 中构建Experimental
目标。
ctest
可执行文件将构建项目,运行所有测试,并将结果提交到 Kitware 的公共仪表板:https://my.cdash.org/index.php?project=CMakeTutorial。
解决方案
本步骤中唯一需要更改的 CMake 代码是通过包括CTest
模块在我们的顶级CMakeLists.txt
中启用 CDash 仪表板提交:
include(CTest)
第七步:添加系统检测
让我们考虑向项目中添加一些依赖于目标平台可能没有的功能代码。例如,我们将添加一些代码,这些代码取决于目标平台是否具有log
和exp
函数。当然,几乎每个平台都有这些函数,但对于本教程,假设它们并不常见。
练习1 - 评估依赖项可用性
目标
根据可用系统依赖项更改实现。
有用的资源
CheckCXXSourceCompiles
target_compile_definitions
1. CheckCXXSourceCompiles
定义:
CheckCXXSourceCompiles
是一个 CMake 模块,用于检查给定的 C++ 源代码是否能够成功编译。
作用:
它可以用于检测编译器特性、库函数等,通过编译测试代码来验证是否支持某些功能。
用法:
在 CMakeLists.txt 文件中使用 include
命令包含模块,然后调用 check_cxx_source_compiles
函数。
示例:
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("
#include <iostream>
int main() {
std::cout << \"Hello, World!\" << std::endl;
return 0;
}
" RESULT)
if(RESULT)
message(STATUS "C++ source compiles successfully.")
else()
message(FATAL_ERROR "C++ source failed to compile.")
endif()
在这个例子中,包含并使用了 CheckCXXSourceCompiles
模块来检查给定的 C++ 源代码是否能够成功编译,并根据结果输出消息或终止配置。
通过 CheckCXXSourceCompiles
模块,你可以在配置阶段进行复杂的编译器特性检测和条件化配置,从而使构建过程更加健壮和灵活。
编辑的文件
MathFunctions/CMakeLists.txt
MathFunctions/mysqrt.cxx
入门
起始源代码位于Step7
目录中。在本练习中,完成TODO 1
到TODO 5
。
首先编辑MathFunctions/CMakeLists.txt
。包括CheckCXXSourceCompiles
模块。然后,使用check_cxx_source_compiles
确定是否可以从cmath
中使用log
和exp
。如果它们可用,使用target_compile_definitions
指定HAVE_LOG
和HAVE_EXP
作为编译定义。
在MathFunctions/mysqrt.cxx
中,包括cmath
。然后,如果系统具有log
和exp
,使用它们计算平方根。
构建和运行
创建一个名为Step7_build
的新目录。运行cmake
可执行文件或cmake-gui
来配置项目,然后使用你选择的构建工具构建项目并运行Tutorial
可执行文件。
可以如下所示:
mkdir Step7_build
cd Step7_build
cmake ../Step7
cmake --build .
现在哪种函数给出的结果更好,sqrt
还是mysqrt
?
解决方案
在本练习中,我们将使用CheckCXXSourceCompiles
模块的函数,因此首先必须在MathFunctions/CMakeLists.txt
中包含它。
include(CheckCXXSourceCompiles)
然后使用check_cxx_compiles_source
测试log
和exp
的可用性。此函数允许我们在真实的源代码编译之前,尝试编译包含所需依赖项的简单代码。结果变量HAVE_LOG
和HAVE_EXP
表示这些依赖项是否可用。
check_cxx_source_compiles("
#include <cmath>
int main() {
double res = std::log(1.0);
return 0;
}" HAVE_LOG)
check_cxx_source_compiles("
#include <cmath>
int main() {
double res = std::exp(1.0);
return 0;
}" HAVE_EXP)
接下来,我们需要将这些 CMake 变量传递给源代码。这样,我们的源代码可以知道哪些资源可用。如果log
和exp
都可用,使用target_compile_definitions
指定HAVE_LOG
和HAVE_EXP
作为PRIVATE
编译定义。
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(MathFunctions PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
由于我们可能使用log
和exp
,需要在mysqrt.cxx
中包含cmath
。
#include <cmath>
如果系统上有log
和exp
,则在mysqrt
函数中使用它们计算平方根。MathFunctions/mysqrt.cxx
中的mysqrt
函数如下所示:
double mysqrt(double x)
{
#if defined(HAVE_LOG) && defined(HAVE_EXP)
return std::exp(std::log(x) * 0.5);
#else
return sqrt(x);
#endif
}
第八步:添加自定义命令和生成文件
假设在本教程中,我们决定永远不使用平台log
和exp
函数,而是希望生成一个预计算值表,以在mysqrt
函数中使用。在本节中,我们将在构建过程中创建该表,然后将该表编译到我们的应用程序中。
首先,删除MathFunctions/CMakeLists.txt
中log
和exp
函数的检查。然后从mysqrt.cxx
中删除对HAVE_LOG
和HAVE_EXP
的检查。同时,可以删除#include <cmath>
。
在MathFunctions
子目录中,已提供了一个名为MakeTable.cxx
的新源文件,用于生成表。
在查看文件后,我们可以看到该表作为有效的 C++ 代码生成,并且输出文件名作为参数传递。
下一步是在MathFunctions/MakeTable.cmake
中创建文件。然后,添加适当的命令以构建MakeTable
可执行文件,并在构建过程中运行它。需要一些命令来完成此操作。
首先,我们添加一个用于生成表的可执行文件。
# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
创建可执行文件后,我们使用target_link_libraries
将tutorial_compiler_flags
添加到我们的可执行文件中。
target_link_libraries(MakeTable tutorial_compiler_flags)
然后,我们添加一个自定义命令,指定如何通过运行MakeTable
生成Table.h
。
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
接下来,我们必须让 CMake 知道mysqrt.cxx
依赖于生成的文件Table.h
。通过将生成的Table.h
添加到库SqrtLibrary
的源文件列表中来实现这一点。
add_library(SqrtLibrary mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)
我们还必须将当前二进制目录添加到包含目录列表中,以便mysqrt.cxx
可以找到并包含Table.h
。
target_include_directories(SqrtLibrary
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
最后一步,我们需要在MathFunctions/CMakeLists.txt
的顶部包括MakeTable.cmake
。
include(${CMAKE_CURRENT_SOURCE_DIR}/MakeTable.cmake)
现在让我们使用生成的表。首先,修改mysqrt.cxx
以包含Table.h
。接下来,我们可以重写mysqrt
函数以使用该表:
#include "Table.h"
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result = x;
// if the value is in the table, use the table
if (x >= 1 and x < 10) {
result = sqrtTable[static_cast<int>(x)];
} else {
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
}
}
return result;
}
运行cmake
可执行文件或cmake-gui
来配置项目,然后使用你选择的构建工具构建项目。
当构建此项目时,它将首先构建MakeTable
可执行文件。然后运行MakeTable
生成Table.h
。最后,将编译包含Table.h
的mysqrt.cxx
以生成MathFunctions
库。
运行Tutorial
可执行文件,并验证它是否正在使用表。
第九步:打包安装程序
接下来,假设我们希望将项目分发给其他人,以便他们可以使用它。我们希望提供二进制和源代码分发,支持各种平台。这与我们之前在安装和测试中所做的安装有所不同,在那里我们安装了从源代码构建的二进制文件。在本示例中,我们将构建支持二进制安装和包管理功能的安装包。为此,我们将使用 CPack 创建平台特定的安装程序。具体来说,我们需要在顶级CMakeLists.txt
文件底部添加几行代码。
# setup installer
include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Tutorial Package")
set(CPACK_PACKAGE_VENDOR "Vendor Name")
set(CPACK_PACKAGE_CONTACT "[email protected]")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "CMake ${CMake_VERSION_MAJOR}.${CMake_VERSION_MINOR}")
set(CPACK_SOURCE_GENERATOR "TGZ")
set(CPACK_GENERATOR "TGZ;ZIP")
include(CPack)
这就是全部内容。我们首先包括InstallRequiredSystemLibraries
。此模块将包括项目在当前平台上所需的任何运行时库。接下来,我们设置一些 CPack 变量,指向存储项目许可证和版本信息的位置。版本信息在本教程中已经设置,而License.txt
已包含在本步骤的顶级源目录中。CPACK_GENERATOR
和CPACK_SOURCE_GENERATOR
变量选择用于二进制和源代码安装的生成器。
最后,我们包括CPack
模块,该模块将使用这些变量和当前系统的其他属性来设置安装程序。
下一步是按通常方式构建项目,然后运行cpack
可执行文件。要构建二进制分发包,请在二进制目录中运行:
cpack
要指定二进制生成器,请使用-G
选项。对于多配置生成,请使用-C
指定配置。例如:
cpack -G ZIP -C Debug
有关可用生成器的列表,请参阅cpack-generators(7)或调用cpack --help
。像 ZIP 这样的归档生成器会创建包含所有已安装文件的压缩归档。
要创建完整源代码树的归档,可以输入:
cpack --config CPackSourceConfig.cmake
或者,运行make package
或右键单击Package
目标并从 IDE 中构建项目。
运行二进制目录中的安装程序。然后运行安装的可执行文件,验证其是否正常工作。
第十步:选择静态或共享库
在本节中,我们将展示如何使用BUILD_SHARED_LIBS
变量来控制add_library
的默认行为,并允许控制如何构建没有显式类型(STATIC
、SHARED
、MODULE
或OBJECT
)的库。
为此,我们需要将BUILD_SHARED_LIBS
添加到顶级CMakeLists.txt
。我们使用option
命令,因为它允许用户选择该值应为ON
还是OFF
。
# set the output directory for the executable
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
# add option for shared or static libraries
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
# configure a header file to pass the version number only
configure_file(
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
接下来,我们需要为静态和共享库指定输出目录。
# we don't need to tinker with the path to run the executable
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
最后,更新MathFunctions/MathFunctions.h
以使用 DLL 导出定义:
#pragma once
#if defined(_WIN32) && defined(EXPORTING_MYMATH)
#define DECLSPEC __declspec(dllexport)
#elif defined(_WIN32)
#define DECLSPEC __declspec(dllimport)
#else
#define DECLSPEC
#endif
DECLSPEC double mysqrt(double x);
到此为止,如果你构建所有内容,你可能会注意到链接失败,因为我们正在将一个不具有位置独立代码的静态库与具有位置独立代码的库组合。解决方案是显式设置 SqrtLibrary 的POSITION_INDEPENDENT_CODE
目标属性为True
,当构建共享库时。
# state that SqrtLibrary needs PIC when the default is shared libraries
set_target_properties(SqrtLibrary PROPERTIES POSITION_INDEPENDENT_CODE True)
定义EXPORTING_MYMATH
,声明我们在 Windows 上使用declspec(dllexport)
。
# define the symbol stating we are using the declspec(dllexport) when
# building on windows
if(BUILD_SHARED_LIBS)
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
endif()
练习:我们修改了MathFunctions.h
以使用 DLL 导出定义。使用 CMake 文档,你能找到一个帮助模块来简化这个过程吗?
要实现这一点,需要在CMakeLists.txt
中包括GenerateExportHeader
模块:
include(GenerateExportHeader)
然后,为MathFunctions
生成导出头文件:
generate_export_header(MathFunctions)
接下来,修改MathFunctions.h
以包含生成的导出头文件:
#include "MathFunctions_export.h"
MATHFUNCTIONS_EXPORT double mysqrt(double x);
此MATHFUNCTIONS_EXPORT
宏将由生成的导出头文件适当地定义,简化了导出声明的管理。
第十一步:添加导出配置
在安装和测试中,我们添加了 CMake 安装库和头文件的能力。在打包安装程序中,我们添加了打包这些信息以便分发的能力。
下一步是添加必要的信息,以便其他 CMake 项目可以使用我们的项目,无论是从构建目录、本地安装还是打包时。
第一步是更新我们的install(TARGETS)
命令,不仅指定DESTINATION
,还指定EXPORT
。EXPORT
关键字生成一个 CMake 文件,其中包含从安装树导入所有在安装命令中列出的目标的代码。因此,让我们显式EXPORT
库MathFunctions
,通过更新MathFunctions/CMakeLists.txt
中的安装命令,如下所示:
# install libs
install(TARGETS MathFunctions
EXPORT MathFunctionsTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin)
现在我们已经在导出MathFunctions
,我们还需要显式安装生成的MathFunctionsTargets.cmake
文件。通过在顶级CMakeLists.txt
底部添加以下内容来完成此操作:
# install the configuration targets
install(EXPORT MathFunctionsTargets
FILE MathFunctionsTargets.cmake
DESTINATION lib/cmake/MathFunctions)
此时,你应该尝试运行 CMake。如果一切设置正确,你会看到 CMake 生成一个错误,如下所示:
Target "MathFunctions" INTERFACE_INCLUDE_DIRECTORIES property contains
path:
"/Users/robert/Documents/CMakeClass/Tutorial/Step11/MathFunctions"
which is prefixed in the source directory.
CMake 告诉你在生成导出信息时,它将导出一个与当前机器紧密相关的路径,这在其他机器上将无效。解决方案是更新MathFunctions
的target_include_directories
,使其理解在构建目录中使用时和安装/打包时需要不同的INTERFACE
位置。这意味着将MathFunctions
的target_include_directories
调用转换为如下所示:
# to find MathFunctions.h, while we don't
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>)
更新后,我们可以重新运行 CMake,验证是否不再有警告。
到此为止,我们已经正确打包目标信息,但仍需要生成一个MathFunctionsConfig.cmake
,以便 CMake find_package
命令能够找到我们的项目。让我们在项目顶级目录中添加一个新文件Config.cmake.in
,内容如下:
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/MathFunctionsTargets.cmake")
然后,正确配置和安装该文件,在顶级CMakeLists.txt
底部添加以下内容:
# install the configuration targets
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/MathFunctions"
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
DESTINATION "lib/cmake/MathFunctions")
接下来,执行configure_package_config_file
。此命令将配置提供的文件,但与标准configure_file
方式有一些具体区别。为了正确利用此功能,输入文件应有一行包含文本@PACKAGE_INIT@
,此外还有所需内容。该变量将被替换为一块代码,该代码将设置值转换为相对路径。这些新值可以通过相同名称但前缀PACKAGE_
来引用。
# install the configuration targets
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
INSTALL_DESTINATION "lib/cmake/MathFunctions"
)
接下来是write_basic_package_version_file
。此命令写入一个文件,供find_package
使用,记录所请求包的版本和兼容性。在这里,我们使用Tutorial_VERSION_*
变量,并表示它与AnyNewerVersion
兼容,这表示此版本或更高版本与所请求版本兼容。
# generate the version file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
COMPATIBILITY AnyNewerVersion
)
最后,设置两个生成文件进行安装:
# install the generated configuration files
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsConfigVersion.cmake"
DESTINATION "lib/cmake/MathFunctions")
到此为止,我们已经生成了一个可重定位的 CMake 配置,用于项目安装或打包后使用。如果我们希望项目也能从构建目录中使用,只需在顶级CMakeLists.txt
底部添加以下内容:
# needs to be after the install(TARGETS) command
export(EXPORT MathFunctionsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/MathFunctionsTargets.cmake")
通过此导出调用,我们现在生成一个MathFunctionsTargets.cmake
,允许构建目录中的配置MathFunctionsConfig.cmake
文件被其他项目使用,而无需安装。
第十二步:打包调试和发布
注意: 此示例适用于单配置生成器,不适用于多配置生成器(如 Visual Studio)。
默认情况下,CMake 的模型是一个构建目录仅包含一个配置,可以是 Debug、Release、MinSizeRel 或 RelWithDebInfo。然而,可以设置 CPack 以捆绑多个构建目录,并构建包含同一项目多个配置的包。
首先,我们要确保调试和发布构建使用不同名称的将要安装的库。让我们使用d
作为调试库的后缀。
在顶级CMakeLists.txt
文件的开头设置CMAKE_DEBUG_POSTFIX
:
project(Tutorial VERSION 1.0)
set(CMAKE_DEBUG_POSTFIX "d")
在 Tutorial 可执行文件上设置DEBUG_POSTFIX
属性:
# add the executable
add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX "d")
让我们也为MathFunctions
库添加版本编号。在MathFunctions/CMakeLists.txt
中,设置VERSION
和SOVERSION
属性:
# setup the version numbering
set_target_properties(MathFunctions PROPERTIES
VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
SOVERSION "${Tutorial_VERSION_MAJOR}")
从Step12
目录,创建debug
和release
子目录。布局如下所示:
- Step12
- debug
- release
现在,我们需要设置调试和发布构建。可以使用CMAKE_BUILD_TYPE
设置配置类型:
cd debug
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build .
cd ../release
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
现在调试和发布构建都已完成,可以使用自定义配置文件将两个构建打包到一个发布包中。在Step12
目录中,创建一个名为MultiCPackConfig.cmake
的文件。在此文件中,首先包括由cmake
可执行文件创建的默认配置文件。
接下来,使用CPACK_INSTALL_CMAKE_PROJECTS
变量指定要安装的项目。在本例中,我们希望安装调试和发布版本。
include(CPackConfig.cmake)
set(CPACK_INSTALL_CMAKE_PROJECTS
"${CMAKE_BINARY_DIR}/debug;MyProject;ALL;/"
"${CMAKE_BINARY_DIR}/release;MyProject;ALL;/")
从Step12
目录运行cpack
,使用--config
选项指定我们的自定义配置文件:
cpack --config MultiCPackConfig.cmake
这将创建一个包含调试和发布构建的包。
结语
恭喜你完成本教程,相信你现在对CMake有了更深的理解,如果要继续深入学习CMake,非常推荐阅读采用CMake构建的开源项目,你将从中学习到更多高级的用法,这里推荐几个不错的采用CMake构建的开源项目。
- https://github.com/Kitware/CMake
- https://github.com/opencv/opencv
- https://github.com/grpc/grpc