CMake 实战入门教程

文章目录

前言

之前学习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。

image.png

接下来正式进入教程,祝大家有一个不错的收获~

第一步:基础起点

本章节将介绍一些 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 1TODO 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_STANDARDCMAKE_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>:缓存变量的类型,如 BOOLSTRINGPATH 等。
    • <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_MAJORTutorial_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_directoriestarget_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.hmysqrt.h。它们各自的源文件MathFunctions.cxxmysqrt.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]:初始值,可以是 ONOFF,默认为 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>...:预处理器宏定义,可以是 NAMENAME=value 的形式。

示例:

target_compile_definitions(MyApp PRIVATE VERSION_MAJOR=1)

在这个例子中,MyApp 目标将使用 VERSION_MAJOR=1 作为编译定义。

编辑的文件

  • MathFunctions/CMakeLists.txt
  • MathFunctions/MathFunctions.cxx

入门

从练习1的结果文件开始。完成TODO 7TODO 14

首先在MathFunctions/CMakeLists.txt中使用option命令创建变量USE_MYMATH。在同一文件中,使用该选项向MathFunctions库传递编译定义。

然后,更新MathFunctions.cxx以基于USE_MYMATH重定向编译。

最后,在USE_MYMATH关闭时阻止编译mysqrt.cxx,通过在MathFunctions/CMakeLists.txtUSE_MYMATH块中使其成为单独的库。

构建和运行

由于我们已经从练习1配置了构建目录,只需调用以下命令重新构建:

cd ../Step2_build
cmake --build .

接下来,在几个数字上运行Tutorial可执行文件,以验证它是否仍然正确。

现在让我们将USE_MYMATH的值更新为OFF。最简单的方法是使用cmake-guiccmake,如果你在终端中。或者,作为替代方案,如果你想从命令行更改选项,请尝试:

cmake ../Step2 -DUSE_MYMATH=OFF

现在,使用以下命令重新构建代码:

cmake --build .

然后,再次运行可执行文件以确保在USE_MYMATH设置为OFF时它仍然有效。哪种函数给出的结果更好,sqrt还是mysqrt

解决方案

第一步是在MathFunctions/CMakeLists.txt中添加一个选项。此选项将在cmake-guiccmake中显示,默认值为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_MYMATHON时,编译定义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_MYMATHOFFmysqrt.cxx将不会被使用,但它仍将被编译,因为MathFunctions目标将mysqrt.cxx列为源文件。

有几种方法可以解决这个问题。第一种方法是使用target_sourcesUSE_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_directoriesadd_subdirectory 命令都使用 CMAKE_CURRENT_SOURCE_DIR 变量来指定相对于当前 CMakeLists.txt 文件目录的路径。

这个变量在处理复杂项目结构时特别有用,因为它允许你编写相对独立的 CMakeLists.txt 文件,而无需硬编码绝对路径。

编辑的文件

  • MathFunctions/CMakeLists.txt
  • CMakeLists.txt

入门

在本练习中,我们将重构代码,使其使用现代 CMake 方法。我们将让库定义其自己的使用要求,以便根据需要传递给其他目标。在本例中,MathFunctions将自己指定任何需要的包含目录。然后,消费目标Tutorial只需链接到MathFunctions,无需担心任何额外的包含目录。

起始源代码位于Step3目录中。在本练习中,完成TODO 1TODO 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_11cxx_std_14 等。

示例:

target_compile_features(MyApp PUBLIC cxx_std_14)

在这个例子中,MyApp 目标将使用 C++14 标准进行编译。

编辑的文件

  • CMakeLists.txt
  • MathFunctions/CMakeLists.txt

入门

在本练习中,我们将重构代码,使用INTERFACE库来指定 C++ 标准。

从我们在 Step3 练习1结束时留下的代码开始。你需要完成TODO 4TODO 7

首先,编辑顶级CMakeLists.txt文件。构建一个名为tutorial_compiler_flagsINTERFACE库目标,并指定cxx_std_11作为目标编译特性。

修改CMakeLists.txtMathFunctions/CMakeLists.txt,使所有目标都有一个调用target_link_libraries指向tutorial_compiler_flags

构建和运行

由于我们已经从练习1配置了构建目录,只需调用以下命令重新构建:

cd Step3_build
cmake --build .

接下来,使用新构建的Tutorial并验证其是否按预期工作。

解决方案

让我们更新上一步的代码,使用接口库设置我们的 C++ 要求。

首先,我们需要删除两个set调用,分别是CMAKE_CXX_STANDARDCMAKE_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)

最后,使用接口库设置好后,我们需要将可执行文件TutorialSqrtLibrary库和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_LIBRARIESINCLUDE_DIRECTORIESCOMPILE_DEFINITIONS等。它们也可以在使用这些属性的命令时使用,例如target_link_librariestarget_include_directoriestarget_compile_definitions等。

生成器表达式可用于启用条件链接、条件定义编译、条件包含目录等。这些条件可以基于构建配置、目标属性、平台信息或任何其他可查询的信息。

生成器表达式包括逻辑表达式、信息表达式和输出表达式。

逻辑表达式用于创建条件输出。基本表达式是01表达式。$<0:…>生成空字符串,$<1:…>生成的内容。它们也可以嵌套。

练习1 - 使用生成器表达式添加编译器警告标志

生成器表达式的一个常见用法是有条件地添加编译器标志,例如用于语言级别或警告的标志。一个不错的模式是将此信息与一个INTERFACE目标相关联,允许此信息传播。

目标

在构建时添加编译器警告标志,但不用于已安装的版本。

有用的资源

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>:当 string1string2 相等时为真。
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 1TODO 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_cxxmsvc_cxx中,如下所示:

set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,GNU,Clang,AppleClang>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")

接下来,添加我们项目所需的编译器警告标志。使用变量gcc_like_cxxmsvc_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 1TODO 4

首先,更新MathFunctions/CMakeLists.txt以将MathFunctionstutorial_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,我们希望将库和头文件安装到libinclude目录。
  • 对于Tutorial可执行文件,我们希望将可执行文件和配置头文件安装到bininclude目录。

因此,在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 5TODO 9

首先,我们需要启用测试。接下来,开始使用add_test向项目添加测试。我们将添加3个简单的测试,然后你可以根据需要添加更多的测试。

构建和运行

导航到构建目录并重新构建应用程序。然后,运行ctest可执行文件:ctest -Nctest -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 结果。

有用的资源

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)

第七步:添加系统检测

让我们考虑向项目中添加一些依赖于目标平台可能没有的功能代码。例如,我们将添加一些代码,这些代码取决于目标平台是否具有logexp函数。当然,几乎每个平台都有这些函数,但对于本教程,假设它们并不常见。

练习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 1TODO 5

首先编辑MathFunctions/CMakeLists.txt。包括CheckCXXSourceCompiles模块。然后,使用check_cxx_source_compiles确定是否可以从cmath中使用logexp。如果它们可用,使用target_compile_definitions指定HAVE_LOGHAVE_EXP作为编译定义。

MathFunctions/mysqrt.cxx中,包括cmath。然后,如果系统具有logexp,使用它们计算平方根。

构建和运行

创建一个名为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测试logexp的可用性。此函数允许我们在真实的源代码编译之前,尝试编译包含所需依赖项的简单代码。结果变量HAVE_LOGHAVE_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 变量传递给源代码。这样,我们的源代码可以知道哪些资源可用。如果logexp都可用,使用target_compile_definitions指定HAVE_LOGHAVE_EXP作为PRIVATE编译定义。

if(HAVE_LOG AND HAVE_EXP)
  target_compile_definitions(MathFunctions PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()

由于我们可能使用logexp,需要在mysqrt.cxx中包含cmath

#include <cmath>

如果系统上有logexp,则在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
}

第八步:添加自定义命令和生成文件

假设在本教程中,我们决定永远不使用平台logexp函数,而是希望生成一个预计算值表,以在mysqrt函数中使用。在本节中,我们将在构建过程中创建该表,然后将该表编译到我们的应用程序中。

首先,删除MathFunctions/CMakeLists.txtlogexp函数的检查。然后从mysqrt.cxx中删除对HAVE_LOGHAVE_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_librariestutorial_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.hmysqrt.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 "contact@example.com")
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_GENERATORCPACK_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的默认行为,并允许控制如何构建没有显式类型(STATICSHAREDMODULEOBJECT)的库。

为此,我们需要将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,还指定EXPORTEXPORT关键字生成一个 CMake 文件,其中包含从安装树导入所有在安装命令中列出的目标的代码。因此,让我们显式EXPORTMathFunctions,通过更新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 告诉你在生成导出信息时,它将导出一个与当前机器紧密相关的路径,这在其他机器上将无效。解决方案是更新MathFunctionstarget_include_directories,使其理解在构建目录中使用时和安装/打包时需要不同的INTERFACE位置。这意味着将MathFunctionstarget_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中,设置VERSIONSOVERSION属性:

# setup the version numbering
set_target_properties(MathFunctions PROPERTIES
  VERSION "${Tutorial_VERSION_MAJOR}.${Tutorial_VERSION_MINOR}"
  SOVERSION "${Tutorial_VERSION_MAJOR}")

Step12目录,创建debugrelease子目录。布局如下所示:

- 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
  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CMake是一个跨平台的开源构建工具,它能够自动生成各种不同平台的构建脚本,简化了项目的构建过程。下面是一个300字快速入门CMake实战教程。 首先,安装CMake并确保它被正确添加到系统的环境变量中。 接下来,在你的项目根目录下创建一个CMakeLists.txt文件,这个文件是CMake的配置文件,用来指导CMake生成项目的构建脚本。在CMakeLists.txt文件中,首先指定项目的名称和最低版本要求,例如: ``` cmake_minimum_required(VERSION 3.10) project(MyProject) ``` 然后,添加你的项目的源文件和头文件,使用add_executable或add_library命令指定它们的路径,例如: ``` add_executable(MyApp main.cpp foo.cpp bar.cpp) ``` 接下来,你可以添加依赖库,使用target_link_libraries命令指定它们的路径,例如: ``` target_link_libraries(MyApp ${CMAKE_DL_LIBS}) ``` 此外,你还可以指定编译选项,例如: ``` set(CMAKE_CXX_FLAGS "-std=c++11 -Wall") ``` 最后,你可以通过命令行执行cmake命令来生成构建脚本,并执行构建过程,例如: ``` mkdir build cd build cmake .. make ``` 在build目录中,你会找到生成的构建脚本及其生成的可执行文件(或库文件)。 通过这个简单的实战教程,你可以快速入门CMake,并且开始使用它来管理你的项目的构建过程。当然,在实际应用中,你还可以进行更多高级配置,例如添加条件编译、安装目标等。希望这个简短的回答能够给你一个基本的CMake入门指导。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值