RustのLinux/Windows/OS X向けバイナリをCIで生成する

#2016/9/11追記
この記事の情報は古くなってしまったので「[RustのLinux/Windows/OS X向けバイナリをCIで生成する（更新版）](http://qiita.com/dalance/items/cb4df1e8c4be506ee220)」をどうぞ。

# はじめに
[Rust](https://www.rust-lang.org/)はLinux/Windows/OS Xをサポートしていますが、[Go](https://golang.org/)のように簡単にクロスコンパイルできるわけではありません。（いろいろ設定すると最終的に出来るようにはなるのですが、かなり面倒な印象です）
手元でできないならCIでやればいい、ということで今回はCI環境で以下の6つのバイナリを生成できるようにしました。

- Linux 64bit
- Linux 32bit
- Windows 64bit
- Windows 32bit
- OS X 64bit
- OS X 32bit

# 前提
この情報は2016/1/24時点のものです。各CIサービスの状況や、Rustが提供するstd-libのURLなどが今後変わった場合にうまくいかなくなるかもしれません。
使うCIサービスは以下の2つです

- [Travis-CI](https://travis-ci.org/)
- [AppVeyor](https://www.appveyor.com/)

Travis-CIでLinux/OS X向けビルドを、AppVeyorでWindows向けビルドを行います。（当初Travis-CIでmingwを使ったWindows向けクロスコンパイルも試みたのですが、うまくいかなかったためAppVeyorに切り替えました）

## Travis-CIによるLinux/OS X向けビルド

```.travis.yml
language: rust

os:
  - linux
  - osx

rust: stable

before_install:
    - if [[ $TRAVIS_OS_NAME == "linux" ]]; then sudo apt-get -qq update; fi
    - if [[ $TRAVIS_OS_NAME == "linux" ]]; then sudo apt-get install -y gcc-multilib g++-multilib; fi

before_script:
  - if [[ $TRAVIS_OS_NAME == "osx"   ]]; then curl -O https://static.rust-lang.org/dist/rust-std-1.6.0-i686-apple-darwin.tar.gz; fi
  - if [[ $TRAVIS_OS_NAME == "osx"   ]]; then tar xf rust-std-1.6.0-i686-apple-darwin.tar.gz; fi
  - if [[ $TRAVIS_OS_NAME == "osx"   ]]; then mv rust-std-1.6.0-i686-apple-darwin/rust-std-i686-apple-darwin/lib/rustlib/i686-apple-darwin ~/rust/lib/rustlib/; fi
  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then curl -O https://static.rust-lang.org/dist/rust-std-1.6.0-i686-unknown-linux-gnu.tar.gz; fi
  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then tar xf rust-std-1.6.0-i686-unknown-linux-gnu.tar.gz; fi
  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then mv rust-std-1.6.0-i686-unknown-linux-gnu/rust-std-i686-unknown-linux-gnu/lib/rustlib/i686-unknown-linux-gnu ~/rust/lib/rustlib/; fi

script:
  - if [[ $TRAVIS_OS_NAME == "osx"   ]]; then cargo build --release --target=x86_64-apple-darwin;      fi
  - if [[ $TRAVIS_OS_NAME == "osx"   ]]; then cargo build --release --target=i686-apple-darwin;        fi
  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then cargo build --release --target=x86_64-unknown-linux-gnu; fi
  - if [[ $TRAVIS_OS_NAME == "linux" ]]; then cargo build --release --target=i686-unknown-linux-gnu;   fi
```

Travis-CIのOSは`$TRAVIS_OS_NAME`で取れるので、それに応じて動作を切り替えます。
`before_install`で32bitビルド用のmultilibを、`before_script`で32bitのrustlibを展開します。Travis-CIの`language: rust`指定におけるRustのバスは`~/rust`なので展開先は`~/rust/lib/rustlib`になります。
また、rustlibのバージョンは直接書いていますが、stable前提なら`$(rustc --version | awk '{print $2}')`などとしてもよいかもしれません。（動作は未確認です）

## Travis-CIによるWindows向けビルド（失敗）

```.travis.yml
language: rust

os:
  - linux

rust: stable

before_install:
    - sudo apt-get -qq update
    - sudo apt-get install -y binutils-mingw-w64 mingw-w64

before_script:
  - curl -O https://static.rust-lang.org/dist/rust-std-1.6.0-x86_64-pc-windows-gnu.tar.gz
  - tar xf rust-std-1.6.0-x86_64-pc-windows-gnu.tar.gz
  - mv rust-std-1.6.0-x86_64-pc-windows-gnu/rust-std-x86_64-pc-windows-gnu/lib/rustlib/x86_64-pc-windows-gnu ~/rust/lib/rustlib/

script:
  - cargo build --release --target=x86_64-pc-windows-gnu
```

基本的にLinux/OS X向けビルドと同様ですが、`before_install`でmingwのインストールをしています。
また、インストールするだけでは使ってくれないので、`Cargo.toml`などがあるディレクトリに`.cargo`ディレクトリを作成し、`.cargo/config`に以下のように書きます。
これで特定targetの時のみ指定したlinker/arを使ってくれるようになります。

```
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-ar"
```

手元のCentOS7.2環境ではこれでビルドが通ったのですが、Travis-CIのUbuntu上ではリンク時に`undefined reference`になってしまいます。
（念のためtrustyも試しましたが、同様に失敗しました）

## AppVeyorによるWindows向けビルド

```appveyor.yml
platform:
    - x64

environment:
    RUST_INSTALL_DIR: C:\Rust
    matrix:
        - RUST_INSTALL_TRIPLE: i686-pc-windows-msvc
          RUST_VERSION: 1.6.0
        - RUST_INSTALL_TRIPLE: x86_64-pc-windows-msvc
          RUST_VERSION: 1.6.0

install:
    - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:RUST_VERSION}-${env:RUST_INSTALL_TRIPLE}.exe"
    - cmd: rust-%RUST_VERSION%-%RUST_INSTALL_TRIPLE%.exe /VERYSILENT /NORESTART /DIR="%RUST_INSTALL_DIR%"
    - cmd: SET PATH=%PATH%;%RUST_INSTALL_DIR%\bin

build_script:
    - cmd: cargo build --release --target=%RUST_INSTALL_TRIPLE%
```

AppVeyorの設定は[こちらの記事](http://keens.github.io/blog/2015/12/20/travistoappveyerdekurosupurattofo_munacisuruhanashi/)を参考に（というかほぼコピペ）させていただきました。
当然ですが普通のWindows環境なので特筆すべきことはありません。

## opensslを含む場合（2016/2/21追記）
依存関係上opensslを含むライブラリ（例えば[hyper](https://github.com/hyperium/hyper)など）を使う場合に問題がありましたので追記します。
まず、Travis-CIのLinuxビルドですが、現在デフォルトのUbuntu12.04はopensslのmultiarch対応に[バグ](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=689093)があるらしく、32bitビルドが通りません。そのためUbuntu14.04(trusty)を使う必要があります。また、`libssl-dev:i386`は`libssl-dev`とコンフリクトするので32bitビルドと64bitビルドを同時に出来ないようです。（Debian系のパッケージはよくわからないので使い方が間違ってるかもしれませんが）
というわけで、以下のように`trusty`を指定、32bitビルドと64bitビルドを分ければいけました。

```
dist: trusty

env:
- ARCH=i686
- ARCH=x86_64

before_install:
- if [[ $TRAVIS_OS_NAME == "linux" && $ARCH == "i686" ]]; then sudo apt-get -qq update; fi
- if [[ $TRAVIS_OS_NAME == "linux" && $ARCH == "i686" ]]; then sudo apt-get install -y gcc-multilib g++-multilib libssl-dev:i386; fi
```

また、WindowsはそもそもOpenSSLが入っていないので入れるところからになります。64bit版と32bit版でインストーラやライブラリのパスが違うのでそれぞれ設定します。（環境変数の設定は[ここ](https://github.com/sfackler/rust-openssl/issues/293)を参考にしました）
以下に差分だけ書いておきます。

```
environment:
    matrix:
        - OPENSSL_ARCH: Win32
          OPENSSL_LIB_DIR: C:\OpenSSL-Win32\lib\VC
          OPENSSL_LIBS: ssleay32MT:libeay32MT
          OPENSSL_INCLUDE_DIR: C:\OpenSSL-Win32\include
          DEP_OPENSSL_INCLUDE_DIR: C:\OpenSSL-Win32\include
        - OPENSSL_ARCH: Win64
          OPENSSL_LIB_DIR: C:\OpenSSL-Win64\lib\VC
          OPENSSL_LIBS: ssleay32MT:libeay32MT
          OPENSSL_INCLUDE_DIR: C:\OpenSSL-Win64\include
          DEP_OPENSSL_INCLUDE_DIR: C:\OpenSSL-Win64\include

install:
    - ps: Start-FileDownload "http://slproweb.com/download/${env:OPENSSL_ARCH}OpenSSL-1_0_2f.exe"
    - ps: Start-Process "${env:OPENSSL_ARCH}OpenSSL-1_0_2f.exe" -ArgumentList "/silent /verysilent /sp- /suppressmsgboxes" -Wait
```

# まとめ
RustのLinux/Windows/OS X向けバイナリ計6種をCIで生成してみました。Travis-Ci上でWindowsコンパイルは時間の関係で早々にあきらめましたが、うまくいっている方がいらっしゃいましたら教えてください。