使用 rustup 将 Rust 带到任何地方

2016 年 5 月 13 日 · Brian Anderson

交叉编译 是一个对于常见需求来说略显夸张的术语

  • 你希望使用你的笔记本电脑为 Android、iOS 或你的路由器构建应用程序。

  • 你希望在你的 Mac 上编写、测试和构建代码,但将其部署到你的 Linux 服务器上。

  • 你希望你的基于 Linux 的构建服务器为所有你发布的平台生成二进制文件。

  • 你希望构建一个超轻便的二进制文件,你可以将其发布到任何 Linux 平台。

  • 你希望使用 EmscriptenWebAssembly 来面向浏览器。

换句话说,你希望在一个 “主机” 平台上进行开发/构建,但最终得到一个在不同的 “目标” 平台上运行的二进制文件

得益于 LLVM 后端,原则上交叉编译 Rust 代码始终是可能的:只需告诉后端使用不同的目标!事实上,勇敢的黑客已经将 Rust 应用于嵌入式系统,例如 Raspberry Pi 3裸机 ARM运行 OpenWRT 的 MIPS 路由器 以及许多其他设备。

但在实践中,你需要将许多问题都解决妥当才能使其正常工作:适当的 Rust 标准库、一个包括链接器、头文件和 C 库的二进制文件的交叉编译 C 工具链等等。这通常需要仔细研读各种博客文章和软件包安装程序,才能使一切 “恰到好处”。并且,对于每对主机和目标平台,工具的精确组合可能是不同的。

Rust 社区一直在努力实现 “一键式交叉编译” 的目标。我们希望通过运行单个命令,为给定的主机/目标对提供完整的设置。今天,我们很高兴地宣布这项工作的主要部分正在进入 beta 状态:我们正在为各种目标构建 Rust 标准库的二进制文件,并通过一个名为 rustup 的新工具将其交付给你。

rustup 介绍

从本质上讲,rustup 是一个 Rust 的工具链管理器。它可以下载并切换所有受支持平台的 Rust 编译器和标准库的副本,并跟踪 Rust 的 nightly、beta 和 release 频道 以及特定的版本。在这方面,rustup 类似于 Ruby 和 Python 的 rvmrbenvpyenv 工具。我将在帖子的其余部分中介绍所有这些功能以及它们有用的情况。

如今,rustup 是一个命令行应用程序,我将向你展示一些它可以执行的操作示例,但它也是一个 Rust 库,最终这些功能有望通过适当的图形界面呈现 - 特别是在 Windows 上。交叉编译的设置最终应该是在 Rust 安装程序中勾选一个复选框的问题。

我们的目标不仅仅是管理 Rust 工具链:为了获得真正的 “一键式” 交叉编译体验,它还需要设置 C 工具链。此功能今天尚未发布,但我们希望在未来几个月内将其纳入。

基本工具链管理

让我们从一个简单的示例开始:安装多个 Rust 工具链。在此示例中,我创建了一个新库 'hello',然后使用 rustc 1.8 对其进行测试,然后使用 rustup 在 1.9 beta 上安装并测试同一个 crate。

这是一种轻松验证你的代码在下一个 Rust 版本上是否有效的方法。这是一种良好的 Rust 社区行为!

我们可以使用 rustup show 来显示已安装的工具链,并使用 rustup update 来保持它们与 Rust 版本的同步。

最后,rustup 还可以使用 rustup default 更改默认工具链

$ rustc --version
rustc 1.8.0 (db2939409 2016-04-11)
$ rustup default 1.7.0
info: syncing channel updates for '1.7.0-x86_64-unknown-linux-gnu'
info: downloading component 'rust'
info: installing component 'rust'
info: default toolchain set to '1.7.0-x86_64-unknown-linux-gnu'

  1.7.0-x86_64-unknown-linux-gnu installed - rustc 1.7.0 (a5d1e7a59 2016-02-29)

$ rustc --version
rustc 1.7.0 (a5d1e7a59 2016-02-29)

在 Windows 上,Rust 同时支持 GNU 和 MSVC ABI,你可能希望从 Windows 上以 32 位 x86 架构和 GNU ABI 为目标的默认稳定工具链切换到以 64 位 MSVC ABI 为目标的稳定工具链。

$ rustup default stable-x86_64-pc-windows-msvc
info: syncing channel updates for 'stable-x86_64-pc-windows-msvc'
info: downloading component 'rustc'
info: downloading component 'rust-std'
...

  stable-x86_64-pc-windows-msvc installed - rustc 1.8.0-stable (db2939409 2016-04-11)

在这里,“stable” 工具链名称附加了一个额外的标识符,指示编译器的架构,在本例中为 x86_64-pc-windows-msvc。此标识符称为 “目标三元组”:“目标” 是因为它指定了编译器生成(目标)机器代码的平台;而 “三元组” 是出于历史原因(在许多情况下,“三元组” 现在实际上是四元组)。目标三元组是我们引用特定常见平台的基本方式;rustc 默认知道其中的 56 个,而 rustup 今天可以获取 14 个编译器的标准库和 30 个标准库。

示例:在 Linux 上构建静态二进制文件

现在我们已经准备好了基本要素,让我们将它们应用到简单的交叉编译任务中:为 Linux 构建超轻便的静态二进制文件。

Linux 的一个独特的特性,并且日益受到重视的是其稳定的 syscall 接口。由于 Linux 内核在维护向后兼容的内核接口方面付出了巨大的努力,因此可以分发没有动态库依赖的 ELF 二进制文件,这些文件将在任何版本的 Linux 上运行。除了成为使 Docker 成为可能的功能之一之外,它还允许开发人员构建独立的应用程序并将其部署到任何运行 Linux 的机器,无论它是 Ubuntu、Fedora 还是任何其他发行版,也无论它们安装了什么软件库。

今天的 Rust 依赖于 libc,并且在大多数 Linux 上,这意味着 glibc。从技术上讲,完全静态链接 glibc 是一个具有挑战性的事情,当使用它来生成真正独立的二进制文件时会带来困难。幸运的是,存在替代方案:musl,它是 libc 的一个小型、现代实现,可以轻松地进行静态链接。自 1.1 版本以来,Rust 一直与 musl 兼容,但直到最近,开发人员仍需要构建自己的编译器才能从中受益。

有了这些背景知识,让我们来完成编译静态链接的 Linux 可执行文件。在此示例中,你需要在 Linux 上运行 - 也就是说,你的主机平台将是 Linux,而你的目标平台也将是 Linux,只是不同的风格:musl。(是的,即使两个目标都是 Linux,这在技术上也是交叉编译)。

我将在 Ubuntu 16.04 上运行(使用 此 Docker 镜像)。我们将构建基本的 hello world

rust:~$ cargo new --bin hello && cd hello
rust:~/hello$ cargo run
   Compiling hello v0.1.0 (file:///home/rust/hello)
     Running `target/debug/hello`
Hello, world!

这是使用默认的 x86_64-unknown-linux-gnu 目标。你可以看到它有很多动态依赖项

rust:~/hello$ ldd target/debug/hello
        linux-vdso.so.1 =>  (0x00007ffe5e979000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fca26d03000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fca26ae6000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fca268cf000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fca26506000)
        /lib64/ld-linux-x86-64.so.2 (0x000056104c935000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fca261fd000)

要改为为 musl 编译,请使用参数 --target=x86_64-unknown-linux-musl 调用 cargo。如果我们直接尝试,将会收到错误

rust:~/hello$ cargo run --target=x86_64-unknown-linux-musl
   Compiling hello v0.1.0 (file:///home/rust/hello)
error: can't find crate for `std` [E0463]
error: aborting due to previous error
Could not compile `hello`.
...

错误告诉我们编译器找不到 std。当然,那是因为我们尚未安装它。

要开始交叉编译,你需要获取目标平台的标准库。以前,这是一个容易出错的手动过程 - 回顾我之前提到的那些博客文章。但是,使用 rustup,它只是日常工作流程的一部分

rust:~/hello$ rustup target add x86_64-unknown-linux-musl
info: downloading component 'rust-std' for 'x86_64-unknown-linux-musl'
info: installing component 'rust-std' for 'x86_64-unknown-linux-musl'
rust:~/hello$ rustup show
installed targets for active toolchain
--------------------------------------

x86_64-unknown-linux-gnu
x86_64-unknown-linux-musl

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.8.0 (db2939409 2016-04-11)

因此,我正在针对 64 位 x86 运行 Linux 的 1.8 工具链,如 x86_64-unknown-linux-gnu 目标三元组所示,现在我也可以以 x86_64-unknown-linux-musl 为目标。真棒。我们肯定已经准备好构建一个可以发布到云端的、精巧的静态链接二进制文件了。让我们试试

rust:~/hello$ cargo run --target=x86_64-unknown-linux-musl
   Compiling hello v0.1.0 (file:///hello)
     Running `target/x86_64-unknown-linux-musl/debug/hello`
Hello, world!

然后... 它就工作了!在上面运行 ldd 来证明它是真实存在的

rust:~/hello$ ldd target/x86_64-unknown-linux-musl/debug/hello
        not a dynamic executable

现在,将该 hello 二进制文件复制到任何运行 Linux 的 x86_64 机器上,它都可以正常运行。

有关 musl 的更多高级用法,请考虑使用 rust-musl-builder,这是一个为 musl 开发设置的 Docker 镜像,它有益地包含为 musl 编译的常见 C 库。

示例:在 Android 上运行 Rust

再举一个例子。这次是从 Linux 为 Android 构建,即从 x86_64-unknown-linux-gnuarm-linux-androideabi。这也可以从 OS X 或 Windows 完成,尽管在 Windows 上设置略有不同。

要为 Android 构建,我们需要添加 Android 目标,因此让我们设置另一个 'hello, world' 项目并安装它。

rust:~$ cargo new --bin hello && cd hello
rust:~/hello$ rustup target add arm-linux-androideabi
info: downloading component 'rust-std' for 'arm-linux-androideabi'
info: installing component 'rust-std' for 'arm-linux-androideabi'
rust:~/hello$ rustup show
installed targets for active toolchain
--------------------------------------

arm-linux-androideabi
x86_64-unknown-linux-gnu

active toolchain
----------------

stable-x86_64-unknown-linux-gnu (default)
rustc 1.8.0 (db2939409 2016-04-11)

那么,如果我们尝试直接构建我们的 'hello' 项目而不安装任何其他内容,会发生什么

rust:~/hello$ cargo build --target=arm-linux-androideabi
   Compiling hello v0.1.0 (file:///home/rust/hello)
error: linking with `cc` failed: exit code: 1
... (lots of noise elided)
error: aborting due to previous error
Could not compile `hello`.

问题在于我们还没有支持 Android 的链接器,因此让我们花点时间来讨论一下为 Android 构建。要为 Android 开发,我们需要 Android NDK。它包含 rustc 创建 Android 二进制文件所需的链接器。要仅构建以 Android 为目标的 Rust 代码,我们只需要 NDK,但是对于实际开发,我们还需要 Android SDK

在 Linux 上,使用以下命令下载并解压它们(此处未包含其输出)

rust:~/home$ cd
rust:~$ curl -O https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
rust:~$ tar xzf android-sdk_r24.4.1-linux.tgz
rust:~$ curl -O https://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip
rust:~$ unzip android-ndk-r10e-linux-x86_64.zip

我们还需要创建 NDK 所谓的 “独立工具链”。我们将把我们的工具链放在一个名为 android-18-toolchain 的目录中

rust:~$ android-ndk-r10e/build/tools/make-standalone-toolchain.sh \
      --platform=android-18 --toolchain=arm-linux-androideabi-clang3.6 \
      --install-dir=android-18-toolchain --ndk-dir=android-ndk-r10e/ --arch=arm
Auto-config: --toolchain=arm-linux-androideabi-4.8, --llvm-version=3.6
Copying prebuilt binaries...
Copying sysroot headers and libraries...
Copying c++ runtime headers and libraries...
Copying files to: android-18-toolchain
Cleaning up...
Done.

让我们注意一下这些命令中的一些事项。首先,我们下载的 NDK android-ndk-r10e-linux-x86_64.zip 不是最新版本(在撰写本文时为 ‘r11c’)。Rust 的 std 是针对 r10e 构建的,并链接到不再包含在 NDK 中的符号。因此,目前我们必须使用较旧的 NDK。其次,在构建独立工具链时,我们向 make-standalone-toolchain.sh 传递了 --platform=android-18。这里的“18”是 Android API 级别。如今,Rust 的 arm-linux-androideabi 目标是针对 Android API 级别 18 构建的,并且理论上应该与后续的 Android API 级别向前兼容。因此,我们选择级别 18 以获得 Rust 目前允许的最大 Android 兼容性。

我们要做的最后一件事是告诉 Cargo 在哪里可以找到 android 链接器,该链接器位于我们刚刚创建的独立 NDK 工具链中。为此,我们在 .cargo/config 中使用 'linker' 值配置 arm-linux-androideabi 目标。在执行此操作时,我们将继续为该项目设置默认目标为 Android,这样我们就无需一直使用 --target 选项来调用 cargo。

[build]
target = "arm-linux-androideabi"

[target.arm-linux-androideabi]
linker = "/home/rust/android-18-toolchain/bin/arm-linux-androideabi-clang"

现在让我们切换回 'hello' 项目目录并再次尝试构建

rust:~$ cd hello
rust:~/hello$ cargo build
   Compiling hello v0.1.0 (file:///home/rust/hello)

成功!当然,仅仅让某些东西构建完成并不是故事的结尾。你还需要将你的代码打包为 Android APK。为此,你可以使用 cargo-apk

其他地方的 Rust

Rust 是一个软件平台,它有可能在任何带 CPU 的东西上运行。在这篇文章中,我向你展示了 Rust 已经可以使用 rustup 工具完成的一小部分工作。如今,Rust 在你每天使用的大多数平台上运行。明天,它将在任何地方运行。

那么你接下来应该期待什么?

在接下来的几个月中,我们将继续消除 Rust 交叉编译的障碍。目前,rustup 提供了对标准库的访问,但正如我们在这篇文章中看到的,交叉编译不仅仅是 rustc + std。最令人头疼的是获取和配置链接器以及 C 工具链——每个主机和目标平台的组合都需要略有不同的配置。我们希望简化这一过程,并将在 rustup 中添加“NDK 支持”。其具体含义将取决于实际情况,但我们将从最受欢迎的 Android 等平台开始,并尽可能自动化非 Rust 工具链组件的检测、安装和配置。例如,在 Android 上,我们的目标是自动化除了接受许可之外的所有基本初始设置。

除此之外,还有多项工作致力于改进 Rust 交叉编译工具,包括 xargo,它可以用于为 rustup 不支持的目标构建标准库,以及 cargo-apk,它可以从 Cargo 包构建 Android 包。

最后,Rust 未来最令人兴奋的平台不是传统的系统语言目标:而是 Web。借助今天的 Emscripten,通过将 LLVM IR 转换为 JavaScript(或 JavaScript 的 asm.js 子集),在 Web 上运行 C++ 代码非常容易。即将到来的 WebAssembly (wasm) 标准将巩固 Web 平台作为编程语言的首选目标。

Rust 具有独特的优势,有望在不久的将来成为最强大且最易用的 wasm 目标语言。 Rust 之所以如此容易移植到真实硬件,是因为它几乎可以轻松移植到 wasm。对于包含垃圾收集器的复杂运行时的语言来说,情况并非如此。

Rust 已经 被移植到 Emscripten (至少两次),但代码尚未完全落地。今年夏天,这一切将会发生:Rust + Emscripten。Web 上的 Rust。无处不在的 Rust。

结语

虽然很多人报告说 rustup 使用成功,但它仍然处于 beta 阶段,仍有一些 关键的未解决的错误,并且还不是 Rust 官方推荐的安装方法(尽管你应该尝试一下)。我们将继续征求反馈,进行改进,并修复错误。然后,我们将通过 将其嵌入到行为类似于合适的 Windows 安装程序的 GUI 中来改进 Windows 上的 rustup 安装体验。

到那时,我们可能会更新 www.rust-lang.org 上的下载说明 以推荐 rustup。我预计所有现有的安装方法都将保持可用,包括非 rustup Windows 安装程序,但届时我们的重点将是通过 rustup 改进安装体验。rustup 本身也可能被打包到 Homebrew 和 apt 等包管理器中。

如果你想自己尝试 rustup,请访问 www.rustup.rs 并按照说明进行操作。然后在 专门的论坛主题 上留下反馈,或者在问题跟踪器上 提交错误。有关 rustup 的更多信息,请参阅 README

感谢

没有许多个人的帮助,Rust 就不会成为如此强大的系统。感谢 Diggory Blake 创建 rustup,感谢 Jorge Aparicio 修复了大量的交叉编译错误并记录了该过程,感谢 Tomaka 开创了 Rust on Android,以及感谢 Alex Crichton 为 Rust 的许多平台创建了发布基础设施。

感谢所有 rustup 的贡献者:Alex Crichton、Brian Anderson、Corey Farwell、David Salter、Diggory Blake、Jacob Shaffer、Jeremiah Peschka、Joe Wilm、Jorge Aparicio、Kai Noda、Kamal Marhubi、Kevin K、llogiq、Mika Attila、NODA、Kai、Paul Padier、Severen Redwood、Taylor Cramer、Tim Neumann、trolleyman、Vadim Petrochenkov、V Jackson、Vladimir、Wayne Warren、Yasushi Abe、Y. T. Chung