第三章:设置你的第一个 CMake 项目
现在我们已经收集了足够的信息,可以开始讨论 CMake 的核心功能:构建项目。在 CMake 中,一个项目包含管理将我们的解决方案带入生活的所有源文件和配置。配置从执行所有检查开始:目标平台是否受支持,是否拥有所有必要的依赖项和工具,以及提供的编译器是否工作并支持所需功能。
完成后,CMake 将为我们的选择构建工具生成一个构建系统并运行它。源文件将与彼此以及它们的依赖项编译和链接,以产生输出工件。
项目可以由一组开发人员内部使用,产生用户可以通过包管理器在其系统上安装的包,或者它们可以用来提供单执行文件安装器。项目还可以在开源存储库中分享,以便用户可以使用 CMake 在他们的机器上编译项目并直接安装它们。
充分利用 CMake 项目将改善开发体验和生成代码的质量,因为我们可以自动化许多单调的任务,例如在构建后运行测试,检查代码覆盖率,格式化代码,以及使用 linters 和其他工具检查源代码。
为了充分发挥 CMake 项目的力量,我们首先要了解一些关键决策——这些决策是如何正确配置整个项目以及如何划分项目和设置源代码树,以便所有文件都整齐地组织在正确的目录中。
然后,我们将学习如何查询项目构建的环境——例如,它的架构是什么?有哪些工具可用?它们支持哪些功能?并使用的是哪种语言标准?最后,我们将学习如何编译一个测试**C++**文件,以验证所选编译器是否满足我们项目中设定的标准要求。
在本章中,我们将涵盖以下主要主题:
- 基本指令和命令
- 如何划分你的项目
- 思考项目结构
- 作用域环境
- 配置工具链
- 禁用源代码内构建
技术要求
你可以在 GitHub 上找到本章中出现的代码文件:github.com/PacktPublishing/Modern-CMake-for-Cpp/tree/main/examples/chapter03
。
要构建本书提供的示例,始终使用推荐命令:
cmake -B <build tree> -S <source tree> cmake --build <build tree>
务必将占位符和
替换为合适的路径。作为提醒:build tree是目标/输出目录的路径,source tree是源代码所在的路径。
基本指令和命令
在*第一章**,CMake 的初步步骤中,我们已经看了一个简单的项目定义。让我们回顾一下。这是一个包含CMakeLists.txt
文件的目录,其中包含了几条配置语言处理器的命令:
chapter01/01-hello/CMakeLists.txt:CMake 语言中的 Hello world
cmake_minimum_required(VERSION 3.20) project(Hello) add_executable(Hello hello.cpp)
在同一章节中,在项目文件部分,我们了解了一些基本命令。让我们深入解释一下。
指定最小的 CMake 版本——cmake_minimum_required()
这并不是一个严格的项目特定命令,因为它也应该用于脚本,但我们在这里重复它是因为它非常重要。正如你所知,cmake_minimum_required()
将检查系统是否有正确的 CMake 版本,但隐式地,它还会调用另一个命令,cmake_policy(VERSION)
,这将告诉 CMake 对于这个项目应该使用哪些正确的策略。这些策略是什么?
在 CMake 发展的过去 20 年中,随着 CMake 及其支持的语言的发展,命令的行为发生了许多变化。为了保持语法简洁明了,CMake 团队决定引入策略来反映这些变化。每当引入一个向后不兼容的更改时,它都会附带一个策略,启用新的行为。
通过调用cmake_minimum_required()
,我们告诉 CMake 需要应用到提供参数中的版本的策略。当 CMake 通过新的策略升级时,我们不需要担心它们会破坏我们的项目,因为新策略不会被启用。如果我们用最新版本测试项目并且结果令我们满意,我们可以把更新后的项目发送给我们的用户。
策略可以影响 CMake 的每一个方面,包括其他重要命令如project()
。因此,很重要的一点是,你要在CMakeLists.txt
文件开始时设定你正在使用的版本。否则,你会收到警告和错误。
每个版本都引入了许多策略——除非你正在将旧项目升级到最新的 CMake 版本遇到问题,否则描述它们并没有真正的价值。在这种情况下,请参考官方文档中的策略:cmake.org/cmake/help/latest/manual/cmake-policies.7.html
。
定义语言和元数据——project()
从技术上讲,CMake 不需要project()
命令。任何包含CMakeLists.txt
文件的目录都会以项目模式被解析。CMake 隐式地在文件顶部添加了这个命令。但我们已经知道我们需要首先指定最小版本,所以最好不要忘记调用project()
。我们可以使用它的两种形式之一:
project(<PROJECT-NAME> [<language-name>...]) project(<PROJECT-NAME> [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]] [DESCRIPTION <project-description-string>] [HOMEPAGE_URL <url-string>] [LANGUAGES <language-name>...])
我们需要指定,但其他参数是可选的。调用这个命令将隐式地设置以下变量:
PROJECT_NAME
CMAKE_PROJECT_NAME
(仅在最顶层的CMakeLists.txt
中)PROJECT_SOURCE_DIR
、_SOURCE_DIR
PROJECT_BINARY_DIR
、_BINARY_DIR
支持哪些语言?很多。以下是您可以用来配置项目的语言关键词列表:C
、CXX
(C++)、CUDA
、OBJC
(Objective-C)、OBJCXX
(Objective C++)、Fortran
、ISPC
、ASM
,以及CSharp
(C#)和Java
。
CMake 默认支持 C 和 C++,所以您可能只想明确指定CXX
用于您的 C++项目。为什么?project()
命令将检测和测试您选择的可用编译器,所以选择正确的编译器将使您在配置阶段节省时间,通过跳过任何对未使用语言的检查。
指定VERSION
将使以下变量可用:
PROJECT_VERSION
、_VERSION
CMAKE_PROJECT_VERSION
(仅在顶级CMakeLists.txt
中)PROJECT_VERSION_MAJOR
、_VERSION_MAJOR
PROJECT_VERSION_MINOR
、_VERSION_MINOR
PROJECT_VERSION_PATCH
、_VERSION_PATCH
PROJECT_VERSION_TWEAK
、_VERSION_TWEAK
这些变量将有助于配置软件包,或将版本传递给编译文件,以便在最终可执行文件中可用。
遵循这一原则,我们可以设置DESCRIPTION
和HOMEPAGE_URL
,这将以相同的方式设置变量。
CMake 还允许通过enable_language()
指定使用的语言,这将不会创建任何元数据变量。
这些命令将允许我们创建一个基本的列表文件并初始化一个空项目。现在,我们可以开始添加东西来构建。对于迄今为止我们所用的例子中的微小单文件项目,结构确实不太重要。但是当代码量增加时会发生什么?
划分您的项目
随着我们的解决方案在行数和文件数量上的增长,我们逐渐意识到不可避免的事情即将发生:要么我们开始分区项目,要么我们淹没在代码行和众多文件中。我们可以用两种方法来解决这个问题:通过分区 CMake 代码,或将源文件移动到子目录中。在这两种情况下,我们都旨在遵循一个称为关注点分离的设计原则。简单来说,就是将代码分成块,将具有紧密相关功能的代码分组,同时将其他代码片段解耦,以创建强大的边界。
在第一章《CMake 的初步步骤》中讨论列表文件时,我们稍微提到了分区 CMake 代码。我们讨论了include()
命令,该命令允许 CMake 执行来自外部文件的代码。调用include()
不会引入任何未在文件中定义的作用域或隔离(如果包含的文件包含函数,那么在调用时它们的作用域将会被正确处理)。
这种方法有助于关注点的分离,但效果有限——专用代码被提取到单独的文件中,甚至可以跨不相关的项目共享,但如果作者不小心,它仍然可能会用其内部逻辑污染全局变量作用域。编程中的一个古老真理是,即使是最糟糕的机制也比最好的意图好。我们将在后面学习如何解决这个问题,但现在,让我们将重点转移到源代码上。
让我们考虑一个支持小型汽车租赁公司的软件示例——它将有很多源文件,定义软件的不同方面:管理客户、车辆、停车位、长期合同、维护记录、员工记录等等。如果我们把这些文件都放在一个单一的目录中,找到任何东西都将是一场噩梦。因此,我们在项目的主目录中创建多个目录,并将相关文件移入其中。我们的CMakeLists.txt
文件可能看起来像这样:
第三章/01-partition/CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0) project(Rental CXX) add_executable(Rental main.cpp cars/car.cpp # more files in other directories )
这很好,但正如您所看到的,我们仍然在顶层文件中包含了嵌套目录的源文件列表!为了增加关注点的分离,我们可以将源文件列表放在另一个列表文件中,并使用前述的include()
命令和cars_sources
变量,像这样:
第三章/02-include/CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0) project(Rental CXX) include(cars/cars.cmake) add_executable(Rental main.cpp ${cars_sources} # ${more variables} )
新的嵌套列表文件将包含以下源文件:
第三章/02-include/cars/cars.cmake
set(cars_sources cars/car.cpp # cars/car_maintenance.cpp )
CMake 将有效地在add_executable
相同的范围内设置cars_sources
,用所有文件填充该变量。这个解决方案可行,但它有几个缺点:
- 嵌套目录中的变量将污染顶层作用域(反之亦然):
在简单的示例中这不是问题,但在更复杂的多级树结构中,存在多个变量在过程中使用,它可能很快变得难以调试。
- 所有目录将共享相同的配置:
这个问题在项目随时间成熟的过程中显示了其真实面目。由于没有任何粒度,我们必须对每个翻译单元一视同仁,无法指定不同的编译标志,为代码的某些部分选择更新的语言版本,以及在代码的特定区域静默警告。一切都是全局的,这意味着我们需要同时对所有源文件引入更改。
- 存在共享编译触发器:
配置的任何更改都意味着所有文件都将需要重新编译,即使更改对其中一些文件来说毫无意义。
- 所有路径都是相对于顶层而言的:
请注意,在cars.cmake
中,我们不得不提供cars/car.cpp
文件的全路径。这导致很多重复的文本破坏了可读性,违反了不要重复自己(DRY)的清洁编码原则。重命名目录将是一场斗争。
另一种方法是使用add_subdirectory()
命令,它引入了变量作用域等。让我们来看看。
作用域子目录
常见的做法是按照文件系统的自然结构来组织项目,其中嵌套目录表示应用程序的离散元素:业务逻辑、GUI、API 和报告,最后,单独的目录包含测试、外部依赖、脚本和文档。为了支持这个概念,CMake 提供了以下命令:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
如前所述,这将为我们的构建添加一个源目录。可选地,我们可以提供一个路径,其中将生成文件(binary_dir
)。EXCLUDE_FROM_ALL
关键字将禁用子目录中定义的目标的默认构建(我们将在下一章讨论目标)。这对于分离不需要核心功能的项目的部分(例如示例和扩展)可能很有用。
此命令将在source_dir
路径(相对于当前目录评估)中寻找一个CMakeLists.txt
文件。然后在该目录作用域中解析该文件,意味着前面方法中提到的所有缺陷都不存在:
- 变量更改被限制在嵌套作用域内。
- 您可以自由地以任何喜欢的方式配置嵌套的艺术品。
- 更改嵌套的
CMakeLists.txt
文件不需要构建无关的目标。 - 路径仅限于目录本地,如果需要,它们甚至可以添加到父级包含路径中。
让我们来看一个使用add_subdirectory()
的项目:
chapter03/03-add_subdirectory# tree -A . ├── CMakeLists.txt ├── cars │ ├── CMakeLists.txt │ ├── car.cpp │ └── car.h └── main.cpp
这里,我们有两个CMakeLists.txt
文件。顶层文件将使用嵌套目录cars
:
chapter03/02-add_subdirectory/CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0) project(Rental CXX) add_executable(Rental main.cpp) add_subdirectory(cars) target_link_libraries(Rental PRIVATE cars)
最后一行用于将来自cars
目录的艺术品链接到Rental
可执行文件。这是一个目标特定的命令,我们将在下一章深入讨论。让我们看看嵌套列表文件看起来如何:
chapter03/02-add_subdirectory/cars/CMakeLists.txt
add_library(cars OBJECT car.cpp # car_maintenance.cpp ) target_include_directories(cars PUBLIC .)
正如你所看到的,我使用add_library()
来生成一个全局可见的目标cars
,并使用target_include_directories()
将其添加到其公共包含目录中。这允许main.cpp
不提供相对路径即可包含cars.h
文件:
#include "car.h"
我们可以在嵌套列表文件中看到add_library()
命令,所以我们是在这个例子中开始使用库了吗?实际上,不是。由于我们使用了OBJECT
关键字,我们表明我们只对生成对象文件感兴趣(与上一个例子完全一样)。我们只是将它们分组在一个逻辑目标(cars
)下。您可能已经对目标有一个大致的了解。暂时保留那个想法——我们马上就会讨论到。
嵌套项目
在上一节中,我们简要提到了 add_subdirectory()
命令中使用的 EXCLUDE_FROM_ALL
参数。CMake 文档建议,如果我们有这样的部分存在于源树中,它们在自己的 CMakeLists.txt
文件中应该有自己的 project()
命令,这样它们就可以生成自己的构建系统,并且可以独立构建。
还有其他哪些场景会用到这个功能呢?当然。例如,一个场景是当你在一个 CI/CD 管道中构建多个 C++ 项目(也许是在构建框架或一系列库时)。另一种情况可能是,你可能正在从遗留解决方案(如 GNU Make)中移植构建系统,该解决方案使用普通的 makefiles。在这种情况下,你可能需要一个选项,逐步将事物分解成更独立的单元——可能要把它们放在单独的构建管道中,或者只是为了在一个更小的范围内工作,这可以被如 CLion 这样的 IDE 加载。
你可以通过在嵌套目录中的 listfile 添加 project()
命令来实现。只是不要忘记用 cmake_minimum_required()
它前缀。
由于支持项目嵌套,我们能否 somehow 连接并排构建的相关项目?
外部项目
技术上可以从一个项目到达另一个项目,CMake 也在一定程度上支持这一点。甚至还有一个 load_cache()
命令,允许你从另一个项目的缓存中加载值。话说回来,这并不是一个常规或推荐的使用场景,它会导致循环依赖和项目耦合的问题。最好避免使用这个命令,并做出决定:我们的相关项目应该嵌套、通过库连接,还是合并成一个项目?
这些是我们可用的分区工具:包括 listfiles、添加子目录和嵌套项目。但我们应该如何使用它们,使我们的项目保持可维护性、易于导航和扩展?为了实现这一点,我们需要一个定义良好的项目结构。
思考项目结构
众所周知,随着项目增长,在 listfiles 和源代码中找到东西变得越来越难。因此,从一开始就保持项目卫生非常重要。
想象一个场景,你需要交付一些重要、时间敏感的更改,它们不适合你的项目中的两个目录之一。现在,你需要快速推送一个 cleanup commit ,引入更多的目录和另一层文件层次结构,以便你的更改有一个好的地方放置。或者(更糟糕的是),你决定只是把它们推到任何地方,并创建一个票证稍后处理问题。
在整个一年中,这些工单积累,技术债务增长,维护代码的成本也在增加。当需要快速修复现场系统的严重错误,且不熟悉代码库的人需要引入他们的更改时,这变得极其麻烦。
所以,我们需要一个好的项目结构。但这意味着什么?我们可以从软件开发的其他领域(例如,系统设计)借鉴几条规则。项目应该具有以下特征:
- 它应该易于导航和扩展。
- 它应该是自包含的——例如,项目特定的文件应该在项目目录中,而不应该在其他地方。
- 抽象层次应该通过可执行文件和二进制文件来表达。
没有一种单一公认的解决方案,但在网上可用的众多项目结构模板中,我建议遵循这个模板,因为它简单且非常可扩展:
图 3.1 – 项目结构示例
这个项目概述了以下组件的目录结构:
cmake
:包括宏和函数、find_modules 以及一次性脚本src
:将存储我们的二进制文件和库的源代码doc
:用于构建文档extern
:我们从中源代码构建的外部项目的配置test
:包含自动化测试的代码
在这种结构中,CMakeLists.txt
文件应该存在于以下目录中:顶级项目目录、src
、doc
、extern
和 test
。主列表文件不应该声明任何自身的构建步骤,而是应该使用 add_subdirectory()
命令来执行嵌套目录中的所有列表文件。如果有需要,这些还可以将这项工作委托给更深层次的目录。
注意
一些开发者建议将可执行文件与库分开,创建两个顶级目录(src
和 lib
),而不是一个。CMake 将这两种工件同等对待,在这种层次上进行分离并不真正重要。
在 src
目录中有多个目录对于大型项目来说非常有用。但如果你只是构建一个可执行文件或库,你可以跳过它们,并将源文件直接存储在 src
中。无论如何,记得在那里添加一个 CMakeLists.txt
文件,并执行任何嵌套的列表文件。
你的目标文件树可能看起来是这样的:
图 3.2 – 可执行文件的目录结构
我们在app1
目录的根目录中看到一个CMakeLists.txt
文件——它将配置关键的项目设置并包括嵌套目录中的所有列表文件。src
目录包含另一个CMakeLists.txt
文件以及.cpp
实现文件:两个类和带有可执行程序入口点的主文件。CMakeLists.txt
文件应该定义一个目标,使用这些源文件构建一个可执行文件——我们将在下一章学习如何做到这一点。
我们的头文件放在include
目录中——这些文件被.cpp
实现文件用来声明来自其他 C++翻译单元的符号。
我们有一个test
目录来存储自动化测试的源代码,我们还有lib3
,它包含了一个只针对这个可执行文件的库(项目其他地方使用的库或导出到项目外的库应该放在src
目录中)。
这个结构非常具有表现力,并允许项目的许多扩展。随着我们不断添加更多的类,我们可以很容易地将它们分组到库中,以加快编译过程。让我们看看库的样子:
](https://gitee.com/OpenDocCN/freelearn-c-cpp-pt2-zh/raw/master/docs/mdn-cmk-cpp/img/Figure_3.4.jpg)
图 3.3 – 库的目录结构
结果证明,库遵循与可执行文件相同的结构,只有一个小的区别:在include
目录中有一个可选的lib3
目录。只有当我们从项目中外部使用库时,这个目录才应该存在。它提供了其他项目在编译时将消耗的公共头文件。我们将在第五章*,使用 CMake 编译 C++源代码中回到这个主题,构建我们自己的库。
所以,我们已经讨论了文件是如何布局在目录结构中的。现在,是时候看看单独的CMakeFiles.txt
文件是如何组合成一个项目的,以及它们在大场景中的作用。
图 3.4 – CMake 如何将列表文件合并到一个项目中
在图 3.4中,每个框代表了一个位于特定目录中的CMakeLists.txt
文件列表,而草体字中的标签代表了每个文件执行的动作(从上到下)。让我们从 CMake 的角度再次分析这个项目:
- 执行从项目的根开始——也就是说,从源树中的一个列表文件开始。这个文件将设置所需的最小 CMake 版本和相应的策略,设置项目名称,支持的语言,全局变量,并包括来自
cmake
目录的文件,以便它们的内容在全局范围内可用。 - 下一步是进入
src
目录的作用域,通过调用add_subdirectory(src bin)
命令(我们想将编译后的工件放在/bin
中,而不是/src
)。 - CMake 读取
src/CMakeLists.txt
文件并发现它的唯一目的是添加四个嵌套子目录:app1
、app2
、lib1
和lib2
。 - CMake 进入了
app1
的变量作用域,并了解了一个嵌套库lib3
,该库拥有自己的CMakeLists.txt
文件;然后进入了lib3
的作用域。 lib3
库添加了一个与名称相同的静态库目标。CMake 返回app1
的父作用域。app1
子目录添加了一个依赖于lib3
的可执行文件。CMake 返回src
的父作用域。- CMake 将继续进入剩余的嵌套作用域并执行它们的列表文件,直到所有
add_subdirectory()
调用完成。 - CMake 返回顶层作用域并执行剩余的三个命令:
add_subdirectory(doc)
、add_subdirectory(extern)
和add_subdirectory(test)
。每次,CMake 进入新的作用域并从相应的列表文件中执行命令。 - 所有目标都被收集并检查其正确性。CMake 现在拥有生成构建系统的所有必要信息。
我们需要记住,前面的步骤是按照我们编写命令的准确顺序发生的。有时这很重要,而其他时候,则不那么重要。我们在下一章解决这个问题。
所以,创建包含项目所有元素的目录的正确时机是什么时候呢?我们应该从一开始就创建未来所需的一切并保持目录空空如也,还是等到我们实际上需要放入其自己类别的文件时再做呢?这是一个选择——我们可以遵循极端编程规则 YAGNI(你不需要它),或者我们可以尝试使我们的项目具有未来性,并为即将到来的新开发者打下良好的基础。
尝试在這些方法之间寻求良好的平衡——如果你怀疑你的项目可能有一天需要一个 extern
目录,那么添加它(你可能需要创建一个空白的 .keep
文件以将目录检入仓库)。为了帮助其他人知道将他们的外部依赖项放在哪里,创建一个 readme
文件,为未来踏上这条道路的 less 经验丰富的程序员铺平道路。你自己可能已经注意到了这一点:开发者不愿意创建目录,尤其是在项目的根目录中。如果我们提供一个好的项目结构,人们倾向于遵循它。
一些项目可以在几乎所有的环境中构建,而其他项目则非常关注它们的特定环境。顶层列表文件是评估如何进行项目的最佳位置,取决于有什么可用。让我们来看看如何做到这一点。
环境作用域
CMake 提供了多种查询环境的方法,使用CMAKE_
变量、ENV
变量和特殊命令。例如,收集的信息可以用来支持跨平台脚本。这些机制允许我们避免使用可能不易移植或在不同环境中命名不同的平台特定 shell 命令。
对于性能关键的应用程序,了解目标平台的所有特性(例如,指令集、CPU 核心数等)将很有用。然后可以将这些信息传递给编译后的二进制文件,以便它们可以被完美地调整(我们将在下一章学习如何做到这一点)。看看 CMake 中 native 提供了哪些信息。
发现操作系统
有许多场合知道目标操作系统是有用的。即使是像文件系统这样平凡的东西,在 Windows 和 Unix 之间也有很大的不同,比如大小写敏感性、文件路径结构、扩展名的存在、权限等。在一个系统上大多数命令在另一个系统上可能不可用,或者它们可能命名不同(即使只是一个字母——例如,ifconfig
和ipconfig
命令)。
如果你需要用一个 CMake 脚本支持多个目标操作系统,只需检查CMAKE_SYSTEM_NAME
变量,以便你可以相应地采取行动。这是一个简单的例子:
if(CMAKE_SYSTEM_NAME STREQUAL "Linux") message(STATUS "Doing things the usual way") elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") message(STATUS "Thinking differently") elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") message(STATUS "I'm supported here too.") elseif(CMAKE_SYSTEM_NAME STREQUAL "AIX") message(STATUS "I buy mainframes.") else() message(STATUS "This is ${CMAKE_SYSTEM_NAME} speaking.") endif()
如果需要,有一个包含操作系统版本的变量:CMAKE_SYSTEM_VERSION
。然而,我的建议是尽量使你的解决方案尽可能系统无关,并使用内置的 CMake 跨平台功能。特别是在操作文件系统时,你应该使用附录部分描述的file()
命令。
交叉编译——什么是宿主和目标系统?
在一台机器上编译代码,然后在另一台机器上运行,这被称为交叉编译。你可以(使用正确的工具集)在 Windows 机器上运行 CMake 来为 Android 编译应用程序。交叉编译不在本书的范围内,但了解它如何影响 CMake 的某些部分是非常重要的。
允许交叉编译的必要步骤之一是将CMAKE_SYSTEM_NAME
和CMAKE_SYSTEM_VERSION
变量设置为目标操作系统(CMake 文档中将其称为目标系统)的适当值。用于执行构建的操作系统称为宿主系统。
无论配置如何,宿主系统上的信息总是可以通过带有HOST
关键词的变量访问:CMAKE_HOST_SYSTEM
、CMAKE_HOST_SYSTEM_NAME
、CMAKE_HOST_SYSTEM_PROCESSOR
和CMAKE_HOST_SYSTEM_VERSION
。
还有一些变量在其名称中带有HOST
关键字,所以只需记住它们明确地引用了宿主系统。否则,所有变量都引用目标系统(通常是宿主系统,除非我们进行交叉编译)。
如果你对交叉编译感兴趣,我建议参考 CMake 文档在cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html
。
缩写变量
CMake 将预定义一些变量,提供关于宿主和目标系统的信息。如果使用特定的系统,相应的变量将被设置为非假值(即1
或true
):
ANDROID
,APPLE
,CYGWIN
,UNIX
,IOS
,WIN32
,WINCE
,WINDOWS_PHONE
CMAKE_HOST_APPLE
,CMAKE_HOST_SOLARIS
,CMAKE_HOST_UNIX
,CMAKE_HOST_WIN32
WIN32
和CMAKE_HOST_WIN32
变量对于 32 位和 64 位的 Windows 和 MSYS 版本以及为了遗留原因而保持为真。另外,UNIX
对于 Linux、macOS 和 Cygwin 也为真。
面向 C++ 的现代 CMake 教程(二)(2)https://developer.aliyun.com/article/1525489