使用 Corrosion

使用 corrosion_import_crate 自动导入 crate 目标

为了将 Rust crate 集成到 CMake 中,你首先需要从 packageworkspace 导入 Rust crates。Corrosion 提供 corrosion_import_crate() 来自动导入在 Cargo.toml 清单文件中定义的 crates:

  1. corrosion_import_crate(
  2. MANIFEST_PATH <path/to/cargo.toml>
  3. [ALL_FEATURES]
  4. [NO_DEFAULT_FEATURES]
  5. [NO_STD]
  6. [NO_LINKER_OVERRIDE]
  7. [LOCKED]
  8. [FROZEN]
  9. [PROFILE <cargo-profile>]
  10. [IMPORTED_CRATES <variable-name>]
  11. [CRATE_TYPES <crate_type1> ... <crate_typeN>]
  12. [CRATES <crate1> ... <crateN>]
  13. [FEATURES <feature1> ... <featureN>]
  14. [FLAGS <flag1> ... <flagN>]
  15. )
  • MANIFEST_PATH: 指向 Cargo.toml 清单 文件的路径。
  • ALL_FEATURES: 等同于传递给 cargo build 的 --all-features
  • NO_DEFAULT_FEATURES: 等同于传递给 cargo build 的 --no-default-features
  • NO_STD: 禁用标准库的链接(对于 no_std crates 是必需的)。
  • NO_LINKER_OVERRIDE: 让 Rust/Cargo 确定使用哪个链接器,而不是腐蚀(当 Rust 调用链接时)。
  • LOCKED: 传递 --locked 给 cargo build 和 cargo metadata。
  • FROZEN: 传递 --frozen 给 cargo build 和 cargo metadata。
  • PROFILE: 指定 cargo build 配置文件(dev/release 或一个 自定义配置文件; benchtest 不受支持)。
  • IMPORTED_CRATES: 将导入的 crates 列表保存到当前作用域中提供名称的变量中。
  • CRATE_TYPES: 只导入指定类型的 crate。有效值:staticlib, cdylib, bin
  • CRATES: 从工作区中只导入指定的 crates。值:Crate 名称。
  • FEATURES: 启用指定的特性。等同于传递给 cargo build--features
  • FLAGS: 传递给 cargo build 的任意标志。

Corrosion 将使用 cargo metadata 为清单文件中定义的每个 crate 添加一个 cmake 目标,并添加构建目标所需的规则。对于 Rust 可执行文件,将创建一个与清单文件中 [[bin]] 部分定义的名称相同的 IMPORTED 可执行目标。如果没有定义这样的名称,则目标名称默认为 Rust 包的名称。对于 Rust 库目标,将创建一个与清单文件中 [lib] 部分定义的名称相同的 INTERFACE 库目标。这个 INTERFACE 库链接了一个内部腐蚀目标,根据 Rust crate 类型(cdylibstaticlib),这是一个 SHAREDSTATIC IMPORTED 库。

创建的库目标可以通过简单地使用 target_link_libraries 链接到其他 CMake 目标中。

Corrosion 默认将生成的 Rust 工件复制到 ${CMAKE_CURRENT_BINARY_DIR}。可以通过在导入的 Rust 目标上设置 CMake OUTPUT_DIRECTORY 目标属性来更改目标位置。有关更多详细信息,请参见 OUTPUT_DIRECTORY 部分。

corrosion_import_crate 可用的许多选项也可以为每个目标单独设置,请参阅 每个目标的选项 了解详细信息。

每个目标的选项

可以为每个目标单独指定一些配置选项。你可以通过下面指定的 corrosion_set_xxx() 函数来设置它们:

  • corrosion_set_env_vars(<target_name> <key1=value1> [... <keyN=valueN>]): 为指定目标定义在调用 cargo build 期间应设置的环境变量。请注意,环境变量只会在通过 cmake 直接构建目标时设置,而不会在 cargo 将所讨论的 crate 作为另一个目标的依赖项构建的任何构建中设置。环境变量可能包含生成器表达式。
  • corrosion_add_target_rustflags(<target_name> <rustflag> [... <rustflagN>]): 在构建目标时,RUSTFLAGS 环境变量将包含通过此函数添加的标志。请注意,任何依赖项(由 cargo 构建)也将看到这些标志。另见:corrosion_add_target_local_rustflags
  • corrosion_add_target_local_rustflags(target_name rustc_flag [more_flags ...]): 支持仅为主目标(crate)设置 rustflags,而不包括其任何依赖项。这在仅需要在主 crate 上设置 rustflags,但需要为不同目标设置不同标志的情况下很有用。如果没有“local” Rustflags,这将需要重建依赖项,当切换目标时。
  • corrosion_set_hostbuild(<target_name>): 目标应该为主机目标编译并忽略任何交叉编译配置。
  • corrosion_set_features(<target_name> [ALL_FEATURES <Bool>] [NO_DEFAULT_FEATURES] [FEATURES <feature1> ... ]): 对于给定的目标,通过 FEATURES 启用特定特性,切换 ALL_FEATURES 开或关,或通过 NO_DEFAULT_FEATURES 禁用所有特性。有关特性的更多信息,请参阅 cargo 参考
  • corrosion_set_cargo_flags(<target_name> <flag1> ...]): 对于给定的目标,在 cargo build 调用的末尾添加选项和标志。这将在 crate 导入期间通过 FLAGS 传递的任何参数之后追加。
  • corrosion_set_linker(target_name linker): 使用 linker 链接目标。请注意,这只对由 cargo 执行最终链接调用的目标有效,即链接外来代码到 Rust 代码的目标,而不是相反。另请注意,如果你正在交叉编译并指定一个链接器,如 clang,你需要负责添加一个 rustflag,该 rustflag 添加了必要的 --target= 参数给链接器。

全局 Corrosion 选项

所有以下变量在大多数情况下都会自动评估。在典型情况下,你不需要更改其中任何一个。如果你想手动指定它们,请确保在 find_package(Corrosion REQUIRED) 之前 设置它们。

  • Rust_TOOLCHAIN:STRING - 指定要使用的 rustup 工具链名称。更改此变量将重置所有其他选项。默认:如果首先找到的 rustc 是 rustup 代理,则使用默认的 rustup 工具链(参见 rustup show)。否则,该变量默认未设置。
  • Rust_ROOT:STRING - CMake 提供。要使用的 Rust 工具链的路径。如果你想要选择一个特定的 Rust 工具链,但它不受 rustup 管理,这是另一种选择。默认:无。
  • Rust_COMPILER:STRING - 应使用的 rustc 的路径,用于编译或工具链检测(如果它是 rustup 代理)。默认:来自 rustup 或用户 PATH 中可用的工具链的第一个 rustc
  • Rust_RESOLVE_RUSTUP_TOOLCHAINS:BOOL - 如果找到的 rustc 是 rustup 代理,根据 rustup 工具链选择规则和其他选项详细说明,解析到由 rustup 管理的特定工具链的具体路径。如果关闭此选项,即使找到的 rustc 是 rustup 代理,也将使用找到的 rustc 进行编译,这可能会增加编译时间。默认:如果找到的 rustc 是 rustup 代理或请求了 rustup 管理的工具链,则为 ON;否则为 OFF。如果没有找到 rustup,则强制为 OFF

  • Rust_CARGO:STRING - cargo 的路径。默认:安装在 ${Rust_COMPILER} 旁边的 cargo

  • Rust_CARGO_TARGET:STRING - 默认目标三元组用于构建。交叉编译时修改。默认:在 Visual Studio 生成器上,匹配 CMAKE_VS_PLATFORM_NAME 的三元组。否则,由 ${Rust_COMPILER} --version --verbose 报告的默认目标三元组。

开发者/维护者选项

这些选项在正常使用 Corrosion 时不会用到,但用于配置 Corrosion 的构建和安装方式。仅适用于 Corrosion 构建和子目录使用。

  • CORROSION_BUILD_TESTS:BOOL - 构建 Corrosion 测试。默认:如果 Corrosion 是子目录,则为 Off;如果它是顶层项目,则为 ON

Corrosion 提供的信息

为了方便,Corrosion 设置了许多变量,其中包含有关 Rust 工具链版本的信息。你可以使用 CMake 版本比较运算符(例如 VERSION_GREATER_EQUAL)在主变量上(例如 if(Rust_VERSION VERSION_GREATER_EQUAL "1.57.0")),或者你可以单独检查主要、次要和补丁版本的变量。

  • Rust_VERSION<_MAJOR|_MINOR|_PATCH> - rustc 的版本。
  • Rust_CARGO_VERSION<_MAJOR|_MINOR|_PATCH> - cargo 版本。
  • Rust_LLVM_VERSION<_MAJOR|_MINOR|_PATCH> - rustc 使用的 LLVM 版本。
  • Rust_IS_NIGHTLY - 如果使用夜间工具链,则为 1,否则为 0。对于仅在夜间工具链上可用的 crate 的不稳定特性很有用。
  • 缓存变量,包含有关所选目标的目标三元组的默认主机目标的信息:
    • Rust_CARGO_TARGET_ARCH, Rust_CARGO_HOST_ARCH:例如 x86_64aarch64
    • Rust_CARGO_TARGET_VENDOR, Rust_CARGO_HOST_VENDOR:例如 applepcunknown
    • Rust_CARGO_TARGET_OS, Rust_CARGO_HOST_OS:例如 darwinlinuxwindowsnone
    • Rust_CARGO_TARGET_ENV, Rust_CARGO_HOST_ENV:例如 gnumusl

选择自定义 cargo 配置文件

Rust 1.57 稳定了对自定义 配置文件 的支持。如果你使用的是足够新的 rust 工具链,你可以通过向 corrosion_import_crate() 添加可选参数 PROFILE <profile_name> 来选择自定义配置文件。如果你没有指定配置文件,或者你使用的是旧版工具链,腐蚀将根据 CMake 配置是 Debug 或未指定来选择标准 dev 配置文件。在所有其他情况下,对于 cargo,选择 release 配置文件。

导入用 Rust 编写的 C 风格库

Corrosion 使得将 crate 导入到现有 CMake 项目中变得非常容易。考虑一个名为 rust2cpp 的项目,其文件结构如下:

  1. rust2cpp/
  2. rust/
  3. src/
  4. lib.rs
  5. Cargo.lock
  6. Cargo.toml
  7. CMakeLists.txt
  8. main.cpp

该项目定义了一个简单的 Rust lib crate,如在 rust2cpp/rust/Cargo.toml 中所示:

  1. [package]
  2. name = "rust-lib"
  3. version = "0.1.0"
  4. authors = ["Andrew Gaspar <andrew.gaspar@outlook.com>"]
  5. license = "MIT"
  6. edition = "2018"
  7. [dependencies]
  8. [lib]
  9. crate-type=["staticlib"]

除了 "staticlib",你还可以使用 "cdylib"。实际上,你可以使用单个 crate 定义两者,并使用标准的 BUILD_SHARED_LIBS 变量在它们之间切换。

这个 crate 定义了一个简单的 crate,名为 rust-lib。将此 crate 导入到你的 CMakeLists.txt 中非常简单:

  1. # 注意:你必须已经包含了 Corrosion,以便 `corrosion_import_crate` 可用。参见上面的 `安装` 部分。
  2. corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml)

现在你已经将 crate 导入到 CMake 中,Rust 中定义的所有可执行文件、静态库和动态库都可以直接引用。因此,只需像平常一样在 CMake 中定义你的 C++ 可执行文件,并使用 target_link_libraries 添加你的 crate 库:

  1. add_executable(cpp-exe main.cpp)
  2. target_link_libraries(cpp-exe PUBLIC rust-lib)

就是这样!你现在正在将你的 Rust 库链接到你的 C++ 库。

自动生成 Rust 库的绑定

目前,你必须在 C 或 C++ 程序中手动声明对你 Rust 项目中导出的例程和类型的绑定。你可以在 Rust 代码C++ 代码 中看到这一点的两面。

计划将来与 cbindgen 集成。

将用 C 和 C++ 编写的库导入到 Rust 中

可以使用 corrosion_import_crate() 将 rust 目标导入到 CMake 中。对于那些应该由 Rust 调用链接器的目标,Corrosion 提供了 corrosion_link_libraries() 将你的 C/C++ 库与 Rust 目标链接。对于额外的链接器标志,你可以使用 corrosion_add_target_local_rustflags() 并通过 -Clink-args 标志将链接器参数传递给 rustc。这些标志只会传递给最终的 rustc 调用,并且不会影响任何 rust 依赖项。

C 绑定可以通过 bindgen 生成。Corrosion 还没有提供直接集成,但你可以在 crate 的构建脚本中生成绑定,或者将绑定生成作为 CMake 构建步骤(例如自定义目标)并从 cargo-prebuild_<rust_target> 到你的自定义目标添加依赖,以生成绑定。

示例:

  1. # 导入你的 Rust 目标
  2. corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml)
  3. # 将你的 C/C++ 库与 Rust 目标链接
  4. corrosion_link_libraries(target_name c_library)
  5. # 可选地明确定义使用哪个链接器。
  6. corrosion_set_linker(target_name your_custom_linker)
  7. # 可选地设置链接器参数
  8. corrosion_add_target_local_rustflags(target_name "-Clink-args=<linker arguments>")
  9. # 可选地告诉 CMake rust crate 依赖于另一个目标(例如代码生成器)
  10. add_dependencies(cargo-prebuild_<target_name> custom_bindings_target)

交叉编译

Corrosion 尽可能普遍地支持交叉编译,尽管并非所有配置都经过测试。在以下场景中明确支持交叉编译。

在所有情况下,你将需要为 Rust 目标三元组安装标准库。当使用 Rustup 时,你可以使用它来安装目标标准库:

rustup target add <target-rust-triple>

如果目标三元组是自动派生的,Corrosion 会在配置期间打印目标。例如:

-- Rust Target: aarch64-linux-android

Windows 到 Windows

Corrosion 支持使用 Visual Studio 生成器在任意 Windows 架构之间进行交叉编译。例如,要从任何平台为 ARM64 交叉编译,只需设置 -A 架构标志:

  1. cmake -S. -Bbuild-arm64 -A ARM64
  2. cmake --build build-arm64

请注意,对于包含构建脚本的项目,至少需要 Rust 1.54 版本,因为之前版本的 cargo 存在一个 bug,导致构建脚本错误地为目标平台构建。

Linux 到 Linux

为了在 Linux 上进行交叉编译,你需要安装交叉编译器。例如,在 Ubuntu 上,要为 64 位小端 PowerPC 小端交叉编译,从 apt-get 安装 g++-powerpc64le-linux-gnu

  1. sudo apt install g++-powerpc64le-linux-gnu

目前,Corrosion 在 Linux 上交叉编译时不会自动确定目标三元组,因此你需要指定一个匹配的 Rust_CARGO_TARGET

  1. cmake -S. -Bbuild-ppc64le -DRust_CARGO_TARGET=powerpc64le-unknown-linux-gnu -DCMAKE_CXX_COMPILER=powerpc64le-linux-gnu-g++
  2. cmake --build build-ppc64le

Android

在所有平台上,Corrosion 支持使用 Makefile 和 Ninja 生成器进行 Android 交叉编译,并且 Rust 目标三元组将自动被选择。这里适用 CMake 的 Android 交叉编译指令。例如,要为 ARM64 构建:

  1. cmake -S. -Bbuild-android-arm64 -GNinja -DCMAKE_SYSTEM_NAME=Android \
  2. -DCMAKE_ANDROID_NDK=/path/to/android-ndk-rxxd -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a

重要说明: Android SDK 随附的 CMake 是 3.10 版本,这是最新的,Android Studio 会优先使用它而不是你本地安装的任何 CMake。CMake 3.10 对于使用 Corrosion 是不够的,因为 Corrosion 需要至少 CMake 3.22。如果你使用 Android Studio 构建你的项目,请按照 Android Studio 文档中的说明 使用特定版本的 CMake

CMake OUTPUT_DIRECTORY 目标属性和 IMPORTED_LOCATION

Corrosion 尊重以下 OUTPUT_DIRECTORY 目标属性:

如果设置了目标属性(例如,在调用 corrosion_import_crate() 之前定义了 CMAKE_XYZ_OUTPUT_DIRECTORY 变量),Corrosion 会将构建的 Rust 工件复制到目标属性中定义的位置。由于 CMake 的限制,这些目标属性会以延迟的方式评估,以支持用户在调用 corrosion_import_crate() 之后设置目标属性。这有一个副作用,即 IMPORTED_LOCATION 属性会晚些设置,用户不应该在配置时使用 get_property 读取 IMPORTED_LOCATION。相反,应该使用生成器表达式来获取目标工件的位置。如果需要在配置时使用 IMPORTED_LOCATION,用户可以使用 cmake_language(DEFER CALL ...) 将评估推迟到设置 IMPORTED_LOCATION 属性之后。