kanejaku.org

Rust: Raspberry Pi (Raspbian) 向けの実行バイナリを手軽に作る

Publish date 23 Jun 2019 Last update 2 Jul 2019

Rust で書いたアプリケーションを手元の Raspberry Pi 3 で動かしたい。ベアメタルではなくて OS は Raspbian。

まずは raspi 上でコンパイルしてみる。遅い。自分のアプリをコンパイルするのに 60 分 (!) もかかる。アプリの開発自体は PC でやっているからそんなに頻繁に raspi 用のバイナリを作る必要はないのだけど、さすがにこれではやっていられない。

raspi 上でバイナリを作るのは諦めてクロスコンパイル環境を用意する。Rust は簡単にいろんなアーキテクチャ向けの toolchain 入れられるものの、コンパイルだけでなく Raspbian 向けの実行バイナリをリンクするには GCC のクロスコンパイラ (gcc-arm-linux-gnueabihf) が別途必要になる。開発しているマシンの OS に対する依存が少なく、かつ手軽にリンクまでできる環境を作りたい。となると Docker コンテナでクロスコンパイル環境を作れば良さそうだ。

まずは toolchain のセットアップから。DockerHub に Rust の公式イメージがあるのでこれをベースにする。加えて GCC のクロスコンパイラと Rust のarmv7-unknown-linux-gnueabihf向け toolchain をインストールする。コンテナが起動したら cargo build するようにしておく。Dockerfile はこんな感じ。

FROM rust:1.35

# コンテナ実行時に -v オプションでマウントされるのを前提としている。
WORKDIR /usr/src/myapp

RUN apt-get update && apt-get install -y gcc-arm-linux-gnueabihf
RUN rustup target add armv7-unknown-linux-gnueabihf

CMD ["cargo", "build", "--target", "armv7-unknown-linux-gnueabihf", "--release"]

続いて Rust で書いたアプリのリポジトリ配下の.cargo/configにクロスコンパイル用の設定を書いておく。

[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

あとは Docker イメージ作って実行すれば raspi 上で動くバイナリを生成できる。

$ docker build -t myapp-cross-raspi
$ docker run -it -v "$PWD":/usr/src/myapp myapp-cross-raspi
$ file target/armv7-unknown-linux-gnueabihf/release/myapp
target/armv7-unknown-linux-gnueabihf/release/myapp: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV),
...

ただ、上記の設定だけではcargoのキャッシュがコンテナ内に保持されてしまう。つまりコンテナを作り直すたびに myapp が依存している crates をダウンロードするはめになる。これは資源の無駄なので自分は CARGO_HOME 環境変数を上書きしてホストの適当な場所を指すようにしている。

$ docker run -it -v "$PWD":/usr/src/myapp -e CARGO_HOME="$PWD"/.docker-cargo myapp-cross-raspi

補記

Docker Desktop (Windows と Mac) では最近になって Arm アーキテクチャのコンテナを x86 環境でも動かせるようになったらしい。

Building Multi-Arch Images for Arm and x86 with Docker Desktop - Docker Engineering Blog

つまり Windows と Mac ではコマンド一つ叩くだけで Rasbian 上で動くバイナリをクロスコンパイルできる。

$ docker run -it -v "$PWD":/usr/src/myapp -w /usr/src/myapp arm32v7/rust:1.35 cargo build

こっちのほうが楽だ、と一瞬思ったが、これが結構遅い。内部で QEMU 使っているようだから遅いのはしょうがない気がするが、actix-web を使った単純な hello-world でも手元のマシンで 10 分ぐらいコンパイルに時間がかかった。

参考