はじめに
Rustの公式32bitバイナリはi686向けになっています。今時ほとんどのx86/32bitはi686なので妥当な選択ではありますが、ごくまれにi586(世代的には初代Pentium~MMX Pentiumあたり)上でRustを動かしたいことがあります。i586上で公式バイナリのrustc
などをそのまま動かしてもillegal hardware instruction
となり実行できません。このエラーについてはこちらで報告されていて、i586向けの独自ビルドを公開している方がいますので、基本的にはそれを使えば問題ありません。
だた、公開先がdropboxということと、stable(1.5.0)しかないということで今回は自分でビルドしてみることにしました。また、ビルドスクリプトも公開し、CircleCIでビルドしてGitHubにリリースするようにしました。
※ちなみにRustの公式パッケージについてはこちらで議論されているようで、i586についても言及があります。そのうち公式にi586バイナリが提供されるようになるかもしれません。
リンク
動作確認環境
CPU: AMD Geode LX 800
Distribution: Arch Linux
使い方
適当なところに展開してパスを通せばいいのですが、multirust
を使うと便利です。ただし、i586上で普通にインストールを試みるとunknown CPU type
といわれてしまいますので、以下のようにCPUチェックをごまかします。
curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sed 's/i486/i586/g' | sh
インストール中にエラーが発生して終了しますが無視して問題ありません。multirust
コマンドが使えるようになったら以下のようにtoolchainを登録します。--link-local
で指定するのはi586版を展開したディレクトリ(rustc
バイナリのパスが$path-to-rust-i586-root/bin/rustc
となるように設定)です。
multirust update nightly-i586 --link-local $path-to-rust-i586-root
これでtoolchainとしてnightly-i586(この名前は適当につけて下さい)が使えるようになるので、あとは普通にdefault/override等で指定すれば使えるようになります。
ビルドスクリプト解説
-march
を変えてコンパイルするだけかと思いきやいろいろと小細工が必要だったので一応解説を。CircleCIでビルドするのでメインはcircle.yml
のtest/overrideあたり。
machine:
environment:
CFLAGS: -m32 -march=i586
CXXFLAGS: -m32 -march=i586
LD_LIBRARY_PATH: /usr/local/lib
dependencies:
post:
- sudo apt-get update
- sudo apt-get install gcc-multilib g++-multilib cmake curl:i386 lib32z1
- sudo ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /usr/lib32/libcrypto.so
- sudo ln -s /lib/i386-linux-gnu/libssl.so.1.0.0 /usr/lib32/libssl.so
- sudo ln -s /usr/lib/i386-linux-gnu/libcurl.so.4 /usr/lib32/libcurl.so
- sudo ln -s /usr/lib32/libz.so.1 /usr/lib32/libz.so
test:
override:
- ./env.sh nightly
- ./setup_rust.sh
- make -C rust rustc-stage1
- make -C rust
- sudo make -C rust install
- ./setup_cargo.sh
- make -C cargo
- ./clean.sh
この後も続きますが、基本的にenv.sh
の引数を変えて同じことをしてるだけです。このあたりの重複はなんとかしたいのですがうまい書き方があれば教えてください。(後述する1コマンド2時間制限があるので、単純にスクリプトにまとめるのはうまくいきません)
また、-march
指定は環境変数経由で行います。(-march
を指定する箇所はここと、src/i586_unknown_linux_gnu.rs
の2か所です)
小細工その1
setup_rust.sh
の中でソースの展開後、いくつかのファイルを追加・上書きしています。
# !/bin/sh
git clone https://github.com/rust-lang/rust.git
cd rust
git checkout `cat ../branch`
cp ../src/i586-unknown-linux-gnu.mk ./mk/cfg
cp ../src/i586_unknown_linux_gnu.rs ./src/librustc_back/target
echo ./src/librustc_back/target/mod.rs | xargs sed -i.bak -e 's/i686_unknown_linux_gnu/i586_unknown_linux_gnu/g'
cp ../src/get-snapshot.py ./src/etc
echo ./src/etc/snapshot.py | xargs sed -i.bak -e 's/i686/i586/g'
./configure --target=i586-unknown-linux-gnu --host=i586-unknown-linux-gnu --build=i586-unknown-linux-gnu
cd ..
i586-unknown-linux-gnu.mk
とi586_unknown_linux_gnu.rs
がrustのtarget追加用ファイルです。また、src/librustc_back/target/mod.rs
にtarget一覧があるので、そこのi686-unknown-linux-gnu
をi586-unknown-linux-gnu
に変更します。snapshot.py
/get-snapshot.py
は後述します。
小細工その2
続いてスナップショット関連です。RustコンパイラはRustで書かれているのでコンパイルするにはRustコンパイラが必要です。ここで必要なRustコンパイラはビルドプロセスの途中でダウンロードされていて、これを行っているのがget-snapshot.py
です。また、その中でダウンロードするバイナリのURLなどを決めているのがsnapshot.py
になります。snapshot.py
にはi686用の記述があるのでi586に変更します。さらに、get-snapshot.py
ではrustc
の置き換えを行います。
unpack_snapshot(triple, dl_path)
os.rename("/home/ubuntu/rust-i586/rust/i586-unknown-linux-gnu/stage0/bin/rustc", "/home/ubuntu/rust-i586/rust/i586-unknown-linux-gnu/stage0/bin/rustc.org")
shutil.copy("/home/ubuntu/rust-i586/src/rustc", "/home/ubuntu/rust-i586/rust/i586-unknown-linux-gnu/stage0/bin")
unpack_snapshot
までがオリジナルの記述で、ダウンロードしてきたRustコンパイラを展開しています。その後の2行が追記分で、展開されたrustc
をrustc.org
にリネームし、rustc
のラッパースクリプトを置きます。
# !/bin/sh
args=`echo "$*" | sed s/--target=i586-unknown-linux-gnu/--target=i686-unknown-linux-gnu/g`
$(cd $(dirname $0) && pwd )/rustc.org $args -L i586-unknown-linux-gnu/stage0/lib/rustlib/i586-unknown-linux-gnu/lib
わざわざrustc
を置き換えるのはダウンロードしてきたrustc
がi686用であるためです。最初のsetup_rush.sh
内で./configure --target=i586-unknown-linux-gnu
を指定すると、このrustc
に渡されるtargetもi586-unknown-linux-gnu
になりますが、i686用ではその指定は無効です。そのためラッパースクリプトでtargetをi686に置き換えています。また、rustlibへのパスもi686用しか通っていないのでi586用のパスを指定しておきます。
小細工その3
CircleCIは無料版でもビルド時間は1500分/月ということでビルドに4~5時間かかっても問題ないと思っていたのですが、1コマンドあたり2時間の制限があるそうです。(参考)
当初リポジトリのクローンからmake
まで1つのスクリプトで行ったところ、この2時間制限に引っかかってしまい、やむなく分割しました。しかしmake
だけでも2時間近く(nightlyの場合で1時間50分台でした)かかってしまい、make
も分割したほうが安全そう、ということで以下のように2段階に分けています。今のところ前半で1時間、後半で45分程度なので大丈夫そうですが、これでも足りなくなるとLLVMのコンパイルも分ける必要がありそうです。
- make -C rust rustc-stage1
- make -C rust
小細工その4
最後はcargoのビルドです。
# !/bin/sh
git clone https://github.com/rust-lang/cargo.git
cd cargo
git submodule update --init
python -B src/etc/install-deps.py
./configure --target=i586-unknown-linux-gnu --build=i586-unknown-linux-gnu --host=i586-unknown-linux-gnu --local-rust-root=/usr/local
make
mkdir ../mods
cp -r ~/.cargo/registry/src/github.com-*/* ../mods/
mkdir .cargo
grep -lr target.i686-unknown-linux-gnu.dependencies `pwd`/../mods | awk 'BEGIN{print "paths = ["}//{out=$0;gsub("Cargo.toml","",$out);print "\""$out"\","}END{print "]"}' > .cargo/config
grep -lr target.i686-unknown-linux-gnu.dependencies ../mods | xargs sed -i.bak -e 's/target.i686-unknown-linux-gnu.dependencies/target.i586-unknown-linux-gnu.dependencies/g'
grep -lr "curl-sys = { path = \"curl-sys\", version = \"0.1.0\" }" ../mods | xargs sed -i.bak -e 's/curl-sys = { path = \"curl-sys\", version = \"0.1.0\" }/curl-sys = \"0.1.0\"/g'
make
grep -lr rustc-link-search=native=/usr/lib/x86_64-linux-gnu ./ | xargs sed -i.bak -e 's/rustc-link-search=native=/usr/lib/x86_64-linux-gnu/rustc-link-search=native=/usr/lib32/g'
cd ..
結構長いですが、ポイントはこの2行です。
grep -lr target.i686-unknown-linux-gnu.dependencies `pwd`/../mods | awk 'BEGIN{print "paths = ["}//{out=$0;gsub("Cargo.toml","",$out);print "\""$out"\","}END{print "]"}' > .cargo/config
grep -lr target.i686-unknown-linux-gnu.dependencies ../mods | xargs sed -i.bak -e 's/target.i686-unknown-linux-gnu.dependencies/target.i586-unknown-linux-gnu.dependencies/g'
cargoのビルドはcargoを使って行われるのですが、依存しているライブラリのCargo.toml
に[target.i686-unknown-linux-gnu.dependencies]
という記述がある場合があります。これは特定のプラットフォームでのみ依存関係を記述する場合の指定ですが、今回の場合プラットフォームはi586-unknown-linux-gnu
なので先ほどの記述には該当せず、依存ライブラリが見つからないことになってしまいます。
そのためこのCargo.toml
は書き換える必要があるのですが、単に~/.cargo/registry/src/github.com-...
以下にあるCargo.toml
を書き換えてもビルドを走らせると元に戻ってしまいます。
そこで
mkdir ../mods
cp -r ~/.cargo/registry/src/github.com-*/* ../mods/
のように、依存ライブラリのディレクトリをコピーして
grep -lr target.i686-unknown-linux-gnu.dependencies `pwd`/../mods | awk 'BEGIN{print "paths = ["}//{out=$0;gsub("Cargo.toml","",$out);print "\""$out"\","}END{print "]"}' > .cargo/config
cargoが~/.cargo/registry/src/github.com-...
ではなくコピー先を見に行くように.cargo/config
を生成します。
(.cargo/config
にpaths = ["dir1","dir2"]
と書くとそのパスを優先して見に行くようになります)
grep -lr target.i686-unknown-linux-gnu.dependencies ../mods | xargs sed -i.bak -e 's/target.i686-unknown-linux-gnu.dependencies/target.i586-unknown-linux-gnu.dependencies/g'
このコピーに対してCargo.toml
を書き換えると、書き換えたCargo.toml
を維持したままビルドすることができます。