CMake 秘籍(一)(3)https://developer.aliyun.com/article/1524605
如何操作
我们如何选择特定的编译器?例如,如果我们想使用 Intel 或 Portland Group 编译器怎么办?CMake 为每种语言的编译器存储在CMAKE__COMPILER
变量中,其中是任何受支持的语言,对我们来说,
CXX
、C
或Fortran
。用户可以通过以下两种方式之一设置此变量:
- 通过在 CLI 中使用
-D
选项,例如:
$ cmake -D CMAKE_CXX_COMPILER=clang++ ..
- 通过导出环境变量
CXX
用于 C++编译器,CC
用于 C 编译器,FC
用于 Fortran 编译器。例如,使用此命令将 clang++作为 C++编译器:
$ env CXX=clang++ cmake ..
到目前为止讨论的任何配方都可以通过传递适当的选项配置为与任何其他编译器一起使用。
CMake 了解环境,并且许多选项可以通过其 CLI 的-D
开关或通过环境变量设置。前者机制覆盖后者,但我们建议始终使用-D
显式设置选项。显式优于隐式,因为环境变量可能设置为不适合当前项目的值。
我们在这里假设额外的编译器在 CMake 进行查找的标准路径中可用。如果不是这种情况,用户需要传递编译器可执行文件或包装器的完整路径。
我们建议使用-D CMAKE__COMPILER
CLI 选项设置编译器,而不是导出CXX
,CC
和FC
。这是唯一保证跨平台兼容且与非 POSIX shell 兼容的方法。它还可以避免用可能影响与项目一起构建的外部库的环境的变量污染您的环境。
它是如何工作的
在配置时,CMake 执行一系列平台测试,以确定哪些编译器可用,以及它们是否适合手头的项目。合适的编译器不仅由我们工作的平台决定,还由我们要使用的生成器决定。CMake 执行的第一个测试基于项目语言的编译器名称。例如,如果cc
是一个工作的 C 编译器,那么它将用作 C 项目的默认编译器。在 GNU / Linux 上,使用 Unix Makefiles 或 Ninja,GCC 家族的编译器将最有可能被默认选择用于 C ++,C 和 Fortran。在 Microsoft Windows 上,如果选择 Visual Studio 作为生成器,则将选择 Visual Studio 中的 C ++和 C 编译器。如果选择 MinGW 或 MSYS Makefiles 作为生成器,则默认使用 MinGW 编译器。
还有更多
我们可以在哪里找到 CMake 将为我们平台选择哪些默认编译器和编译器标志?CMake 提供了--system-information
标志,该标志会将有关您系统的所有信息转储到屏幕或文件中。要查看此信息,请尝试以下操作:
$ cmake --system-information information.txt
在文件(在本例中为information.txt
)中搜索,您将找到CMAKE_CXX_COMPILER
,CMAKE_C_COMPILER
和CMAKE_Fortran_COMPILER
选项的默认值,以及它们的默认标志。我们将在下一个配方中查看这些标志。
CMake 提供了其他变量来与编译器交互:
CMAKE__COMPILER_LOADED
:如果为项目启用了语言,则设置为
TRUE
。CMAKE__COMPILER_ID
:编译器识别字符串,对于编译器供应商是唯一的。例如,对于 GNU 编译器集合,这是GCC
,对于 macOS 上的 Clang,这是AppleClang
,对于 Microsoft Visual Studio 编译器,这是MSVC
。但是请注意,不能保证此变量对所有编译器或语言都定义。CMAKE_COMPILER_IS_GNU
:如果语言的编译器是 GNU 编译器集合的一部分,则此逻辑变量设置为
TRUE
。请注意,变量名称的部分遵循 GNU 约定:对于 C 语言,它将是
CC
,对于 C ++语言,它将是CXX
,对于 Fortran 语言,它将是G77
。CMAKE__COMPILER_VERSION
:此变量包含给定语言的编译器版本的字符串。版本信息以major[.minor[.patch[.tweak]]]
格式给出。但是,与CMAKE__COMPILER_ID
一样,不能保证此变量对所有编译器或语言都定义。
我们可以尝试使用不同的编译器配置以下示例CMakeLists.txt
。在这个例子中,我们将使用 CMake 变量来探测我们正在使用的编译器及其版本:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(recipe-06 LANGUAGES C CXX) message(STATUS "Is the C++ compiler loaded? ${CMAKE_CXX_COMPILER_LOADED}") if(CMAKE_CXX_COMPILER_LOADED) message(STATUS "The C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Is the C++ from GNU? ${CMAKE_COMPILER_IS_GNUCXX}") message(STATUS "The C++ compiler version is: ${CMAKE_CXX_COMPILER_VERSION}") endif() message(STATUS "Is the C compiler loaded? ${CMAKE_C_COMPILER_LOADED}") if(CMAKE_C_COMPILER_LOADED) message(STATUS "The C compiler ID is: ${CMAKE_C_COMPILER_ID}") message(STATUS "Is the C from GNU? ${CMAKE_COMPILER_IS_GNUCC}") message(STATUS "The C compiler version is: ${CMAKE_C_COMPILER_VERSION}") endif()
请注意,此示例不包含任何目标,因此没有要构建的内容,我们只关注配置步骤:
$ mkdir -p build $ cd build $ cmake .. ... -- Is the C++ compiler loaded? 1 -- The C++ compiler ID is: GNU -- Is the C++ from GNU? 1 -- The C++ compiler version is: 8.1.0 -- Is the C compiler loaded? 1 -- The C compiler ID is: GNU -- Is the C from GNU? 1 -- The C compiler version is: 8.1.0 ...
输出当然取决于可用和选择的编译器以及编译器版本。
切换构建类型
本配方的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-01/recipe-07
找到,并包含一个 C++/C 示例。该配方适用于 CMake 版本 3.5(及以上),并在 GNU/Linux、macOS 和 Windows 上进行了测试。
CMake 具有构建类型或配置的概念,例如Debug
、Release
等。在一种配置中,可以收集相关选项或属性,例如编译器和链接器标志,用于Debug
或Release
构建。控制生成构建系统时使用的配置的变量是CMAKE_BUILD_TYPE
。该变量默认情况下为空,CMake 识别的值包括:
Debug
用于构建您的库或可执行文件,不带优化且带有调试符号,Release
用于构建您的库或可执行文件,带有优化且不带调试符号,RelWithDebInfo
用于构建您的库或可执行文件,具有较不激进的优化和调试符号,MinSizeRel
用于构建您的库或可执行文件,优化不会增加对象代码大小。
如何操作
在本配方中,我们将展示如何为示例项目设置构建类型:
- 我们首先定义了最小 CMake 版本、项目名称和支持的语言:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(recipe-07 LANGUAGES C CXX)
- 然后,我们设置了一个默认构建类型(在这种情况下,
Release
),并将其打印在消息中供用户查看。请注意,该变量被设置为CACHE
变量,以便随后可以通过缓存进行编辑:
if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
- 最后,我们打印出由 CMake 根据构建类型设置的相应编译标志:
message(STATUS "C flags, Debug configuration: ${CMAKE_C_FLAGS_DEBUG}") message(STATUS "C flags, Release configuration: ${CMAKE_C_FLAGS_RELEASE}") message(STATUS "C flags, Release configuration with Debug info: ${CMAKE_C_FLAGS_RELWITHDEBINFO}") message(STATUS "C flags, minimal Release configuration: ${CMAKE_C_FLAGS_MINSIZEREL}") message(STATUS "C++ flags, Debug configuration: ${CMAKE_CXX_FLAGS_DEBUG}") message(STATUS "C++ flags, Release configuration: ${CMAKE_CXX_FLAGS_RELEASE}") message(STATUS "C++ flags, Release configuration with Debug info: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") message(STATUS "C++ flags, minimal Release configuration: ${CMAKE_CXX_FLAGS_MINSIZEREL}")
- 现在让我们验证默认配置的输出:
$ mkdir -p build $ cd build $ cmake .. ... -- Build type: Release -- C flags, Debug configuration: -g -- C flags, Release configuration: -O3 -DNDEBUG -- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG -- C flags, minimal Release configuration: -Os -DNDEBUG -- C++ flags, Debug configuration: -g -- C++ flags, Release configuration: -O3 -DNDEBUG -- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG -- C++ flags, minimal Release configuration: -Os -DNDEBUG
- 现在,让我们切换构建类型:
$ cmake -D CMAKE_BUILD_TYPE=Debug .. -- Build type: Debug -- C flags, Debug configuration: -g -- C flags, Release configuration: -O3 -DNDEBUG -- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG -- C flags, minimal Release configuration: -Os -DNDEBUG -- C++ flags, Debug configuration: -g -- C++ flags, Release configuration: -O3 -DNDEBUG -- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG -- C++ flags, minimal Release configuration: -Os -DNDEBUG
它是如何工作的
我们已经演示了如何设置默认构建类型以及如何从命令行覆盖它。通过这种方式,我们可以控制项目是使用优化标志构建,还是关闭所有优化并启用调试信息。我们还看到了不同可用配置使用的标志类型,这取决于所选的编译器。除了在 CMake 运行期间明确打印标志外,还可以通过运行cmake --system-information
来查看当前平台、默认编译器和语言组合的预设。在下一个配方中,我们将讨论如何为不同的编译器和不同的构建类型扩展或调整编译器标志。
还有更多
我们已经展示了CMAKE_BUILD_TYPE
变量(文档链接:cmake.org/cmake/help/v3.5/variable/CMAKE_BUILD_TYPE.html
)如何定义生成的构建系统的配置。在评估编译器优化级别的影响时,例如,构建项目的Release
和Debug
配置通常很有帮助。对于单配置生成器,如 Unix Makefiles、MSYS Makefiles 或 Ninja,这需要运行 CMake 两次,即对项目进行完全重新配置。然而,CMake 还支持多配置生成器。这些通常是由集成开发环境提供的项目文件,最著名的是 Visual Studio 和 Xcode,它们可以同时处理多个配置。这些生成器的可用配置类型可以通过CMAKE_CONFIGURATION_TYPES
变量进行调整,该变量将接受一个值列表(文档链接:cmake.org/cmake/help/v3.5/variable/CMAKE_CONFIGURATION_TYPES.html
)。
以下是使用 Visual Studio 的 CMake 调用:
$ mkdir -p build $ cd build $ cmake .. -G"Visual Studio 12 2017 Win64" -D CMAKE_CONFIGURATION_TYPES="Release;Debug"
将生成Release
和Debug
配置的构建树。然后,您可以使用--config
标志决定构建哪一个:
$ cmake --build . --config Release
当使用单配置生成器开发代码时,为Release
和Debug
构建类型创建单独的构建目录,两者都配置相同的源代码。这样,您可以在两者之间切换,而不会触发完全重新配置和重新编译。
控制编译器标志
本示例的代码可在github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-01/recipe-08
找到,并包含一个 C++示例。本示例适用于 CMake 版本 3.5(及更高版本),并在 GNU/Linux、macOS 和 Windows 上进行了测试。
之前的示例展示了如何向 CMake 查询有关编译器的信息,以及如何调整项目中所有目标的编译器优化。后一项任务是控制项目中使用哪些编译器标志的一般需求的一个子集。CMake 提供了调整或扩展编译器标志的很大灵活性,您可以选择两种主要方法之一:
- CMake 将编译选项视为目标的属性。因此,可以在不覆盖 CMake 默认设置的情况下,为每个目标设置编译选项。
- 通过使用
-D
CLI 开关,您可以直接修改CMAKE__FLAGS_
变量。这些变量将影响项目中的所有目标,并覆盖或扩展 CMake 的默认设置。
在本示例中,我们将展示这两种方法。
准备工作
我们将编译一个计算不同几何形状面积的示例程序。代码在名为compute-areas.cpp
的文件中有一个main
函数:
#include "geometry_circle.hpp" #include "geometry_polygon.hpp" #include "geometry_rhombus.hpp" #include "geometry_square.hpp" #include <cstdlib> #include <iostream> int main() { using namespace geometry; double radius = 2.5293; double A_circle = area::circle(radius); std::cout << "A circle of radius " << radius << " has an area of " << A_circle << std::endl; int nSides = 19; double side = 1.29312; double A_polygon = area::polygon(nSides, side); std::cout << "A regular polygon of " << nSides << " sides of length " << side << " has an area of " << A_polygon << std::endl; double d1 = 5.0; double d2 = 7.8912; double A_rhombus = area::rhombus(d1, d2); std::cout << "A rhombus of major diagonal " << d1 << " and minor diagonal " << d2 << " has an area of " << A_rhombus << std::endl; double l = 10.0; double A_square = area::square(l); std::cout << "A square of side " << l << " has an area of " << A_square << std::endl; return EXIT_SUCCESS; }
各种函数的实现包含在其他文件中:每个几何形状都有一个头文件和一个对应的源文件。总共,我们有四个头文件和五个源文件需要编译:
. ├── CMakeLists.txt ├── compute-areas.cpp ├── geometry_circle.cpp ├── geometry_circle.hpp ├── geometry_polygon.cpp ├── geometry_polygon.hpp ├── geometry_rhombus.cpp ├── geometry_rhombus.hpp ├── geometry_square.cpp └── geometry_square.hpp
我们不会为所有这些文件提供列表,而是引导读者参考github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-01/recipe-08
。
如何操作
现在我们有了源文件,我们的目标将是配置项目并尝试使用编译器标志:
- 我们设置 CMake 的最低要求版本:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
- 我们声明项目的名称和语言:
project(recipe-08 LANGUAGES CXX)
- 然后,我们打印当前的编译器标志集。CMake 将使用这些标志来编译所有 C++目标:
message("C++ compiler flags: ${CMAKE_CXX_FLAGS}")
- 我们为我们的目标准备了一份标志列表。其中一些在 Windows 上可能不可用,我们确保考虑到这种情况:
list(APPEND flags "-fPIC" "-Wall") if(NOT WIN32) list(APPEND flags "-Wextra" "-Wpedantic") endif()
- 我们添加一个新的目标,
geometry
库及其源依赖项:
add_library(geometry STATIC geometry_circle.cpp geometry_circle.hpp geometry_polygon.cpp geometry_polygon.hpp geometry_rhombus.cpp geometry_rhombus.hpp geometry_square.cpp geometry_square.hpp )
- 我们为这个库目标设置编译选项:
target_compile_options(geometry PRIVATE ${flags} )
- 然后,我们为
compute-areas
可执行文件添加一个目标:
add_executable(compute-areas compute-areas.cpp)
- 我们还为可执行目标设置编译选项:
target_compile_options(compute-areas PRIVATE "-fPIC" )
- 最后,我们将可执行文件链接到
geometry
库:
target_link_libraries(compute-areas geometry)
它是如何工作的
在这个例子中,警告标志-Wall
、-Wextra
和-Wpedantic
将被添加到geometry
目标的编译选项中;compute-areas
和geometry
目标都将使用-fPIC
标志。编译选项可以通过三种可见性级别添加:INTERFACE
、PUBLIC
和PRIVATE
。
可见性级别具有以下含义:
- 使用
PRIVATE
属性,编译选项将仅应用于给定目标,而不会应用于其他消费它的目标。在我们的示例中,设置在geometry
目标上的编译器选项不会被compute-areas
继承,尽管compute-areas
会链接到geometry
库。 - 使用
INTERFACE
属性,给定目标的编译选项将仅应用于消费它的目标。 - 使用
PUBLIC
属性,编译选项将应用于给定目标以及所有其他消费它的目标。
目标属性的可见性级别是现代 CMake 使用的核心,我们将在本书中经常并广泛地回顾这个主题。以这种方式添加编译选项不会污染CMAKE__FLAGS_
全局 CMake 变量,并给你对哪些选项用于哪些目标的精细控制。
我们如何验证标志是否如我们所愿正确使用?换句话说,你如何发现一个 CMake 项目实际上使用了哪些编译标志?一种方法是使用 CMake 传递额外的参数,在这种情况下是环境变量VERBOSE=1
,给本地构建工具:
$ mkdir -p build $ cd build $ cmake .. $ cmake --build . -- VERBOSE=1 ... lots of output ... [ 14%] Building CXX object CMakeFiles/geometry.dir/geometry_circle.cpp.o /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_circle.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_circle.cpp [ 28%] Building CXX object CMakeFiles/geometry.dir/geometry_polygon.cpp.o /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_polygon.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_polygon.cpp [ 42%] Building CXX object CMakeFiles/geometry.dir/geometry_rhombus.cpp.o /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_rhombus.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_rhombus.cpp [ 57%] Building CXX object CMakeFiles/geometry.dir/geometry_square.cpp.o /usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o CMakeFiles/geometry.dir/geometry_square.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/geometry_square.cpp ... more output ... [ 85%] Building CXX object CMakeFiles/compute-areas.dir/compute-areas.cpp.o /usr/bin/c++ -fPIC -o CMakeFiles/compute-areas.dir/compute-areas.cpp.o -c /home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/compute-areas.cpp ... more output ...
前面的输出确认编译标志根据我们的指示正确设置。
控制编译器标志的第二种方法不涉及对CMakeLists.txt
的任何修改。如果想要为该项目中的geometry
和compute-areas
目标修改编译器选项,只需使用一个额外的参数调用 CMake 即可。
$ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..
正如你可能已经猜到的,这个命令将编译项目,禁用异常和运行时类型识别(RTTI)。
这两种方法也可以结合使用。可以使用一组基本的标志全局设置,同时保持对每个目标发生的情况的控制。我们可以使用CMakeLists.txt
并运行这个命令:
$ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..
这将使用-fno-exceptions -fno-rtti -fPIC -Wall -Wextra -Wpedantic
配置geometry
目标,同时使用-fno-exceptions -fno-rtti -fPIC
配置compute-areas
。
在本书的其余部分,我们通常会为每个目标设置编译器标志,这是我们推荐您项目采用的做法。使用 target_compile_options()
不仅允许对编译选项进行细粒度控制,而且还更好地与 CMake 的更高级功能集成。
CMake 秘籍(一)(5)https://developer.aliyun.com/article/1524611