ヴェズルフェルニルの研究ノート

座右の銘「ただ一人犀の角のように歩め」的な研究活動ノート

【Rust】別ターゲット用にプログラムをクロスコンパイルする

ここ最近、毎日Rustを使ってRaspberry Pi用プログラムを書いている。センサー・デバイスから取得した計測データをSQLiteデータベースに保存して、同時にWebSocket経由でサーバーへ転送するようなものだ。ターゲットはRaspberry Piだが、Rustプログラムの開発ホスト機としてMacを使っている。

前記事にRustのインストールと開発環境の構築手順を書いたが、

blog.ketus-ix.work

この記事の中で、下のようなRustコンポーネントの一覧を取得するコマンドを紹介した。

% rustup component list
cargo-x86_64-apple-darwin (installed)
clippy-x86_64-apple-darwin (installed)
llvm-bitcode-linker-x86_64-apple-darwin
llvm-tools-x86_64-apple-darwin
rls-x86_64-apple-darwin
rust-analysis-x86_64-apple-darwin
rust-analyzer-x86_64-apple-darwin
rust-docs-x86_64-apple-darwin (installed)
rust-src
rust-std-aarch64-apple-darwin
rust-std-aarch64-apple-ios
rust-std-aarch64-apple-ios-sim
rust-std-aarch64-linux-android
rust-std-aarch64-pc-windows-gnullvm
rust-std-aarch64-pc-windows-msvc
rust-std-aarch64-unknown-fuchsia
rust-std-aarch64-unknown-linux-gnu
rust-std-aarch64-unknown-linux-musl
rust-std-aarch64-unknown-linux-ohos
rust-std-aarch64-unknown-none
rust-std-aarch64-unknown-none-softfloat
rust-std-aarch64-unknown-uefi
rust-std-arm-linux-androideabi
rust-std-arm-unknown-linux-gnueabi
rust-std-arm-unknown-linux-gnueabihf
rust-std-arm-unknown-linux-musleabi
rust-std-arm-unknown-linux-musleabihf
rust-std-armebv7r-none-eabi
rust-std-armebv7r-none-eabihf
rust-std-armv5te-unknown-linux-gnueabi
rust-std-armv5te-unknown-linux-musleabi
rust-std-armv7-linux-androideabi
rust-std-armv7-unknown-linux-gnueabi
rust-std-armv7-unknown-linux-gnueabihf
rust-std-armv7-unknown-linux-musleabi
rust-std-armv7-unknown-linux-musleabihf
rust-std-armv7-unknown-linux-ohos
rust-std-armv7a-none-eabi
rust-std-armv7r-none-eabi
rust-std-armv7r-none-eabihf
rust-std-i586-pc-windows-msvc
rust-std-i586-unknown-linux-gnu
rust-std-i586-unknown-linux-musl
rust-std-i686-linux-android
rust-std-i686-pc-windows-gnu
rust-std-i686-pc-windows-gnullvm
rust-std-i686-pc-windows-msvc
rust-std-i686-unknown-freebsd
rust-std-i686-unknown-linux-gnu
rust-std-i686-unknown-linux-musl
rust-std-i686-unknown-uefi
rust-std-loongarch64-unknown-linux-gnu
rust-std-loongarch64-unknown-none
rust-std-loongarch64-unknown-none-softfloat
rust-std-nvptx64-nvidia-cuda
rust-std-powerpc-unknown-linux-gnu
rust-std-powerpc64-unknown-linux-gnu
rust-std-powerpc64le-unknown-linux-gnu
rust-std-riscv32i-unknown-none-elf
rust-std-riscv32im-unknown-none-elf
rust-std-riscv32imac-unknown-none-elf
rust-std-riscv32imafc-unknown-none-elf
rust-std-riscv32imc-unknown-none-elf
rust-std-riscv64gc-unknown-linux-gnu
rust-std-riscv64gc-unknown-none-elf
rust-std-riscv64imac-unknown-none-elf
rust-std-s390x-unknown-linux-gnu
rust-std-sparc64-unknown-linux-gnu
rust-std-sparcv9-sun-solaris
rust-std-thumbv6m-none-eabi
rust-std-thumbv7em-none-eabi
rust-std-thumbv7em-none-eabihf
rust-std-thumbv7m-none-eabi
rust-std-thumbv7neon-linux-androideabi
rust-std-thumbv7neon-unknown-linux-gnueabihf
rust-std-thumbv8m.base-none-eabi
rust-std-thumbv8m.main-none-eabi
rust-std-thumbv8m.main-none-eabihf
rust-std-wasm32-unknown-emscripten
rust-std-wasm32-unknown-unknown
rust-std-wasm32-wasi
rust-std-wasm32-wasip1
rust-std-wasm32-wasip1-threads
rust-std-x86_64-apple-darwin (installed)
rust-std-x86_64-apple-ios
rust-std-x86_64-fortanix-unknown-sgx
rust-std-x86_64-linux-android
rust-std-x86_64-pc-solaris
rust-std-x86_64-pc-windows-gnu
rust-std-x86_64-pc-windows-gnullvm
rust-std-x86_64-pc-windows-msvc
rust-std-x86_64-unknown-freebsd
rust-std-x86_64-unknown-fuchsia
rust-std-x86_64-unknown-illumos
rust-std-x86_64-unknown-linux-gnu
rust-std-x86_64-unknown-linux-gnux32
rust-std-x86_64-unknown-linux-musl
rust-std-x86_64-unknown-linux-ohos
rust-std-x86_64-unknown-netbsd
rust-std-x86_64-unknown-none
rust-std-x86_64-unknown-redox
rust-std-x86_64-unknown-uefi
rustc-x86_64-apple-darwin (installed)
rustc-dev-aarch64-apple-darwin
rustc-dev-aarch64-pc-windows-msvc
rustc-dev-aarch64-unknown-linux-gnu
rustc-dev-aarch64-unknown-linux-musl
rustc-dev-arm-unknown-linux-gnueabi
rustc-dev-arm-unknown-linux-gnueabihf
rustc-dev-armv7-unknown-linux-gnueabihf
rustc-dev-i686-pc-windows-gnu
rustc-dev-i686-pc-windows-msvc
rustc-dev-i686-unknown-linux-gnu
rustc-dev-loongarch64-unknown-linux-gnu
rustc-dev-powerpc-unknown-linux-gnu
rustc-dev-powerpc64-unknown-linux-gnu
rustc-dev-powerpc64le-unknown-linux-gnu
rustc-dev-riscv64gc-unknown-linux-gnu
rustc-dev-s390x-unknown-linux-gnu
rustc-dev-x86_64-apple-darwin
rustc-dev-x86_64-pc-windows-gnu
rustc-dev-x86_64-pc-windows-msvc
rustc-dev-x86_64-unknown-freebsd
rustc-dev-x86_64-unknown-illumos
rustc-dev-x86_64-unknown-linux-gnu
rustc-dev-x86_64-unknown-linux-musl
rustc-dev-x86_64-unknown-netbsd
rustc-docs-x86_64-unknown-linux-gnu
rustfmt-x86_64-apple-darwin (installed)

この出力情報から、コマンド "rustup" によって複数ターゲット用のクロスコンパイラをインストールできることが判る。

Rustでは公式サイトによってクロスコンパイラが配布されており、別ターゲットを対象としたクロスプラットフォーム開発環境が簡単に構築できるようになっている。これはRustの大きな利点であり、最近のプロジェクト(特に組み込み系)でRustが採用されるケースが増える要因となっている。

Rustを使うと、クロスコンパイル環境さえ構築すれば、Mac, WindowsLinux,組み込みLinux搭載機、Androidモバイル機、さらにOS無しマイコン環境さえも一つのプロジェクトで対応できるようになる。

本記事では、Rustによるクロスコンパイル環境の構築方法を紹介する。

ターゲット機としてRaspberry Pi OS 64-bit(Raspberry Pi 4用)を想定しているが、コンパイラやリンカの設定方法が変わるだけで、Linux搭載機がターゲットなら同じ手順でクロスコンパイル環境を構築できる。

cross パッケージによるクロスコンパイル

cross パッケージのインストール

% cargo install cross

プロジェクトのクロスビルド

% cd PROJECT_DIR
% cross build --target aarch64-unknown-linux-gnu

ビルドによって生成された実行可能ファイルはプロジェクト・ディレクリ内の以下のパスに置かれる。

  • オプションなし(または--debugオプション付き)で "cross build --target" コマンドを実行した場合〔Debugビルド〕

 target/aarch64-unknown-linux-gnu/debug/<project_name>

  • --releaseオプション付きで "cross build --target" コマンドを実行した場合〔Releaseビルド〕

 target/aarch64-unknown-linux-gnu/release/<project_name>

コマンド "cross build" はDockerコンテナを利用してプログラムのクロスコンパイルを行っており、Dockerがインストール済みで、"docker" コマンドが非root権限ユーザーによって実行できる状態であることが本コマンドを使える前提条件となる。

プロジェクト・プログラムの実行

cargo環境に cross パッケージを導入済みの場合、別ターゲット用にビルドしたプログラムを開発ホスト機上で実行することができる。

% cross run --target aarch64-unknown-linux-gnu

このコマンドはQEMUを使ってプログラムをエミュレート実行している。

ターゲット用リンカを使うクロスコンパイル

ターゲット用リンカのインストール

% brew tap messense/macos-cross-toolchains
% brew install aarch64-unknown-linux-gnu
$ sudo apt install gcc-aarch64-linux-gnu

ターゲット用ツールチェインのインストール

% rustup target add aarch64-unknown-linux-gnu

ターゲット用ビルド設定の作成

プロジェクト・ディレクトリ内に以下のような内容の.cargo/config.tomlというファイルを作成する。

% cd PROJECT_DIR
% mkdir .cargo
% vi .cargo/config.toml
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

プロジェクトのクロスビルド

上記の手順操作が済んでいる状態で、プロジェクト・ディレクトリ内で下のようなコマンドを実行すると、当該プログラムを指定ターゲット用にビルドすることができる。

% cargo build --target aarch64-unknown-linux-gnu

zigbuild パッケージによるクロスコンパイル

Zigというプログラミング言語が在るが、このZigにLLVMベース・ツールチェインが統合されたZig ccというものがある。

zig.guide

このZig ccをcargo環境で使えるようにした cargo-zigbuild というRustパッケージが存在する。

github.com

この cargo-zigbuild パッケージを利用すると、Rustのクロスコンパイル環境を構築できる。

zigbuild パッケージのインスール

% brew install zig
% cargo install cargo-zigbuild
$ snap install zig --classic --beta
$ cargo install cargo-zigbuild

プロジェクトのクロスビルド

% cd PROJECT_DIR
% cargo zigbuild --target aarch64-unknown-linux-gnu

【補足説明】

クロスビルドによって生成したRustプログラムの実行可能ファイルをターゲット機へ転送するには、SSH経由で行うのが良く利用される方法だろう。

scpを使う方法

% scp target/aarch64-unknown-linux-gnu/debug/hello_rust <remote_user>@<target_remote_host>://tmp/

rsyncを使う方法

% rsync -av -e ssh target/aarch64-unknown-linux-gnu/debug/hello_rust <remote_user>@<target_remote_host>://tmp/

転送したプログラムの動作確認は、ターゲット機のコンソールから直接実行するか、あるいは、以下のようにSSH経由でターゲット機にログインした上で転送済みの実行可能ファイルを起動すれば良い。

% ssh <remote_user>@<target_remote_host>
% cd /tmp
% ./hello_rust
Hello, world!

【参照リンク】

qiita.com

qiita.com

zenn.dev