CMake-Best-Practices 第1章学习笔记
1. 启用 CMake
- CMake 简介
- 安装 CMake
- CMake 的构建过程
- 书写 CMake 文件
- 不同工具链和构建配置
1.1 CMake 简介
CMake 开源,并且可以在许多平台上使用,还与编译器无关,从而成为构建和分发跨平台软件的强大工具。这些特性使它成为对软件极具价值的工具——可以自动化构建,以及内置的代码质量检验。
CMake 由三个命令行工具组成:
- cmake: CMake 本身,用于生成构建指令
- ctest: CMake 的测试程序,用于检测和运行测试
- cpack: CMake 的打包工具,将软件打包成方便的安装程序,如 deb、 RPM 和自解压缩的安装程序
还有两种交互工具:
- cmake-gui: 配置项目的图形界面
- ccmake: 配置 CMake 的交互式终端界面
与常规构建系统相比, CMake 有很多优势。首先是跨平台方面,可以更容易地为各种编译器和平台创建构建指令,而不需要深入了解各自构建系统的细节。
然后, CMake 能够发现系统库和依赖关系,这大大减轻了查找正确库的烦恼。还有就是 CMake 与包管理器 (如 Conan 和 vcpkg) 集成得很好。
1.2 安装 CMake
CMake 可以从 https://cmake.org/download/ 免费下载。既可以下载预编译的二进制文件,也可以下载源代码。
在 Ubuntu 中安装 CMake 非常简单,直接使用 apt
命令即可:
sudo apt install cmake
1.3 构建第一个工程
simple_executable – 使用 CMake 构建简单的可执行程序
$ cd ../chapter1
$ cmake -S . -B build/
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.7s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fyang/code/cmake-tutorial/CMake-Best-Practices/chapter1/build
$ cmake --build ./build/
[ 50%] Building CXX object simple_executable/CMakeFiles/chapter1.dir/src/main.cpp.o
[100%] Linking CXX executable chapter1
[100%] Built target chapter1
$ ./build/simple_executable/chapter1
Hello World!
这将产生名为 chapter1
的可执行文件,在控制台上输出 Hello World!
- 示例 CMake 项目的文件结构在构建前是这样的:
.
├── CMakeLists.txt
└── src
└── main.cpp
除了源代码的文件夹外,还有一个名为 CMakeLists.txt 的文件。这个文件包含 CMake 关于如何创建项目的构建说明,以及如何构建的说明。
每个 CMake 项目在项目的根目录下都有一个 CMakeLists.txt 文件,但是在不同的子文件夹中可能有许多同名的文件。
- 使用 cmake 启动构建过程, CMake 的构建过程分为两个阶段。
- 第一步称为配置,读取 CMakeLists.txt 文件,并为系统的本地构建工具链生成指令。
- 第二步,执行这些构建指令,并构建可执行文件或库。
Tips:
配置步骤中,将检查构建需求,解析依赖关系,并生成构建指令。
- 项目还会创建一个名为 CMakeCache.txt 的文件,其中包含创建构建指令所需的信息。
cmake --build
的下一个阶段,通过内部调用 CMake 来执行构建; 若在 Windows 上,则通过调用 Visual Studio 编译器来实现,这是编译二进制文件的实际方式。若一切顺利,在构建文件夹中会有一个名为chapter1
的可执行文件。
如上例 CMake,可以使用
-S
选项来选择源文件.
当前目录下的 CMakeLists.txt 文件(也就是 chapter1 目录下的 CMakeLists.txt 文件),使用-B
选项来指定构建目录build
,构建生成的中间文件及可执行文件将在构建目录build
中生成。也可以先使用makdir
创建构建目录并进入,并使用相对路径来查找源文件目录的方式来使用 CMake 进行构建。
$ cd ../chapter1
$ mkdir build
$ cd build
$ cmake ..
-- The CXX compiler identification is GNU 11.4.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.7s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fyang/code/cmake-tutorial/CMake-Best-Practices/chapter1/build
$ cmake --build .
[ 50%] Building CXX object simple_executable/CMakeFiles/chapter1.dir/src/main.cpp.o
[100%] Linking CXX executable chapter1
[100%] Built target chapter1
$ ./simple_executable/chapter1
Hello World!
Tips:
在持续集成环境中使用 CMake 时,显式传递构建目录和源目录通常很方便,也助于提高可维护性。若想为不同的配置创建不同的构建目录,例如在构建跨平台软件时,这也会很有帮助。
1.4 简单的 CMakeLists.txt 示例
#Set minimum required CMake version
cmake_minimum_required(VERSION 3.21)
#Define a CMake project
project(
ch1_simple_executable
VERSION 1.0
DESCRIPTION "A simple C++ project to demonstrate basic CMake usage"
LANGUAGES CXX
)
#Add an executable target named 'chapter1'
add_executable(chapter1)
#Add source to the target 'chapter1'
target_sources(chapter1 PRIVATE src/main.cpp)
- 第一行定义了构建这个项目所需的 CMake 的最低版本。每个 CMakeLists.txt 文件都以这个指令开始。这是用来提示用户,若项目使用了 CMake 的特性,可能只有在某个版本以上才可用。通常,建议将版本设置为支持项目中使用特性的最低版本。
- 下一行是要构建的项目的名称、版本和描述,后面是项目中使用的编程语言。这里,使用 CXX 将其标记为一个 C++ 项目。
add_executable
告诉 CMake 要构建一个可执行文件 (与库或自定义目标不同)。target_sources
告诉 CMake 在哪里查找名为 chapter1 的可执行文件的源文件,并且源文件的可见性仅限于可执行文件
1.5 CMake 的构建过程
CMake 的构建过程分为两个阶段:
- 配置阶段: 将 CMakeLists.txt 转换为用于特定构建工具的文件
- 生成阶段: 使用生成的构建文件构建项目
标准输出是 Unix Makefiles,除非检测到唯一的编译器是 Microsoft Visual Studio,这种情况下将创建 Visual Studio 解决方案 (.sln
)。
修改生成器,可以使用 -G
选项:
cmake .. -G Ninja
这将生成 Ninja 的构建文件。 CMake 支持很多生成器, Ninja 是其中之一。在 CMake 网站上可以找到一个生成器支持列表
生成器主要有两种类型 – 一种是 Makefile 风格的生成器和 Ninja 生成器,多以命令行方式使用,另一种是为 IDE(如 Visual Studio 或 Xcode) 创建构建工程文件。
源文件夹和构建文件夹
CMake 中存在两个逻辑文件夹。一个是源文件夹,包含项目的层次结构集;另一个是构建文件夹,包含构建指令、缓存,以及所有生成的二进制文件和工件。
源文件夹是 CMakeLists.txt 文件所在的位置。构建文件夹可以放在源文件夹中,也可以将其放在另一个位置。两种方式都可以;构建文件夹通命名为 build
,但也可以使用其他名称,包括不同平台的前缀和后缀。当在源代码树中使用构建文件夹时,最好将其添加到 .gitignore
中。
当配置 CMake 项目时,将在构建文件夹中重新创建源文件夹的项目和文件夹结构,以便所有构建工件都位于相同的位置。每个文件夹中,都有一个名为 CMakeFiles 的子文件夹,其中包含 CMake 配置步骤生成的信息。
下面显示了一个 CMake 项目的目录结构:
.
├── CMakeLists.txt
├── README.md
└── simple_executable
├── CMakeLists.txt
└── src
└── main.cpp
当执行 CMake 配置时, CMake 项目的文件结构会映射到 build
文件夹中。每个包含 CMakeLists.txt 文件的文件夹将进行映射,将创建一个名为 CMakeFiles 的子文件夹,其中包含用于构建的信息:
.
├── build
│ └── simple_executable
│ ├── CMakeFiles
│ ├── Makefile
│ └── cmake_install.cmake
└── simple_executable
├── CMakeLists.txt
└── src
└── main.cpp
1.6 书写 CMake 文件
CMake 语言
CMake 使用 CMakeLists.txt 文件的配置文件来确定构建规范。这些文件用脚本语言编写,通常也称为 CMake。语言本身很简单,支持变量、字符串函数、宏、函数定义和导入其他 CMake 文件。
除了列表,不支持数据结构,如结构或类。但若操作得当,这种简单性使得 CMake 项目具有更好的可维护性。
语法基于关键字和以空格分隔的参数。例如,下面的指令会将相应的文件添加到库中:
target_sources(MyLibrary
PUBLIC include/api.h
PRIVATE src/internals.cpp src/foo.cpp)
PUBLIC
和 PRIVATE
关键字表示文件链接到这个库时的可见性,并充当文件列表之间的分隔符。
此外, CMake 语言支持“生成器表达式”,可以在生成构建系统时进行,通常用于为每个构建配置指定特殊信息。
工程
CMake 会将各种构建工件 (如库、可执行文件、测试和文档) 组织到项目中。虽然不同项目可以相互封装,但这里需要一个根项目。每个 CMakeLists.txt 文件对应一个项目,所以每个项目必须在源目录中有一个单独的文件夹。
项目描述如下:
project(
"chapter1"
VERSION 1.0
DESCRIPTION "A simple C++ project to demonstrate basic CMake usage"
LANGUAGES CXX
)
正在解析的当前项目存储在 PROJECT_NAME
变量中。根项目存储在 CMAKE_PROJECT_NAME
中,这对于确定一个项目是独立的还是封装在另一个项目中很有用。CMake 从 3.21
版本开始,还有一个 PROJECT_IS_TOP_LEVEL
变量来直接确定当前项目是否是顶层项目。此外,使用 <PROJECT-NAME>_IS_TOP_LEVEL
,可以检测特定的项目是否为顶层项目。
下面是关于项目的其他内置变量,可以在根项目的值前加上 CMAKE_
前缀。若没有在
project()
指令中定义,则字符串为空:
PROJECT_DESCRIPTION
: 项目的描述字符串PROJECT_HOMEPAGE_URL
: 项目的 URL 字符串PROJECT_VERSION
: 项目的完整版本信息PROJECT_VERSION_MAJOR
: 版本字符串的第一个数字PROJECT_VERSION_MINOR
: 版本字符串的第二个数字PROJECT_VERSION_PATCH
: 版本字符串的第三个数字PROJECT_VERSION_TWEAK
: 版本字符串的第四个数字
每个项目都有一个源目录和二进制目录,假设下面的例子中的每个 CMakeLists.txt 文件都定义了一个项目:
.
├── CMakeLists.txt #defines project("CMakeBestPractices"...)
├── chapter_1
│ ├── CMakeLists.txt # defines project("Chapter 1"...)
当解析根目录下的 CMakeLists.txt 文件时, PROJECT_NAME
和 CMAKE_PROJECT_NAME
都将是 CMakeBestPractices
。当解析 chapter_1/CMakeLists.txt 时, PROJECT_NAME 变量将变为 chapter_1
,但 CMAKE_PROJECT_NAME
还是 CMakeBestPractices
,并设置在根文件夹中。
尽管项目可以嵌套,但最好以独立的方式编写。虽然它们可能依赖于文件层次结构中较低的其他项目,但应该没有必要将一个项目作为另一个项目的子项目的必要。
可以在同一个 CMakeLists.txt 文件中使用多个 project()
,但并不推荐这样做,其会使项目混乱,难以维护。
通常,最好为每个项目创建一个 CMakeLists.txt 文件,并用子文件夹组织结构。
变量
变量是 CMake 语言的核心部分。可以使用 set
指令设置变量,使用 unset
指令删除变量。变量名区分大小写。
下面的例子展示了如何设置一个名为 MYVAR
的变量,并将其赋值为 1234
:
set(MYVAR "1234")
要删除 MYVAR
变量,可以使用 unset
:
unset(MYVAR)
一般的代码约定使用全大写命名变量。在内部,变量总是表示为字符串。这里,可以用 $
符号和花括号来访问变量的值:
message(STATUS "The content of MYVAR are ${MYVAR}")
变量的引用可以嵌套,并由内而外求值:
${outer_${inner_variable}_variable}
变量的作用域可以通过以下方式确定:
- 函数作用域: 在函数内部设置的变量只在函数内部可见。
- 目录作用域: 源树中的每个子目录绑定变量,并包括来自父目录的变量。
- 持久缓存: 缓存的变量可以是系统的,也可以是用户定义的。在多次运行中保持它们的值不变。
将 PARENT_SCOPE
选项传递给 set()
会使变量在父作用域中可见。CMake 提供了各种预定义变量,通常以 CMAKE_
为前缀。完整的列表地址
列表
尽管 CMake 在内部将变量存储为字符串,但可以在 CMake 中使用分号分隔值来处理列表。列表可以通过传递多个未加引号的变量给 set()
,或直接作为一个分号分隔的字符串来创建:
set(MYLIST abc def ghi)
set(MYLIST "abc;def;ghi")
使用 list
指令可以通过修改列表内容、重新排序或查找内容来操作列表。下面的代码将在 MYLIST
中查询 abc
值的索引,然后检索该值并将其存储在名为 ABC
的变量中:
list(FIND MYLIST abc ABC_INDEX)
list(GET MYLIST ${ABC_INDEX} ABC)
要向列表追加一个值,可以使用 APPEND
关键字。这里, xyz
值会添加到 MYLIST
中:
list(APPEND MYLIST "xyz")
缓存变量和选项
CMake 缓存了一些变量,以便后续的构建运行得更快,变量存储在 CMakeCache.txt 文件中。通常,不必手动编辑改文件,但对其修改可以用于调试构建。
所有用于配置构建的变量都会进行缓存。要缓存一个自定义变量 ch1_MYVAR
,其值为 foo
值,可以使用 set
指令:
set(ch1_MYVAR foo CACHE STRING "Variable foo that configures bar")
注意,缓存变量必须有一个类型和一个文档字符串,以提供它们的快照。
对于简单的布尔型缓存变量, CMake 还提供了 option
指令,默认值为 OFF
,除非另有说明。也可以通过 CMakeDependentOption 模块相互依赖:
option(CHAPTER1_PRINT_LANGUAGE_EXAMPLES "Print examples for
each language" OFF)
include(CMakeDependentOption)
cmake_dependent_option(CHAPTER1_PRINT_HELLO_WORLD "print a
greeting from chapter1 " ON CHAPTER1_PRINT_LANGUAGE_EXAMPLES
ON)
选项通常是指定简单项目配置的方法,是 bool 类型的缓存变量。若已经存在与该选项同名的变量,则 option
不会执行任何操作。
属性
CMake 中的属性是附加到特定对象或 CMake 范围的值,如文件、目标、目录或测试用例。属性可以通过 set_property
指令来设置。要读取属性的值,可以使用 get_property
。默认情况下, set_property
会覆盖已经存储在属性中的值,可以通过传递 APPEND
或APPEND_STRING
将值添加到当前值中。
完整签名如下:
set_property(<Scope> <EntityName>
[APPEND] [APPEND_STRING]
PROPERTY <propertyName> [<values>])
作用域说明符可以有以下值:
-
GLOBAL
: 影响整个构建过程的全局属性。 -
DIRECTORY <dir>
: 属性绑定到当前目录或<dir>
中指定的目录,也可以直接使用set_directory_properties
进行设置。 -
TARGET <targets>
: 特定目标的属性,也可以使用set_target_properties
进行设
置。 -
SOURCE <files>
: 将属性应用于源文件列表,也可以直接使用set_source_files_properties
进行设置。此外,还有SOURCE DIRECTORY
和SOURCE TARGET_DIRECTORY
扩展选项:-
DIRECTORY <dirs>
: 为目录范围内的源文件设置属性,该目录必须解析为当前目录或
add_subdirectory
添加的目录。 -
TARGET_DIRECTORY <targets>
: 这将属性设置为创建指定目标的目录,目标必须在
设置属性前已经存在。
-
-
INSTALL <files>
: 这将设置已安装文件的属性,可以用来控制cpack
的行为。 -
TEST <tests>
: 这将设置测试的属性,也可以直接使用set_test_properties
进行设置。 -
CACHE <entry>
: 这将设置缓存变量的属性。最常见的方法包括将变量设置为高级或向其添加文档字符串。
支持的属性的完整列表,请查阅
当修改属性时,可以使用 set_target_properties
和 set_test_properties
,而非
set_property
。使用显式指令可以避免属性名之间的错误和混淆,可读性会更强。还有一个
define_property
指令,其会创建一个不设置值的属性。我们不建议使用,因为属性总需要有一个默认值。
循环和条件
和其他编程语言一样, CMake 支持条件块和循环块。条件块位于 if()
、 elseif()
、 else()
和 endif()
语句之间。条件使用各种关键字表示。
一元关键字可以在值前加前缀:
if(DEFINED MY_VAR)
条件中使用的一元关键字如下:
COMMAND
: 若提供的值是命令,则为True
EXISTS
: 检查文件或路径是否存在DEFINED
: 若值是一个已定义的变量,则为True
此外,还有一元文件系统的条件:
EXISTS
: 若给定的文件或目录退出,则为True
IS_DIRECTORY
: 检查提供的路径是否为目录IS_SYMLINK
: 若提供的路径是符号链接,则为True
IS_ABSOULTE
: 检查提供的路径是否为绝对路径
二元测试比较两个值,并放在要比较的值之间:
if(MYVAR STREQUAL "FOO")
二元运算符如下:
-
LESS GREATER
,EQUAL
,LESS_EQUAL
和GREATER_EQUAL
: 比较数值。 -
STRLESS
,STREQUAL
,STRGREATER
,STRLESS_EQUAL
和STRGREATER_EQUAL
: 按字典序比较字符串。 -
VERSION_LESS
,VERSION_EQUAL
,VERSION_GREATER
,VERSION_LESS_EQUAL
和VERSION_GREATER_EQUAL
: 比较版本字符串。 -
MATCHES
: 使用正则表达式进行匹配。 -
IS_NEWER_THAN
: 比较两个文件中哪一个最近修改过。但这不是很精确,若两个文件具有相同的时间戳,也会返回True
。还有更令人困惑的结果,若其中任何一个文件丢失,结果也为True
。
最后,就是布尔运算符 OR
、 AND
和 NOT
。
循环可以通过 while()
和 endwhile()
,或 foreach()
和 endforeach()
实现。循环可以使用 break()
终止, Continue()
会中止当前的迭代并立即开始下一个迭代。
while
循环接受与 if
语句相同的条件。下面的示例只要 MYVAR
小于 5
就会循环。为了增加变量,我们使用 math()
指令:
set(MYVAR 0)
while(MYVAR LESS "5")
message(STATUS "Chapter1: MYVAR is '${MYVAR}'")
math(EXPR MYVAR "${MYVAR}+1")
endwhile()
除了 while
循环之外, CMake 还知道用于遍历列表或范围的循环:
foreach(ITEM IN LISTS MYLIST)
# do something with ${ITEM}
endforeach()
for
循环可以使用 RANGE
关键字:
foreach(ITEM RANGE 0 10)
# do something with ${ITEM}
endforeach()
foreach()
的RANGE
版本可以只用一个结束值,不过指定开始值和结束值是一个很好的习惯。
函数
函数由 function()/endfunction()
定义。函数为变量创建了一个新的作用域,因此所有在内部定义的变量都不能从外部访问,除非将 PARENT_SCOPE
选项传递给 set()
。
函数不区分大小写,通过 function
后的名称加上圆括号来使用函数:
function(foo ARG1)
# do something
endfunction()
# invoke foo with parameter bar
foo("bar")
函数是使 CMake 部分可重用方法,当在做大型项目时,函数会经常使用。
宏
CMake 宏使用 macro()/endmacro()
定义,有点像函数。不同的是,函数参数是真变量,而在宏中是字符串替换。这意味着必须使用大括号访问宏的所有参数。
另一个区别是,通过调用函数,作用区域转移到函数内部。执行宏时,就好像宏的主体粘贴到调用位置一样,宏不会创建变量和控制流的作用域。因此,避免在宏中调用 return()
。
目标
CMake 的构建系统会组织一组逻辑目标,这些逻辑目标对应于可执行文件、库或自定义命令或工件,如文档或类似的文件。
CMake 中有三种主要的方法来创建目标 —— add_executable
, add_library
和 add_custom_target
。前两个用于创建可执行文件和静态或动态库,而第三个可以包含要执行的定制命令。
目标间可以相互依赖,因此一个目标必须在另一个目标之前建立。在为构建配置或编译器选项设置属性时,使用目标变量是个好习惯。一些目标属性具有可见性修饰符,如 PRIVATE
、 PUBLIC
或 INTERFACE
,以表示哪些需求是可传递的——哪些属性必须由依赖的目标“继承”。
生成器表达式
生成器表达式是在构建的配置阶段进行的语句。大多数函数允许使用生成器表达式,只有少数例外。以 $<OPERATOR:VALUE>
的形式使用,其中 OPERATOR
或直接使用,或与 VALUE
进行比较。这里,可以将生成器表达式看作小型的内联 if
语句。
下面的例子中,若编译器是 GCC、 Clang 或 Apple Clang,则使用生成器表达式为 my_target
启用 -Wall
编译器标志。注意 GCC 的 COMPILER_ID
标识为”GNU”:
target_compile_options(my_target PRIVATE
"$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall>")
若 CXX_COMPILER_ID
变量匹配 GNU, Clang, AppleClang 列表中任意一个,则附加 -Wall
选项到目标——也就是 my_target
。生成器表达式在编写独立于平台和编译器的 CMake 文件时非常方便.
除了查询值,生成器表达式还可以用于转换字符串和列表:
$<LOWER_CASE:CMake>
这将输出“cmake”。想要了解生成器表达式更多详情,可参阅
CMake 策略
对于顶层的 CMakeLists.txt 文件的第一行,必须要有 cmake_minimum_required
,因为其会设置用于构建项目的内部 CMake 策略。
策略用于保持跨多个 CMake 版本的向后兼容性,可以配置为使用 OLD
行为,所以 CMake 的行为需要向后兼容,或者配置为 NEW
,使新策略生效。由于每个新版本都将引入新的规则和功能,因此将使用策略来警告向后兼容性问题。可以使用 cmake_policy
禁用或启用策略。
下面的例子中, CMP0121 策略设置为向后兼容的值。 CMP0121 在 CMake 3.21 引入,用于检查 list()
的索引变量是否为有效格式 —— 是否为整数:
cmake_minimum_required(VERSION 3.21)
cmake_policy(SET CMP0121 OLD)
list(APPEND MYLIST "abc;def;ghi")
list(GET MYLIST "any" OUT_VAR)
通过设置 cmake_policy(SET CMP0121 OLD)
,启用向后兼容,并且前面的代码不会产生警告,尽管这里使用“any” (不是一个整数) 索引访问 MYLIST。
在配置步骤中将策略设置为 NEW
将会抛出一个错误 —— [build] list index: any is not a valid index
。
除非包含遗留项目,否则避免设置策略
通常,策略应该通过
cmake_minimum_required
来控制,而不是通过更改单个策略来控制。更改策略常用于,将历史遗留项目作为子文件夹包含在其中时。
1.7 不同工具链和构建配置
CMake 的强大之处在于,可以为各种编译器工具链使用相同的构建规范。工具链通常由一系列程序组成,这些程序可以编译和链接二进制文件,以及创建存档和类似的文件。
CMake 支持各种可以配置工具链的语言。我们将重点关注 C++。配置不同编程语言的
工具链是通过用相应的语言标签,可以使用以下变量替换 CXX 部分来完成:
- C
- CXX – C++
- CUDA
- OBJC – Objective C
- OBJCXX – Objective C++
- Fortran
- HIP – 针对 NVIDIA 和 AMD GPU 的 HIP C++ 运行时 API
- ISPC – 基于 C 语言的 SPMD 编程语言
- ASM – 汇编编译器
若一个项目没有指定语言,就假定使用了 C 和 CXX, CMake 将通过检查系统自动检测要使用
的工具链。可以通过环境变量进行配置,或者在交叉编译的情况下,通过提供工具链文件进行配置。
此工具链存储在缓存中,若工具链发生更改,则必须删除并重新构建缓存。若安装了多个编译器,可以通过在调用 CMake 修改环境变量, CC
对应于 C 编译器, CXX
对应于 C++ 编译器,从而指定一个非默认编译器。这里,我们使用 CXX
环境变量来覆盖 CMake 中使用的默认编译器:
CXX=g++-7 cmake /path/to/the/source
也可以通过使用 -D
传递相应的 cmake 变量来覆盖 C++ 编译器,如下所示:
cmake -D CMAKE_CXX_COMPILER=g++-7 /path/to/source
两种方法都可以确保 CMake 使用 GCC 版本 7 来构建,而不是使用系统中任何默认的编译器。避免在 CMakeLists.txt 文件中设置编译器工具链,因为这与 CMake 文件应该是平台和编译器无关的范式相冲突。
默认情况下,链接器由编译器自动选择,但也可以通过将链接器可执行文件路径传递给
CMAKE_CXX_LINKER
变量,来指定不同的连接器。
构建类型
当构建 C++ 应用时,有各种构建类型很常见,例如包含所有调试符号的 Debug 构建和优化的
Release 构建。
CMake 本身提供了四种构建类型:
-
Debug: 未优化,包含所有调试符号,所有的断言都是启用的。这与 GCC 和 Clang 设置
-O0 -g
是一样的。 -
Release: 对运行速度进行优化,没有调试符号和断言禁用。通常,这是用于交付的构建类型。这与
-O3 -DNDEBUG
相同。 -
RelWithDebInfo: 提供优化,并包括调试符号,禁用断言,这与
-O2 -g -DNDEBUG
相同。 -
MinSizeRel: 这和 Release 是一样的,但优化了二进制大小,而不是速度,这与
-Os -DNDEBUG
相同。注意,并不是所有平台上的生成器都支持此配置。