RasPi用のRustバイナリをクロスコンパイルする環境が欲しかったので
dockerでその環境を構築して誰でも利用できるようにdocker hubに登録した
ビルトと実行について
TL;DR
ビルドコマンド
dockerがインストールされている環境で以下のコマンドを実行する
docker run -it --rm -v ${PWD}:/source yasuyuky/rust-arm cargo build --release --target=arm-unknown-linux-gnueabihf
ビルド結果格納先: target/arm-unknown-linux-gnueabihf/release/
ビルド方法(もう少し長い版)
dockerが実行可能な環境で以下のようにすればARM上で実行可能なバイナリが作成可能。
Rustのプロジェクトディレクトリに移動して
docker run -it --rm -v ${PWD}:/source yasuyuky:rust-arm
するとdockerコンテナ内のbashに入るので以下のように
--target=arm-unknown-linux-gnueabihf
をつけて
cargo build
するだけでARM用のバイナリができる。
cargo build --release --target=arm-unknown-linux-gnueabihf
target/arm-unknown-linux-gnueabihf/release/
以下にバイナリが出来る。
イメージ内に入らずに実行
docker imageの中に入らずに実行する事も可能。
docker run -it --rm -v ${PWD}:/source yasuyuky/rust-arm cargo build --release --target=arm-unknown-linux-gnueabihf
ローカル側のhistoryにも残るのでこちらの方が実用的かもしれない。
rustcを直接使う場合
rustcを直接使う場合は以下のようにlinkerを指定してやる必要がある。
rustc foo.rs --target=arm-unknown-linux-gnueabihf -C linker=arm-linux-gnueabihf-gcc-4.8
確認
$ file target/arm-unknown-linux-gnueabihf/release/foo
target/arm-unknown-linux-gnueabihf/release/foo: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped
fileコマンドでARM用のバイナリである事を確認できる。
実行
RasPiはRaspbian OSで使っている方が多いと思われるが、
残念ながら現在('15/7/27)ダウンロードで配布されているRaspbian OSは
debian:wheezyベースとなっており、今回の方法で作ったバイナリでは
libcのバージョンが古いため実行できない
jessieベースにアップグレードしましょう。
構築までの長い道のり
そもそもの動機はMac上でコンパイルしてRasPi上にポンと置いておけば動くバイナリが
構築できる環境が欲しかった。
Goあたりはクロスコンパイルがとても簡単で、
OSX上で直接ARM上で動くlinuxのバイナリがコンパイル出来る。
Rustもcrosscompile用のドキュメントがあるので
goほどではないにしろ何かしらそのようなできるだろうと思ったら予想以上に手強かった。
結局Mac上でクロスプラットフォームのgnu toolchainのインストールがとても大変だという事で
直接Mac OS上でarm-unknown-linux-gnueabihfをターゲットにするのは諦め、
dockerでうごかしたコンテナ上でコンパイルすることにした。
docker images構築
Dockerfileは以下のリポジトリに公開してある
https://github.com/yasuyuky/docker-rust-arm
Dockerfileの工夫
一番最初に作ったイメージでは初めて本格的にdockerを触ったため
素朴にDockerfileを書いてビルドした結果
巨大な4.5GBもあるdocker imageが出来上がってしまった。
具体的には以下のような問題点があった。
- 今では
&&
でつなげているところを素朴に全部RUN
していた - curlでソース持ってきてるところをgitチェックアウトしていた
- インストール後のソースのクリーンアップをしていなかった
- 余分なパッケージが入っていた
その後、上記の部分を改善する事により約900MBまでシュリンクできた。
まだ工夫の余地はあるかもしれないが、全部つなげて1レイヤーに
してしまうみたいなのはdockerの良さも消してしまう部分があるので
バランスが大事だと思う。
内容自体は変わった事はしてないが、.cargo/config
をルートに置いて
CargoにARM用のリンカを使うように指示している。
docker build
上記Dockerfileを任意のディレクトリに置いて
docker build .
すればdocker imageが出来上がる。(時間がかかる。)
Successfully built xxxxxxxxxxxx
と言われたら成功。
docker run -it --rm -v${PWD}:/source xxxxxxxxxxxx
builtしたイメージのハッシュを指定してイメージ内に入れる。
各オプションについて簡単に説明すると
-it
はインタラクティブに色々するためのオプション。
--rm
をつけないとシェルを抜けた後にコンテナプロセスが残ってしまうので、つける事推奨。
-v
オプションはローカルのディレクトリをイメージ内にマウントするためのオプション。
Docker hub automated build
Docker hub にはgithubとかbitbucketとかに上げた
Dockerfileから自動的にdocker imageを生成する
automated buildという機能がある。
automated buildを使えばどんなDockerfileが使われているか確認でき、
広く使ってもらってもらうのには有効。
一方で、ビルド時間には2時間という制限があり、
rustのソースからのコンパイルのような時間のかかるビルドは
制限時間にひっかからないように工夫が必要だった。
今回上げたイメージも、当初は2時間制限にひっかかり
できるだけ無駄な処理をしないよう削るなどした。
制限時間はクリアできたが、現状100分以上かかっててギリギリなので今後
rustのコンパイル時間がさらに伸びた場合は対策を考えないといけないかもしれない。
まとめ
Rustのクロスコンパイル環境をdockerで構築した。
当初はワークアラウンドとして考えた策だったが、作ってみると
- キッティングとしてもdocker(boot2docker)インストールするだけで楽
- Windows環境などでも普通に使えそう
- CIするときも同じイメージつかえそう
というメリットがある事に気づいた。
今回初めてdockerを本格的に使ったが、とてもよくできていて
今後Rustに限らず、クロスコンパイル環境の構築が難しいものに
ついてdockerを積極的に採用していってもいいかなと思えた。