cmake-compile-features(7)

简介

项目源代码可能依赖于编译器某些特性的可用性,或者以其为条件。会出现三种用例:编译特性要求可选编译特性条件编译选项

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

CMAKE_C_KNOWN_FEATURESCMAKE_CUDA_KNOWN_FEATURESCMAKE_CXX_KNOWN_FEATURES 全局属性包含 CMake 已知的所有特性,无论编译器是否支持该特性。CMAKE_C_COMPILE_FEATURESCMAKE_CUDA_COMPILE_FEATURESCMAKE_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++11mylib 目标中 C++ 文件的编译行中。如果编译器无法支持该特性,则会发出 FATAL_ERROR

对于此用例,确切的编译标志和语言标准并非用户界面的一部分。CMake 将通过考虑为每个目标指定的特性来计算要使用的适当编译标志。

即使编译器在没有标志的情况下支持特定特性,也会添加此类编译标志。例如,即使使用 -std=gnu++98,GNU 编译器也支持可变参数模板(带有警告)。如果 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 generator expression 来实现此类条件。这可以与构建属性命令(如 target_include_directories()target_link_libraries())一起使用,以设置适当的 buildsystem 属性

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 之前,语言标准标志放置在它们之后。