1.8 设置编译器选项

    前面的示例展示了如何探测CMake,从而获得关于编译器的信息,以及如何切换项目中的编译器。后一个任务是控制项目的编译器标志。CMake为调整或扩展编译器标志提供了很大的灵活性,您可以选择下面两种方法:

    • CMake将编译选项视为目标属性。因此,可以根据每个目标设置编译选项,而不需要覆盖CMake默认值。
    • 可以使用CLI标志直接修改CMAKE_<LANG>_FLAGS_<CONFIG>变量。这将影响项目中的所有目标,并覆盖或扩展CMake默认值。

    本示例中,我们将展示这两种方法。

    编写一个示例程序,计算不同几何形状的面积,computer_area.cpp

    函数的各种实现分布在不同的文件中,每个几何形状都有一个头文件和源文件。总共有4个头文件和5个源文件要编译:

    1. .
    2. ├─ CMakeLists.txt
    3. ├─ compute-areas.cpp
    4. ├─ geometry_circle.cpp
    5. ├─ geometry_circle.hpp
    6. ├─ geometry_polygon.cpp
    7. ├─ geometry_polygon.hpp
    8. ├─ geometry_rhombus.cpp
    9. ├─ geometry_rhombus.hpp
    10. ├─ geometry_square.cpp
    11. └─ geometry_square.hpp

    我们不会为所有文件提供清单,读者可以参考 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-01/recipe-08

    现在已经有了源代码,我们的目标是配置项目,并使用编译器标示进行实验:

    1. 设置CMake的最低版本:

      1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
    2. 声明项目名称和语言:

      1. project(recipe-08 LANGUAGES CXX)
    3. 然后,打印当前编译器标志。CMake将对所有C++目标使用这些:

      1. message("C++ compiler flags: ${CMAKE_CXX_FLAGS}")
    4. 添加了一个新的目标——geometry库,并列出它的源依赖关系:

      1. add_library(geometry
      2. STATIC
      3. geometry_circle.cpp
      4. geometry_circle.hpp
      5. geometry_polygon.cpp
      6. geometry_rhombus.cpp
      7. geometry_rhombus.hpp
      8. geometry_square.cpp
      9. geometry_square.hpp
      10. )
    5. 为这个库目标设置了编译选项:

      1. target_compile_options(geometry
      2. PRIVATE
      3. ${flags}
      4. )
    6. 然后,将生成compute-areas可执行文件作为一个目标:

      1. add_executable(compute-areas compute-areas.cpp)
    7. 还为可执行目标设置了编译选项:

      1. target_compile_options(compute-areas
      2. PRIVATE
      3. "-fPIC"
    8. 最后,将可执行文件链接到geometry库:

    本例中,警告标志有-Wall-Wextra-Wpedantic,将这些标示添加到geometry目标的编译选项中; compute-areasgeometry目标都将使用-fPIC标志。编译选项可以添加三个级别的可见性:INTERFACEPUBLICPRIVATE

    可见性的含义如下:

    • PRIVATE,编译选项会应用于给定的目标,不会传递给与目标相关的目标。我们的示例中, 即使compute-areas将链接到geometry库,compute-areas也不会继承geometry目标上设置的编译器选项。
    • INTERFACE,给定的编译选项将只应用于指定目标,并传递给与目标相关的目标。
    • PUBLIC,编译选项将应用于指定目标和使用它的目标。

    目标属性的可见性CMake的核心,我们将在本书中经常讨论这个话题。以这种方式添加编译选项,不会影响全局CMake变量CMAKE_<LANG>_FLAGS_<CONFIG>,并能更细粒度控制在哪些目标上使用哪些选项。

    我们如何验证,这些标志是否按照我们的意图正确使用呢?或者换句话说,如何确定项目在CMake构建时,实际使用了哪些编译标志?一种方法是,使用CMake将额外的参数传递给本地构建工具。本例中会设置环境变量VERBOSE=1

    1. $ mkdir -p build
    2. $ cd build
    3. $ cmake ..
    4. $ cmake --build . -- VERBOSE=1
    5. ... lots of output ...
    6. [ 14%] Building CXX object CMakeFiles/geometry.dir/geometry_circle.cpp.o
    7. /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
    8. /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
    9. [ 42%] Building CXX object CMakeFiles/geometry.dir/geometry_rhombus.cpp.o
    10. /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
    11. [ 57%] Building CXX object CMakeFiles/geometry.dir/geometry_square.cpp.o
    12. /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
    13. ... more output ...
    14. [ 85%] Building CXX object CMakeFiles/compute-areas.dir/compute-areas.cpp.o
    15. /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

    控制编译器标志的第二种方法,不用对CMakeLists.txt进行修改。如果想在这个项目中修改geometrycompute-areas目标的编译器选项,可以使用CMake参数进行配置:

    1. $ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..

    这个命令将编译项目,禁用异常和运行时类型标识(RTTI)。

    也可以使用全局标志,可以使用CMakeLists.txt运行以下命令:

    1. $ 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的更高级特性进行集成。

    大多数时候,编译器有特性标示。当前的例子只适用于GCCClang;其他供应商的编译器不确定是否会理解(如果不是全部)这些标志。如果项目是真正跨平台,那么这个问题就必须得到解决,有三种方法可以解决这个问题。

    最典型的方法是将所需编译器标志列表附加到每个配置类型CMake变量CMAKE_<LANG>_FLAGS_<CONFIG>。标志确定设置为给定编译器有效的标志,因此将包含在if-endif子句中,用于检查CMAKE_<LANG>_COMPILER_ID变量,例如:

    1. if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
    2. list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions")
    3. list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wsuggest-final-types" "-Wsuggest-final-methods" "-Wsuggest-override")
    4. list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
    5. endif()
    6. if(CMAKE_CXX_COMPILER_ID MATCHES Clang)
    7. list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions" "-Qunused-arguments" "-fcolor-diagnostics")
    8. list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wdocumentation")
    9. list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
    10. endif()

    更细粒度的方法是,不修改CMAKE_<LANG>_FLAGS_<CONFIG>变量,而是定义特定的标志列表:

    稍后,使用生成器表达式来设置编译器标志的基础上,为每个配置和每个目标生成构建系统:

    1. target_compile_option(compute-areas
    2. PRIVATE
    3. ${CXX_FLAGS}
    4. "$<$<CONFIG:Debug>:${CXX_FLAGS_DEBUG}>"
    5. "$<$<CONFIG:Release>:${CXX_FLAGS_RELEASE}>"
    6. )

    两种方法都有效,并在许多项目中得到广泛应用。不过,每种方式都有缺点。CMAKE_<LANG>_COMPILER_ID不能保证为所有编译器都定义。此外,一些标志可能会被弃用,或者在编译器的较晚版本中引入。与CMAKE_<LANG>_COMPILER_ID类似,CMAKE_<LANG>_COMPILER_VERSION变量不能保证为所有语言和供应商都提供定义。尽管检查这些变量的方式非常流行,但我们认为更健壮的替代方法是检查所需的标志集是否与给定的编译器一起工作,这样项目中实际上只使用有效的标志。结合特定于项目的变量、和生成器表达式,会让解决方案变得非常强大。我们将在第7章的第3节中展示,如何使用check-and-set模式。