cmake-compile-features(7)¶
简介¶
项目源代码可能依赖于,或取决于编译器是否支持某些特性。这会产生三种使用场景:编译特性需求、可选编译特性 以及 条件编译选项。
虽然特性通常是在编程语言标准中指定的,但 CMake 提供了一个基于细粒度特性处理的初级用户界面,而不是基于引入该特性的语言标准。
CMAKE_C_KNOWN_FEATURES、CMAKE_CUDA_KNOWN_FEATURES 以及 CMAKE_CXX_KNOWN_FEATURES 全局属性包含了 CMake 所知的所有特性,无论编译器是否支持。而 CMAKE_C_COMPILE_FEATURES、CMAKE_CUDA_COMPILE_FEATURES 以及 CMAKE_CXX_COMPILE_FEATURES 变量则包含了 CMake 已知编译器所支持的所有特性,且与使用它们所需的语言标准或编译标志无关。
CMake 已知特性的命名大多遵循与 Clang 特性测试宏相同的约定。但有一些例外,例如 CMake 使用 cxx_final 和 cxx_override,而不是 Clang 使用的单一 cxx_override_control。
请注意,OBJC 或 OBJCXX 语言没有单独的编译特性属性或变量。它们分别基于 C 或 C++,因此应使用其对应基础语言的属性和变量。
编译特性需求¶
可以通过 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() 的 PUBLIC 或 INTERFACE 签名来指定。
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>_FLAGS 和 CMAKE_<LANG>_FLAGS_<CONFIG> 的语言级标志之后。
在 3.26 版本中更改:语言标准标志被放置在由其他抽象(如 target_compile_options() 命令)指定的标志之前。在 CMake 3.26 之前,语言标准标志是放置在它们之后的。