1.8 设置编译器选项
NOTE : 此示例代码可以在 https://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 默认值。
- 可以使用
-DCLI 标志直接修改CMAKE_<LANG>_FLAGS_<CONFIG>变量。这将影响项目中的所有目标,并覆盖或扩展 CMake 默认值。
本示例中,我们将展示这两种方法。
准备工作
编写一个示例程序,计算不同几何形状的面积, computer_area.cpp :
#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;
}函数的各种实现分布在不同的文件中,每个几何形状都有一个头文件和源文件。总共有 4 个头文件和 5 个源文件要编译:
. ├─ 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
我们不会为所有文件提供清单,读者可以参考 https://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 ,编译选项会应用于给定的目标,不会传递给与目标相关的目标。我们的示例中, 即使
compute-areas将链接到geometry库,compute-areas也不会继承geometry目标上设置的编译器选项。 - INTERFACE ,给定的编译选项将只应用于指定目标,并传递给与目标相关的目标。
- PUBLIC ,编译选项将应用于指定目标和使用它的目标。
目标属性的可见性 CMake 的核心,我们将在本书中经常讨论这个话题。以这种方式添加编译选项,不会影响全局 CMake 变量 CMAKE_<LANG>_FLAGS_<CONFIG> ,并能更细粒度控制在哪些目标上使用哪些选项。
我们如何验证,这些标志是否按照我们的意图正确使用呢?或者换句话说,如何确定项目在 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-rtti - fpic - wall - Wextra - wpedantic 配置 geometry 目标,同时使用 -fno exception -fno-rtti - fpic 配置 compute-areas 。
NOTE : 本书中,我们推荐为每个目标设置编译器标志。使用 target_compile_options() 不仅允许对编译选项进行细粒度控制,而且还可以更好地与 CMake 的更高级特性进行集成。
更多信息
大多数时候,编译器有特性标示。当前的例子只适用于 GCC 和 Clang ;其他供应商的编译器不确定是否会理解(如果不是全部) 这些标志。如果项目是真正跨平台,那么这个问题就必须得到解决,有三种方法可以解决这个问题。
最典型的方法是将所需编译器标志列表附加到每个配置类型 CMake 变量 CMAKE_<LANG>_FLAGS_<CONFIG> 。标志确定设置为给定编译器有效的标志,因此将包含在 if-endif 子句中,用于检查 CMAKE_<LANG>_COMPILER_ID 变量,例如:
if(CMAKE_CXX_COMPILER_ID MATCHES GNU) list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions") list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wsuggest-final-types" "-Wsuggest-final-methods" "-Wsuggest-override") list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused") endif() if(CMAKE_CXX_COMPILER_ID MATCHES Clang) list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions" "-Qunused-arguments" "-fcolor-diagnostics") list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wdocumentation") list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused") endif()
更细粒度的方法是,不修改 CMAKE_<LANG>_FLAGS_<CONFIG> 变量,而是定义特定的标志列表:
set(COMPILER_FLAGS) set(COMPILER_FLAGS_DEBUG) set(COMPILER_FLAGS_RELEASE) if(CMAKE_CXX_COMPILER_ID MATCHES GNU) list(APPEND CXX_FLAGS "-fno-rtti" "-fno-exceptions") list(APPEND CXX_FLAGS_DEBUG "-Wsuggest-final-types" "-Wsuggest-final-methods" "-Wsuggest-override") list(APPEND CXX_FLAGS_RELEASE "-O3" "-Wno-unused") endif() if(CMAKE_CXX_COMPILER_ID MATCHES Clang) list(APPEND CXX_FLAGS "-fno-rtti" "-fno-exceptions" "-Qunused-arguments" "-fcolor-diagnostics") list(APPEND CXX_FLAGS_DEBUG "-Wdocumentation") list(APPEND CXX_FLAGS_RELEASE "-O3" "-Wno-unused") endif()
稍后,使用生成器表达式来设置编译器标志的基础上,为每个配置和每个目标生成构建系统:
target_compile_option(compute-areas
PRIVATE
${CXX_FLAGS}
"$<$<CONFIG:Debug>:${CXX_FLAGS_DEBUG}>"
"$<$<CONFIG:Release>:${CXX_FLAGS_RELEASE}>"
)当前示例中展示了这两种方法,我们推荐后者(特定于项目的变量和 target_compile_options )。
两种方法都有效,并在许多项目中得到广泛应用。不过,每种方式都有缺点。 CMAKE_<LANG>_COMPILER_ID 不能保证为所有编译器都定义。此外,一些标志可能会被弃用,或者在编译器的较晚版本中引入。与 CMAKE_<LANG>_COMPILER_ID 类似, CMAKE_<LANG>_COMPILER_VERSION 变量不能保证为所有语言和供应商都提供定义。尽管检查这些变量的方式非常流行,但我们认为更健壮的替代方法是检查所需的标志集是否与给定的编译器一起工作,这样项目中实际上只使用有效的标志。结合特定于项目的变量、 target_compile_options 和生成器表达式,会让解决方案变得非常强大。我们将在第 7 章的第 3 节中展示,如何使用 check-and-set 模式。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论