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 将通过考虑为每个目标指定的功能来计算要使用的适当编译标志。

即使编译器在没有该标志的情况下支持特定功能,也会添加此类编译标志。例如,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)的常用功能(例如 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() 指定功能,而是在项目代码中使用预处理器条件来检查编译器功能来实现。

在此用例中,项目可能希望在编译器可用时建立特定的语言标准,并使用预处理器条件来检测实际可用的功能。可以通过使用 target_compile_features() 和元功能(如 cxx_std_11)来建立语言标准,或者通过设置 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: Apple Clang for Xcode 版本 4.4+。

  • 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 compiler 4.0+。

  • PGI: PGI 版本 12.10+。

  • NVHPC: NVIDIA HPC compilers 版本 11.0+。

  • TI: Texas Instruments compiler。

  • TIClang: Texas Instruments Clang-based compilers。

  • 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 变量指定的 C++ 标准需求,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 之前,语言标准标志放在它们之后。