cmake-compile-features(7)

简介

项目源代码可能依赖于,或取决于编译器是否支持某些特性。这会产生三种使用场景:编译特性需求可选编译特性 以及 条件编译选项

虽然特性通常是在编程语言标准中指定的,但 CMake 提供了一个基于细粒度特性处理的初级用户界面,而不是基于引入该特性的语言标准。

CMAKE_C_KNOWN_FEATURESCMAKE_CUDA_KNOWN_FEATURES 以及 CMAKE_CXX_KNOWN_FEATURES 全局属性包含了 CMake 所知的所有特性,无论编译器是否支持。而 CMAKE_C_COMPILE_FEATURESCMAKE_CUDA_COMPILE_FEATURES 以及 CMAKE_CXX_COMPILE_FEATURES 变量则包含了 CMake 已知编译器所支持的所有特性,且与使用它们所需的语言标准或编译标志无关。

CMake 已知特性的命名大多遵循与 Clang 特性测试宏相同的约定。但有一些例外,例如 CMake 使用 cxx_finalcxx_override,而不是 Clang 使用的单一 cxx_override_control

请注意,OBJCOBJCXX 语言没有单独的编译特性属性或变量。它们分别基于 CC++,因此应使用其对应基础语言的属性和变量。

编译特性需求

可以通过 target_compile_features() 命令指定编译特性需求。例如,如果一个目标必须在编译器支持 cxx_constexpr 特性的情况下进行编译:

add_library(mylib requires_constexpr.cpp)
target_compile_features(mylib PRIVATE cxx_constexpr)

在处理 cxx_constexpr 特性的需求时,cmake(1) 将确保所使用的 C++ 编译器支持该特性,并将必要的标志(例如 -std=gnu++11)添加到 mylib 目标的 C++ 文件编译命令行中。如果编译器不支持该特性,则会发出 FATAL_ERROR

确切的编译标志和语言标准并非此用例用户界面的组成部分。CMake 会根据为每个目标指定的特性来计算应使用的适当编译标志。

即使编译器无需标志即可支持特定特性,也会添加此类编译标志。例如,GNU 编译器即使在使用了 -std=gnu++98 时也支持可变参数模板(会产生警告)。如果将 cxx_variadic_templates 指定为需求,CMake 仍会添加 -std=gnu++11 标志。

在上述示例中,mylib 在自身构建时需要 cxx_constexpr,但 mylib 的消费者并不一定需要使用支持 cxx_constexpr 的编译器。如果 mylib 的接口确实需要 cxx_constexpr 特性(或任何其他已知特性),可以通过 target_compile_features()PUBLICINTERFACE 签名来指定。

add_library(mylib requires_constexpr.cpp)
# cxx_constexpr is a usage-requirement
target_compile_features(mylib PUBLIC cxx_constexpr)

# main.cpp will be compiled with -std=gnu++11 on GNU for cxx_constexpr.
add_executable(myexe main.cpp)
target_link_libraries(myexe mylib)

特性需求会通过链接实现进行传递性评估。关于构建属性和使用需求的传递性行为,请参见 cmake-buildsystem(7)

语言标准需求

在项目中,如果使用了大量来自特定语言标准(例如 C++ 11)的通用特性,可以指定一个元特性(例如 cxx_std_11),这要求使用至少支持该标准的编译器模式,甚至可以是更高的标准。这比单独指定所有特性更简单,但不能保证存在任何特定特性。对不支持特性的使用检查将推迟到编译时进行。

例如,如果项目头文件中广泛使用了 C++ 11 特性,那么客户端必须使用不低于 C++ 11 的编译器模式。这可以通过以下代码请求:

target_compile_features(mylib PUBLIC cxx_std_11)

在此示例中,CMake 将确保编译器以至少 C++ 11(或 C++ 14、C++ 17……)的模式运行,必要时添加如 -std=gnu++11 的标志。这既适用于 mylib 内部的源文件,也适用于任何依赖项(可能包含来自 mylib 的头文件)。

注意

如果编译器的默认标准级别至少等于所请求功能的标准级别,CMake 可能会省略 -std= 标志。如果编译器的默认扩展模式与 <LANG>_EXTENSIONS 目标属性不匹配,或者 <LANG>_STANDARD 目标属性已设置,则仍可能添加该标志。

编译器扩展的可用性

<LANG>_EXTENSIONS 目标属性默认采用编译器的默认设置(参见 CMAKE_<LANG>_EXTENSIONS_DEFAULT)。注意,由于大多数编译器默认启用扩展,这可能会暴露出用户代码或第三方依赖库头文件中的可移植性错误。

<LANG>_EXTENSIONS 曾经默认为 ON。请参见 CMP0128

可选的编译特性

如果不希望创建硬性要求,可以优先使用编译特性(如果可用)。这可以通过使用 target_compile_features() 指定特性,而是通过项目代码中的预处理器条件来检查编译器能力来实现。

在此用例中,项目可能希望在编译器支持的情况下建立特定的语言标准,并使用预处理器条件来检测实际可用的特性。语言标准可以通过 语言标准需求 使用带有 cxx_std_11 等元特性的 target_compile_features() 来建立,或者通过设置 CXX_STANDARD 目标属性或 CMAKE_CXX_STANDARD 变量来建立。

另请参阅策略 CMP0120 以及关于弃用的 WriteCompilerDetectionHeader 模块的用法示例文档。

条件编译选项

库可能会根据所请求的编译特性提供完全不同的头文件。

例如,with_variadics/interface.h 中的头文件可能包含:

template<int I, int... Is>
struct Interface;

template<int I>
struct Interface<I>
{
  static int accumulate()
  {
    return I;
  }
};

template<int I, int... Is>
struct Interface
{
  static int accumulate()
  {
    return I + Interface<Is...>::accumulate();
  }
};

no_variadics/interface.h 中的头文件可能包含:

template<int I1, int I2 = 0, int I3 = 0, int I4 = 0>
struct Interface
{
  static int accumulate() { return I1 + I2 + I3 + I4; }
};

编写一个包含类似内容的抽象 interface.h 头文件可能是可行的:

#ifdef HAVE_CXX_VARIADIC_TEMPLATES
#include "with_variadics/interface.h"
#else
#include "no_variadics/interface.h"
#endif

然而,如果有大量需要抽象的文件,这可能变得难以维护。真正需要的是根据编译器能力使用可选的包含目录。

CMake 提供了一个 COMPILE_FEATURES 生成器表达式 来实现此类条件。这可以与诸如 target_include_directories()target_link_libraries() 等构建属性命令结合使用,以设置相应的 构建系统 属性。

add_library(foo INTERFACE)
set(with_variadics ${CMAKE_CURRENT_SOURCE_DIR}/with_variadics)
set(no_variadics ${CMAKE_CURRENT_SOURCE_DIR}/no_variadics)
target_include_directories(foo
  INTERFACE
    "$<$<COMPILE_FEATURES:cxx_variadic_templates>:${with_variadics}>"
    "$<$<NOT:$<COMPILE_FEATURES:cxx_variadic_templates>>:${no_variadics}>"
  )

消费端代码只需像往常一样链接到 foo 目标,并使用特性对应的包含目录:

add_executable(consumer_with consumer_with.cpp)
target_link_libraries(consumer_with foo)
set_property(TARGET consumer_with CXX_STANDARD 11)

add_executable(consumer_no consumer_no.cpp)
target_link_libraries(consumer_no foo)

支持的编译器

截至各自指定的版本,CMake 目前能够识别以下 编译器 ID 所支持的 C++ 标准编译特性

  • AppleClang: Xcode 4.4+ 版本的 Apple Clang。

  • Clang: Clang 编译器 2.9+ 版本。

  • GNU: GNU 编译器 4.4+ 版本。

  • MSVC: Microsoft Visual Studio 2010+ 版本。

  • SunPro: Oracle SolarisStudio 12.4+ 版本。

  • Intel: Intel 编译器 12.1+ 版本。

截至各自指定的版本,CMake 目前能够识别以下 编译器 ID 所支持的 C 标准编译特性

  • 上述列出的所有支持 C++ 的编译器及其对应版本。

  • GNU: GNU 编译器 3.4+ 版本。

截至各自指定的版本,CMake 目前能够识别以下 编译器 ID 所支持的 C++ 标准 及其关联的元特性(例如 cxx_std_11):

  • Cray: Cray Compiler Environment 8.1+ 版本。

  • Fujitsu: Fujitsu HPC 编译器 4.0+ 版本。

  • PGI: PGI 12.10+ 版本。

  • NVHPC: NVIDIA HPC 编译器 11.0+ 版本。

  • TI: Texas Instruments 编译器。

  • TIClang: 基于 Texas Instruments Clang 的编译器。

  • XL: IBM XL 10.1+ 版本。

截至各自指定的版本,CMake 目前能够识别以下 编译器 ID 所支持的 C 标准 及其关联的元特性(例如 c_std_99):

  • 上述列出的所有仅支持 C++ 元特性的编译器及其对应版本。

截至各自指定的版本,CMake 目前能够识别以下 编译器 ID 所支持的 CUDA 标准 及其关联的元特性(例如 cuda_std_11):

  • Clang: Clang 编译器 5.0+。

  • NVIDIA: NVIDIA nvcc 编译器 7.5+。

语言标准标志

为了满足 target_compile_features() 命令或 CMAKE_<LANG>_STANDARD 变量指定的需求,CMake 可能会向编译器传递语言标准标志,例如 -std=c++11

对于 Visual Studio 生成器,CMake 无法精确控制语言标准标志在编译器命令行中的放置位置。对于 Ninja 生成器Makefile 生成器 以及 Xcode,CMake 将语言标准标志放置在来自 CMAKE_<LANG>_FLAGSCMAKE_<LANG>_FLAGS_<CONFIG> 的语言级标志之后。

在 3.26 版本中更改:语言标准标志被放置在由其他抽象(如 target_compile_options() 命令)指定的标志之前。在 CMake 3.26 之前,语言标准标志是放置在它们之后的。