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 分ぐらいコンパイルに時間がかかった。