cmake-compile-features(7)

引言

项目源代码可能取决于或者是有条件地依赖于编译器特定功能的可用性。有三种使用用例会出现这种情况:编译功能要求可选编译功能以及条件编译选项

尽管功能通常在编程语言标准中指定,但 CMake 提供了基于功能的细化处理为主的用户界面,而不是引入该功能的语言标准。

无论编译器是否支持,CMAKE_C_KNOWN_FEATURESCMAKE_CUDA_KNOWN_FEATURES以及CMAKE_CXX_KNOWN_FEATURES全局属性包含已知的所有功能,无论编译器是否支持该功能。无论需要什么语言标准或编译标志来使用它们,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 会通过考虑为每个目标指定的特性来计算要使用的适当编译标志。

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

在此用例中,如果编译器提供,项目可能希望建立特定语言标准,并使用预处理器条件来检测实际可用的特性。可以使用 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 目前知道 C++ 标准编译 特征 可用的 编译器 id 为以下版本指定

  • 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 目前知道 C 标准编译 特征 可用的 编译器 id 为以下版本指定

  • 所有上面列出的 C++ 的编译器和版本。

  • GNU: GNU 编译器版本 3.4+

CMake 目前知道 C++ 标准 和它们的关联元特征(例如 cxx_std_11)可用以下 编译器 id 为以下版本指定

  • Cray: Cray 编译器环境版本 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 目前了解 C 标准及其关联元功能(例如 c_std_99),可从以下 编译器 ID 获得(根据为每个编译器指定的版本)

  • 以上列出的所有编译器和版本,但仅具有 C++ 的元功能。

CMake 目前了解 CUDA 标准 及其关联元功能(例如 cuda_std_11),可从以下 编译器 ID 获得(根据为每个编译器指定的版本)

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