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++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()指定特性,而是在项目代码中使用预处理器条件检查编译器能力来实现。

在此用例中,项目可能希望在编译器可用时建立特定的语言标准,并使用预处理器条件检测实际可用的特性。可以通过要求语言标准(使用带有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 已知以下C++ 标准编译特性,支持版本如下列出的每个编译器 ID

  • 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 已知以下C 标准编译特性,支持版本如下列出的每个编译器 ID

  • 所有上述 C++ 编译器和版本。

  • GNU: GNU 编译器版本 3.4+

截至目前,CMake 已知以下C++ 标准及其相关的元特性(例如 cxx_std_11),支持版本如下列出的每个编译器 ID

  • Cray: Cray Compiler Environment 版本 8.1+。

  • Fujitsu: 富士通 HPC 编译器 4.0+。

  • PGI: PGI 版本 12.10+。

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

  • TI: 德州仪器编译器。

  • TIClang: 德州仪器基于 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 之前,语言标准标志位于其之后。