dockerによるRustのARMクロスコンパイル環境の構築


RasPi用のRustバイナリをクロスコンパイルする環境が欲しかったので
dockerでその環境を構築して誰でも利用できるように[docker hubに登録した](https://registry.hub.docker.com/u/yasuyuky/rust-arm/)


# ビルトと実行について

## TL;DR

### ビルドコマンド
dockerがインストールされている環境で以下のコマンドを実行する

```bash
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のプロジェクトディレクトリに移動して

```bash
docker run -it --rm -v ${PWD}:/source yasuyuky:rust-arm
```

するとdockerコンテナ内のbashに入るので以下のように
`--target=arm-unknown-linux-gnueabihf`をつけて
`cargo build`するだけでARM用のバイナリができる。

```bash
cargo build --release --target=arm-unknown-linux-gnueabihf
```

`target/arm-unknown-linux-gnueabihf/release/` 以下にバイナリが出来る。

## イメージ内に入らずに実行
docker imageの中に入らずに実行する事も可能。

```bash
docker run -it --rm -v ${PWD}:/source yasuyuky/rust-arm cargo build --release --target=arm-unknown-linux-gnueabihf
````

ローカル側のhistoryにも残るのでこちらの方が実用的かもしれない。

## rustcを直接使う場合
rustcを直接使う場合は以下のようにlinkerを指定してやる必要がある。

```bash
rustc foo.rs --target=arm-unknown-linux-gnueabihf -C linker=arm-linux-gnueabihf-gcc-4.8
```

## 確認

```bash
$ 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ベースにアップグレードしましょう。


* 参考 http://akkagi.info/20150522_web/


# 構築までの長い道のり

そもそもの動機はMac上でコンパイルしてRasPi上にポンと置いておけば動くバイナリが
構築できる環境が欲しかった。

Goあたりはクロスコンパイルがとても簡単で、
OSX上で直接ARM上で動くlinuxのバイナリがコンパイル出来る。

Rustも[crosscompile用のドキュメント](https://github.com/japaric/ruststrap/blob/master/1-how-to-cross-compile.md)があるので
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までシュリンクできた。

まだ工夫の余地はあるかもしれないが、全部つなげて１レイヤーに
してしまうみたいなのはdockerの良さも消してしまう部分があるので
バランスが大事だと思う。


内容自体は変わった事はしてないが、`.cargo/config`をルートに置いて
CargoにARM用のリンカを使うように指示している。

* 参考 http://doc.crates.io/config.html

## docker build

上記Dockerfileを任意のディレクトリに置いて

```bash
docker build .
```

すればdocker imageが出来上がる。（時間がかかる。）

```bash
Successfully built xxxxxxxxxxxx
```
と言われたら成功。

```bash
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が使われているか確認でき、
広く使ってもらってもらうのには有効。

一方で、ビルド時間には２時間という制限があり、
rustのソースからのコンパイルのような時間のかかるビルドは
制限時間にひっかからないように工夫が必要だった。

今回上げたイメージも、当初は２時間制限にひっかかり
できるだけ無駄な処理をしないよう削るなどした。

制限時間はクリアできたが、現状100分以上かかっててギリギリなので今後
rustのコンパイル時間がさらに伸びた場合は対策を考えないといけないかもしれない。

# まとめ

Rustのクロスコンパイル環境をdockerで構築した。
当初はワークアラウンドとして考えた策だったが、作ってみると

* キッティングとしてもdocker（boot2docker）インストールするだけで楽
* Windows環境などでも普通に使えそう
* CIするときも同じイメージつかえそう

というメリットがある事に気づいた。

今回初めてdockerを本格的に使ったが、とてもよくできていて
今後Rustに限らず、クロスコンパイル環境の構築が難しいものに
ついてdockerを積極的に採用していってもいいかなと思えた。