はじめに
v0.0.1
のようなタグを付けてGitHubにpushすると、以下のように、各OS・アーキテクチャ向けのビルド済みバイナリが、
自動的にReleasesに配置されるRustプロジェクトのサンプルを作成しました。
本記事で使用しているactions-rs/toolchain
およびactions-rs/cargo
が、先月organizationごとarchiveされてしまったようです。
直ちに使用不可となる可能性は低いかもしれませんが、今後はcross
を手動でインストールする等の代替手段を検討した方が良いかもしれません。
ソースコード
リポジトリは以下で公開しています。
今回メインとなるのは以下のファイルです。
このファイルを.github/workflows/
ディレクトリに配置しておくだけで、v0.0.1
のようなtagのpushをトリガーとして、
自動的にワークフローが実行されます。
name: Release
# Releasesへのファイル追加のために書き込み権限が必要
permissions:
contents: write
on:
push:
tags:
- v*
jobs:
build:
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , use-cross: false , extension: "" }
- { os: ubuntu-latest , target: x86_64-unknown-linux-musl , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: armv7-unknown-linux-gnueabihf , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: armv7-unknown-linux-musleabihf , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: aarch64-unknown-linux-musl , use-cross: true , extension: "" }
- { os: macos-latest , target: x86_64-apple-darwin , use-cross: false , extension: "" }
- { os: macos-latest , target: aarch64-apple-darwin , use-cross: false , extension: "" }
- { os: windows-latest , target: x86_64-pc-windows-msvc , use-cross: false , extension: .exe }
steps:
- name: Checkout
uses: actions/checkout@v2
# Rustのpackage名を取得して環境変数に入れておく。(後のステップで使用)
- name: Extract crate information
shell: bash
run: |
echo "PROJECT_NAME=$(sed -n 's/^name = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
# rustcやcargoをインストール
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.job.target }}
override: true
profile: minimal
# targetに応じてcargoもしくはcrossを使用してビルド
- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.job.use-cross }}
command: build
args: --release --target ${{ matrix.job.target }}
# ビルド済みバイナリをリネーム
- name: Rename artifacts
shell: bash
run: |
mv target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.job.target }}${{ matrix.job.extension }}}
# ビルド済みバイナリをReleasesに配置
- name: Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.job.target }}${{ matrix.job.extension }}
解説
書き込み権限の付与
permissions:
contents: write
Releasesへのファイル追加のため、書き込み権限が必要なので付与しています。
リポジトリ設定のActions > General > Workflow permissions
でRead and write permissions
を選択することでも付与可能ですが、
その場合は全ワークフローに書き込み権限が付与されてしまいます。
今回のようにワークフロー単位で最小限の権限のみ付与する方が安全かと思います。
ビルド対象の指定
matrix:
job:
- { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , use-cross: false , extension: "" }
- { os: ubuntu-latest , target: x86_64-unknown-linux-musl , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: armv7-unknown-linux-gnueabihf , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: armv7-unknown-linux-musleabihf , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , use-cross: true , extension: "" }
- { os: ubuntu-latest , target: aarch64-unknown-linux-musl , use-cross: true , extension: "" }
- { os: macos-latest , target: x86_64-apple-darwin , use-cross: false , extension: "" }
- { os: macos-latest , target: aarch64-apple-darwin , use-cross: false , extension: "" }
- { os: windows-latest , target: x86_64-pc-windows-msvc , use-cross: false , extension: .exe }
ビルドを実行するGitHub-hosted runnerのOS、ビルド対象のtarget triple、cross
の使用有無などを指定しています。
cross
を使用することで、cross
がサポートしているARMやMIPSなどのバイナリもビルド可能となります。
今回は、通常のx86_64用、および個人的によく使うARM用のtargetを指定しています。
補足:muslについて
Linux向けのtargetでは、同じアーキテクチャでもgnu
系とmusl
系という2つの選択肢があります。
gnu系はglibcと動的リンクするため、ビルド時と異なる環境では実行できない場合があります。
一方musl系はmusl libcと静的リンクし1、Golangのような依存無しのシングルバイナリが得られるため、どこでも実行出来て便利です。
ただし、musl系はgnu系に比べて遅い2 3という話もあるようです。
また、gnu系とmusl系で一部動作が異なる場合があるため、注意が必要です。
例えば、現在4musl系は、ファイル作成日時を取得するstd::fs::Metadata
のcreated()
メソッドに対応していません。5
以下のコードをそれぞれのtargetでビルド・実行すると、musl系の場合は取得に失敗します。
fn main() -> std::io::Result<()> {
let file = std::fs::File::open("Cargo.toml")?;
let metadata = file.metadata()?;
let _ = dbg!(metadata.created());
Ok(())
}
$ cargo run --target x86_64-unknown-linux-gnu
[src/main.rs:6] metadata.created() = Ok(
SystemTime {
tv_sec: 1698726456,
tv_nsec: 826404242,
},
)
$ cargo run --target x86_64-unknown-linux-musl
[src/main.rs:6] metadata.created() = Err(
Error {
kind: Unsupported,
message: "creation time is not available on this platform currently",
},
)
プロジェクト名の取得
- name: Extract crate information
shell: bash
run: |
echo "PROJECT_NAME=$(sed -n 's/^name = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
ビルド後にバイナリをリネームする際に使用するため、Cargo.toml
からプロジェクト名を取得して環境変数に設定しています。
Rust環境のインストール
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.job.target }}
override: true
profile: minimal
actions-rs/toolchain
を使用して、targetに応じたrustc
やcargo
等をインストールしています。
ビルド
- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.job.use-cross }}
command: build
args: --release --target ${{ matrix.job.target }}
actions-rs/cargo
を使用してビルドを行ないます。
use-cross
がtrueの場合はcross
、falseの場合はcargo
が使用されます。
ビルド済みバイナリのリネーム
- name: Rename artifacts
shell: bash
run: |
mv target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.job.target }}${{ matrix.job.extension }}}
ビルド済みバイナリのファイル名はtargetによらず同じになってしまうため、Releasesへ配置する前に、
hoge-v0.0.1-x86_64-unknown-linux-gnu
のような名前にリネームしています。
バイナリをReleasesに配置
- name: Release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: |
target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.job.target }}${{ matrix.job.extension }}
softprops/action-gh-releaseを使用してバイナリをReleasesに配置しています。
小ネタ
今回参考にしたpastelのworkflowでは、targetに応じたstrip
コマンドを使い分けて、ビルド済みバイナリのサイズを削減していました。
昨年リリースされたCargo 1.59でstrip
オプションが安定化されたため、現在はCargo.toml
に以下の内容を記述しておくだけで同様の効果が得られます。
[profile.release]
strip = "symbols"
おわりに
依存クレート次第では、cross
を使用しても一部targetでのビルドが上手くいかない場合もあります。
それでも上手くビルド出来た場合は、Raspberry PiやM5Stack UnitV2、スマホなどの上でそのまま動くARM用バイナリを簡単に得られるので、一度試してみる価値はあると思います。
(非力なRaspberry Pi上でビルドしなくて済むのは嬉しい)
参考リンク
- GitHub Actions設定の参考にしたリポジトリ
- 使用したAction
-
ただし、
mipsel-unknown-linux-musl
とmips-unknown-linux-musl
はデフォルトでは動的リンクとなります。
https://github.com/rust-lang/rust/issues/80693 ↩ -
https://www.reddit.com/r/rust/comments/gdycv8/why_does_musl_make_my_code_so_slow/ ↩
-
本記事執筆時最新の安定版であるRust 1.73.0時点 ↩
-
作成日時を取得出来る
statx
システムコールにmusl libcが対応していないようです。
https://github.com/rust-lang/rust/blob/1.73.0/library/std/src/sys/unix/fs.rs#L95-L248 ↩