cmake 工具链(7)

介绍

CMake 使用实用工具链来编译、链接库和创建存档,以及其他任务来驱动构建。可用的工具链实用程序由启用的语言决定。在正常构建中,CMake 会根据系统自检和默认值自动确定主机构建的工具链。在交叉编译场景中,可以使用有关编译器和实用程序路径的信息来指定工具链文件。

在 3.19 版本加入: 可以使用 cmake-presets(7) 来指定工具链文件。

语言

语言由 project() 命令启用。语言特定的内置变量,例如 CMAKE_CXX_COMPILERCMAKE_CXX_COMPILER_ID 等是通过调用:command:project 命令设置的.如果顶级 CMakeLists 文件中没有项目命令,则会隐式生成一个。默认情况下,启用的语言是“C”和“CXX”:

project(C_Only C)

NONE 的特殊值也可以与 project() 命令一起使用以启用无语言:

project(MyProject NONE)

enable_language() 命令可用于在 project() 命令之后启用语言:

enable_language(CXX)

当启用一种语言时,CMake 会找到该语言的编译器,并确定一些信息,例如编译器的供应商和版本、目标体系结构和位宽、相应实用程序的位置等。

ENABLED_LANGUAGES 全局属性包含当前启用的语言。

变量和属性

几个变量与启用的工具链的语言组件相关:

CMAKE_<LANG>_COMPILER

用于 <LANG> 的编译器的完整路径

CMAKE_<LANG>_COMPILER_ID

CMake 使用的编译器标识符

CMAKE_<LANG>_COMPILER_VERSION

编译器的版本。

CMAKE_<LANG>_FLAGS

变量和特定于配置的等效项包含在编译特定语言的文件时将添加到编译命令的标志。

CMake 需要一种方法来确定使用哪个编译器来调用链接器。这由 target 的源文件的 LANGUAGE 属性决定,在静态库的情况下,依赖库的 LANGUAGE . CMake 所做的选择可能会被 LINKER_LANGUAGE 目标属性覆盖。

工具链功能

CMake 提供 try_compile() 命令和包装器宏,如 CheckCXXSourceCompilesCheckCXXSymbolExistsCheckIncludeFile 来测试各种工具链特性的能力和可用性。这些 API 以某种方式测试工具链并缓存结果,以便下次运行 CMake 时不必再次执行测试。

一些工具链功能在 CMake 中具有内置处理,不需要编译测试。例如, POSITION_INDEPENDENT_CODE 允许指定目标应该构建为位置无关代码,如果编译器支持该功能的话。 <LANG>_VISIBILITY_PRESETVISIBILITY_INLINES_HIDDEN 目标属性为隐藏可见性添加标志(如果编译器支持)。

交叉编译

如果使用命令行参数调用 cmake(1) --toolchain path/to/file-DCMAKE_TOOLCHAIN_FILE=path/to/file,该文件将提前加载以设置编译器的值。 CMAKE_CROSSCOMPILING 变量在 CMake 交叉编译时设置为 true。

请注意,在工具链文件中使用 CMAKE_SOURCE_DIRCMAKE_BINARY_DIR 变量通常是不可取的。工具链文件用于这些变量在不同位置使用时具有不同值的上下文(例如,作为调用 try_compile() 的一部分)。在大多数情况下,需要评估工具链文件内的路径时,更合适使用的变量是 CMAKE_CURRENT_LIST_DIR,因为它始终具有明确的、可预测的值。

Linux 交叉编译

一个典型的 Linux 交叉编译工具链有如下内容:

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_SYSROOT /home/devel/rasp-pi-rootfs)
set(CMAKE_STAGING_PREFIX /home/devel/stage)

set(tools /home/devel/gcc-4.7-linaro-rpi-gnueabihf)
set(CMAKE_C_COMPILER ${tools}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/arm-linux-gnueabihf-g++)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

在哪里:

CMAKE_SYSTEM_NAME

是要构建的目标平台的 CMake 标识符。

CMAKE_SYSTEM_PROCESSOR

是目标架构的 CMake 标识符。

CMAKE_SYSROOT

是可选的,如果 sysroot 可用,则可以指定。

CMAKE_STAGING_PREFIX

也是可选的。它可用于指定要安装到的主机上的路径。 CMAKE_INSTALL_PREFIX 始终是运行时安装位置,即使在交叉编译时也是如此。

CMAKE_<LANG>_COMPILER

变量可以设置为完整路径,或设置为在标准位置搜索的编译器名称。对于不支持链接没有自定义标志或脚本的二进制文件的工具链,可以将 CMAKE_TRY_COMPILE_TARGET_TYPE 变量设置为 STATIC_LIBRARY 以告诉 CMake 在检查期间不要尝试链接可执行文件。

在所有情况下,CMake find_* 命令将在 sysroot 和 CMAKE_FIND_ROOT_PATH 条目中查找默认情况下,以及在主机系统根前缀中查找。虽然这可以根据具体情况进行控制,但在交叉编译时,排除在主机或目标中查找特定工件可能很有用。通常,包含、库和包应该在目标系统前缀中找到,而必须作为构建的一部分运行的可执行文件应该只在主机上而不是在目标上找到。这是 CMAKE_FIND_ROOT_PATH_MODE_* 变量的目的。

Cray Linux 环境的交叉编译

无需单独的工具链文件即可在 Cray Linux 环境中对计算节点进行交叉编译。在 CMake 命令行上指定“-DCMAKE_SYSTEM_NAME=CrayLinuxEnvironment”将确保配置适当的构建设置和搜索路径。该平台将从当前环境变量中提取其配置,并将配置项目以使用来自 Cray 编程环境的“PrgEnv-*”模块(如果存在并已加载)的编译器包装器。

Cray 编程环境的默认配置是只支持静态库。这可以通过将“CRAYPE_LINK_TYPE”环境变量设置为“动态”来覆盖和启用共享库。

在不指定 CMAKE_SYSTEM_NAME 的情况下运行 CMake 将在假设标准 Linux 环境的主机模式下运行配置步骤。如果不被覆盖,PrgEnv-* 编译器包装器将最终被使用,如果以登录节点或计算节点为目标,这可能不是所需的行为。如果您直接在 NID 上构建而不是从登录节点交叉编译,则例外。如果尝试为登录节点构建软件,您需要先卸载当前加载的 PrgEnv-* 模块或明确告诉 CMake 使用 /usr/bin 中的系统编译器而不是 Cray包装纸。如果需要以计算节点为目标,只需指定 CMAKE_SYSTEM_NAME 如上所述。

使用 Clang 进行交叉编译

某些编译器(例如 Clang)本质上是交叉编译器。 CMAKE_<LANG>_COMPILER_TARGET 可以设置为在编译时将值传递给那些支持的编译器:

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(triple arm-linux-gnueabihf)

set(CMAKE_C_COMPILER clang)
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_CXX_COMPILER_TARGET ${triple})

类似地,一些编译器不提供它们自己的补充实用程序(如链接器),但提供一种方法来指定编译器驱动程序将使用的外部工具链的位置。 CMAKE_<LANG>_COMPILER_EXTERNAL_TOOLCHAIN 变量可以在工具链文件中设置,以将路径传递给编译器驱动程序。

QNX 交叉编译

作为 Clang 编译器,QNX QCC 编译本质上是一个交叉编译器。并且 CMAKE_<LANG>_COMPILER_TARGET 可以设置为在编译时将值传递给那些支持的编译器:

set(CMAKE_SYSTEM_NAME QNX)

set(arch gcc_ntoarmv7le)

set(CMAKE_C_COMPILER qcc)
set(CMAKE_C_COMPILER_TARGET ${arch})
set(CMAKE_CXX_COMPILER QCC)
set(CMAKE_CXX_COMPILER_TARGET ${arch})

set(CMAKE_SYSROOT $ENV{QNX_TARGET})

Windows CE 交叉编译

Windows CE 的交叉编译需要在您的系统上安装相应的 SDK。这些 SDK 通常安装在“C:/Program Files (x86)/Windows CE Tools/SDKs”下。

为 Windows CE 配置 Visual Studio 生成器的工具链文件可能如下所示:

set(CMAKE_SYSTEM_NAME WindowsCE)

set(CMAKE_SYSTEM_VERSION 8.0)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_GENERATOR_TOOLSET CE800) # Can be omitted for 8.0
set(CMAKE_GENERATOR_PLATFORM SDK_AM335X_SK_WEC2013_V310)

CMAKE_GENERATOR_PLATFORM 告诉生成器要使用哪个 SDK。此外 CMAKE_SYSTEM_VERSION 告诉生成器要使用的 Windows CE 版本。当前版本 8.0 (Windows Embedded Compact 2013) 开箱即用。其他版本可能需要将 CMAKE_GENERATOR_TOOLSET 设置为正确的值。

Windows 10 通用应用程序的交叉编译

为 Windows 10 通用应用程序配置 Visual Studio 生成器的工具链文件可能如下所示:

set(CMAKE_SYSTEM_NAME WindowsStore)
set(CMAKE_SYSTEM_VERSION 10.0)

Windows 10 通用应用程序同时针对 Windows 应用商店和 Windows Phone。将 CMAKE_SYSTEM_VERSION 变量指定为 10.0 以使用最新可用的 Windows 10 SDK 进行构建。指定一个更具体的版本(例如 10.0.10240.0 用于 RTM)以使用相应的 SDK 构建。

Windows Phone 交叉编译

为 Windows Phone 配置 Visual Studio 生成器的工具链文件可能如下所示:

set(CMAKE_SYSTEM_NAME WindowsPhone)
set(CMAKE_SYSTEM_VERSION 8.1)

Windows 应用商店的交叉编译

用于为 Windows 应用商店配置 Visual Studio 生成器的工具链文件可能如下所示:

set(CMAKE_SYSTEM_NAME WindowsStore)
set(CMAKE_SYSTEM_VERSION 8.1)

ADSP SHARC/Blackfin 交叉编译

ADSP SHARC 或 Blackfin 的交叉编译可以通过将 CMAKE_SYSTEM_NAME 变量设置为 ADSP 并将 CMAKE_SYSTEM_PROCESSOR 变量设置为“部件号”来配置,不包括 ADSP-` ` 前缀,例如,``21594SC589 等。此值不区分大小写。

CMake 将自动在默认安装位置搜索 CCES 或 VDSP++ 安装,并选择找到的最新版本。如果两者都安装,将选择 CCES 而不是 VDSP++。可以通过 CMAKE_ADSP_ROOT 变量或 ADSP_ROOT 环境变量设置自定义安装路径。

编译器(cc21kccblkfn)根据提供的 CMAKE_SYSTEM_PROCESSOR 值自动选择。

安卓交叉编译

工具链文件可以通过将 CMAKE_SYSTEM_NAME 变量设置为 Android 来为 Android 配置交叉编译。进一步配置特定于要使用的 Android 开发环境。

对于 Visual Studio 生成器,CMake 期望:ref:NVIDIA Nsight Tegra Visual Studio Edition <Cross Compiling for Android with NVIDIA Nsight Tegra Visual Studio Edition>Android 的 Visual Studio 工具 的 Android。有关更多配置详细信息,请参阅这些部分。

对于 Makefile Generators 和 Ninja 生成器,CMake 需要以下环境之一:

  • NDK

  • 独立工具链

CMake 使用以下步骤来选择其中一个环境:

  • 如果设置了 CMAKE_ANDROID_NDK 变量,将使用指定位置的 NDK。

  • 否则,如果设置了 CMAKE_ANDROID_STANDALONE_TOOLCHAIN 变量,将使用指定位置的独立工具链。

  • 否则,如果 CMAKE_SYSROOT 变量设置为``<ndk>/platforms/android-<api>/arch-<arch>`` 形式的目录,<ndk> 部分将用作 CMAKE_ANDROID_NDK 的值,并且将使用 NDK。

  • 否则,如果 CMAKE_SYSROOT 变量设置为 <standalone-toolchain>/sysroot 形式的目录,则 <standalone-toolchain> 部分将用作以下值 CMAKE_ANDROID_STANDALONE_TOOLCHAIN 将使用独立工具链。

  • 否则,如果设置了 cmake 变量 ANDROID_NDK,它将用作 CMAKE_ANDROID_NDK 的值,并且将使用 NDK。

  • 否则,如果设置了 cmake 变量 ANDROID_STANDALONE_TOOLCHAIN,它将用作 CMAKE_ANDROID_STANDALONE_TOOLCHAIN 的值,并且将使用独立工具链。

  • 否则,如果设置了环境变量 ANDROID_NDK_ROOTANDROID_NDK,它将用作 CMAKE_ANDROID_NDK 的值,并使用 NDK。

  • 否则,如果设置了环境变量 ANDROID_STANDALONE_TOOLCHAIN,那么它将用作 CMAKE_ANDROID_STANDALONE_TOOLCHAIN 的值,并且将使用独立工具链。

  • 否则,将发出错误诊断,指出找不到 NDK 或独立工具链。

在 3.20 版本加入: 如果选择了 Android NDK,其版本号将在 CMAKE_ANDROID_NDK_VERSION 变量中报告。

使用 NDK 为 Android 交叉编译

工具链文件可以配置 Makefile Generators、Ninja Generators 或 Visual Studio Generators 以针对 Android 进行交叉编译。

使用以下变量配置 Android NDK 的使用:

CMAKE_SYSTEM_NAME

设置为“Android”。必须指定才能为 Android 启用交叉编译。

CMAKE_SYSTEM_VERSION

设置为 Android API 级别。如果未指定,则按如下方式确定该值:

  • 如果设置了 CMAKE_ANDROID_API 变量,它的值将用作 API 级别。

  • 如果设置了 CMAKE_SYSROOT 变量,则会从包含 sysroot 的 NDK 目录结构中检测 API 级别。

  • 否则,将使用 NDK 中可用的最新 API 级别。

CMAKE_ANDROID_ARCH_ABI

设置为 Android ABI(架构)。如果未指定,此变量将默认为 armeabiarmeabi-v7aarm64-v8a 列表中第一个支持的 ABI。 CMAKE_ANDROID_ARCH 变量将根据 CMAKE_ANDROID_ARCH_ABI 自动计算。另请参阅 CMAKE_ANDROID_ARM_MODECMAKE_ANDROID_ARM_NEON 变量。

CMAKE_ANDROID_NDK

设置为 Android NDK 根目录的绝对路径。如果未指定,将选择此变量的默认值,如上文 <Android 交叉编译> 所指定:ref:`

CMAKE_ANDROID_NDK_DEPRECATED_HEADERS

设置为 true 值以使用已弃用的 per-api-level 标头而不是统一标头。如果未指定,则默认值为 false,除非使用不提供统一标头的 NDK。

CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION

在 NDK r19 或更高版本上,此变量必须取消设置或设置为 clang。在 NDK r18 或更低版本上,将此设置为要选择作为编译器的 NDK 工具链的版本。如果未指定,默认将是最新可用的 GCC 工具链。

CMAKE_ANDROID_STL_TYPE

设置以指定要使用的 C++ 标准库。如果未指定,将选择默认值,如变量文档中所述。

将自动计算并提供以下变量:

CMAKE_<LANG>_ANDROID_TOOLCHAIN_PREFIX

NDK 工具链中 binutils 的绝对路径前缀。

CMAKE_<LANG>_ANDROID_TOOLCHAIN_SUFFIX

NDK 工具链中 binutils 的主机平台后缀。

例如,工具链文件可能包含:

set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_SYSTEM_VERSION 21) # API level
set(CMAKE_ANDROID_ARCH_ABI arm64-v8a)
set(CMAKE_ANDROID_NDK /path/to/android-ndk)
set(CMAKE_ANDROID_STL_TYPE gnustl_static)

或者,可以在没有工具链文件的情况下指定值:

$ cmake ../src \
  -DCMAKE_SYSTEM_NAME=Android \
  -DCMAKE_SYSTEM_VERSION=21 \
  -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a \
  -DCMAKE_ANDROID_NDK=/path/to/android-ndk \
  -DCMAKE_ANDROID_STL_TYPE=gnustl_static

使用独立工具链为 Android 进行交叉编译

工具链文件可以配置 Makefile Generators 或 Ninja 生成器以使用独立工具链以 Android 为目标进行交叉编译。

使用以下变量配置 Android 独立工具链的使用:

CMAKE_SYSTEM_NAME

设置为“Android”。必须指定才能为 Android 启用交叉编译。

CMAKE_ANDROID_STANDALONE_TOOLCHAIN

设置为独立工具链根目录的绝对路径。 ${CMAKE_ANDROID_STANDALONE_TOOLCHAIN}/sysroot 目录必须存在。如果未指定,将选择此变量的默认值,如上文 <Android 交叉编译> 所指定:ref:`

CMAKE_ANDROID_ARM_MODE

当独立工具链以 ARM 为目标时,可选择将其设置为“ON”以以 32 位 ARM 而不是 16 位 Thumb 为目标。有关详细信息,请参阅变量文档。

CMAKE_ANDROID_ARM_NEON

当独立工具链以 ARM v7 为目标时,可选择将此设置为“ON”以以 ARM NEON 设备为目标。有关详细信息,请参阅变量文档。

将自动计算并提供以下变量:

CMAKE_SYSTEM_VERSION

从独立工具链中检测到的 Android API 级别。

CMAKE_ANDROID_ARCH_ABI

从独立工具链中检测到的 Android ABI。

CMAKE_<LANG>_ANDROID_TOOLCHAIN_PREFIX

独立工具链中 binutils 的绝对路径前缀。

CMAKE_<LANG>_ANDROID_TOOLCHAIN_SUFFIX

独立工具链中 binutils 的主机平台后缀。

例如,工具链文件可能包含:

set(CMAKE_SYSTEM_NAME Android)
set(CMAKE_ANDROID_STANDALONE_TOOLCHAIN /path/to/android-toolchain)

或者,可以在没有工具链文件的情况下指定值:

$ cmake ../src \
  -DCMAKE_SYSTEM_NAME=Android \
  -DCMAKE_ANDROID_STANDALONE_TOOLCHAIN=/path/to/android-toolchain

使用 NVIDIA Nsight Tegra Visual Studio Edition 为 Android 交叉编译

用于配置 Visual Studio Generators 之一以使用针对 Android 的 NVIDIA Nsight Tegra 构建的工具链文件可能如下所示:

set(CMAKE_SYSTEM_NAME Android)

CMAKE_GENERATOR_TOOLSET 可以设置为选择 Nsight Tegra“工具链版本”值。

另请参阅目标属性:

针对 iOS、tvOS 或 watchOS 的交叉编译

对于 iOS、tvOS 或 watchOS 的交叉编译,推荐使用 Xcode 生成器。 Unix MakefilesNinja 生成器也可以使用,但它们需要项目处理更多领域,例如目标 CPU 选择和代码签名。

可以通过将 CMAKE_SYSTEM_NAME 变量设置为下表中的值来定位这三个系统中的任何一个。默认情况下,会选择最新的 Device SDK。对于所有 Apple 平台,可以通过设置 CMAKE_OSX_SYSROOT 变量来选择不同的 SDK(例如模拟器),尽管很少有必要这样做(参见下面的 在设备和模拟器之间切换)。可以通过运行 xcodebuild -showsdks 获得可用 SDK 的列表。

操作系统

CMAKE_SYSTEM_NAME

设备 SDK(默认)

模拟器SDK

iOS

iOS

苹果手机

iPhone模拟器

电视操作系统

电视操作系统

小程序

小程序vs模拟器

watchOS

watchOS

手表

手表模拟器

例如,要为 iOS 创建 CMake 配置,以下命令就足够了:

cmake .. -GXcode -DCMAKE_SYSTEM_NAME=iOS

变量 CMAKE_OSX_ARCHITECTURES 可用于设置设备和模拟器的架构。变量 CMAKE_OSX_DEPLOYMENT_TARGET 可用于设置 iOS/tvOS/watchOS 部署目标。

接下来的配置将安装 fat 5 架构 iOS 库,并向编译器添加 -miphoneos-version-min=9.3/-mios-simulator-version-min=9.3 标志:

$ cmake -S. -B_builds -GXcode \
    -DCMAKE_SYSTEM_NAME=iOS \
    "-DCMAKE_OSX_ARCHITECTURES=armv7;armv7s;arm64;i386;x86_64" \
    -DCMAKE_OSX_DEPLOYMENT_TARGET=9.3 \
    -DCMAKE_INSTALL_PREFIX=`pwd`/_install \
    -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \
    -DCMAKE_IOS_INSTALL_COMBINED=YES

例子:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(foo)
add_library(foo foo.cpp)
install(TARGETS foo DESTINATION lib)

安装:

$ cmake --build _builds --config Release --target install

检查库:

$ lipo -info _install/lib/libfoo.a
Architectures in the fat file: _install/lib/libfoo.a are: i386 armv7 armv7s x86_64 arm64
$ otool -l _install/lib/libfoo.a | grep -A2 LC_VERSION_MIN_IPHONEOS
      cmd LC_VERSION_MIN_IPHONEOS
  cmdsize 16
  version 9.3

代码签名

嵌入式 Apple 平台的一些构建工件需要强制代码签名。如果正在使用 Xcode 生成器并且需要或希望进行代码签名,则可以通过 CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM CMake 变量指定开发团队 ID。然后,这个团队 ID 将包含在生成的 Xcode 项目中。默认情况下,CMake 避免在内部配置阶段(即编译器 ID 和功能检测)进行代码签名。

在设备和模拟器之间切换

在为任何嵌入式平台配置时,可以针对真实设备或模拟器。两者都有自己独立的 SDK,但 CMake 只支持在配置阶段指定一个 SDK。这意味着开发人员必须在配置时选择其中之一。使用 Xcode 生成器时,这不是一个限制,因为 Xcode 仍然允许您为设备或模拟器构建,即使只为两者之一执行配置。在 Xcode IDE 中,构建是针对选定的“目标”平台执行的。从命令行构建时,可以通过将“-sdk”选项传递给底层构建工具(“xcodebuild”)来直接指定所需的 sdk。例如:

$ cmake --build ... -- -sdk iphonesimulator

请注意,在配置期间进行的检查是针对配置时 SDK 执行的,可能不适用于其他 SDK。 find_package()find_library() 等命令仅为配置的 SDK/平台存储和使用详细信息,因此如果想要在设备和模拟器构建之间切换,它们可能会出现问题。您可以按照以下规则使设备 + 模拟器配置工作:

  • 使用明确的“-l”链接器标志,例如``target_link_libraries(foo PUBLIC "-lz")``

  • 使用明确的“-framework”链接器标志,例如``target_link_libraries(foo PUBLIC "-framework CoreFoundation")``

  • 仅对使用 CMAKE_IOS_INSTALL_COMBINED 功能安装的库使用 find_package()