よしたかの日常

日常の事やイラスト投稿、開発している時の備忘録を書いています。

Rust GUI アプリの GitHub Actions 配布 — cargo-bundle で 3 OS バンドル

おはこんばんにちは!

タグで 3 OS ビルド では、 card_capture など ターミナルゲーム向けに cargo build --release の生バイナリ を Artifact に載せる話を書きました。

TBH Skill Simulator は egui / eframe の GUI アプリ です。
配布物も「実行ファイル 1 個」ではなく、.deb / .app / .exe(アイコン・メタデータ付き) にしたくなりました。

そこで cargo-bundle を足し、 GitHub Actions の workflow も bundle 出力向け に載せ替えています。

ゲームルールや egui UI の話は TBH Skill Simulator に任せ、 ここでは GUI 配布インフラだけ を書きます。

▲ v* タグ push 後。linux(deb)/ macos(app)/ windows(exe)の 3 job が並んでいる状態。


なぜ CLI のタグビルドと分けたか

観点 CLI 版(card_capture 等) GUI 版(TBH Skill Simulator)
アプリ種別 CLI / TUI GUI(eframe)
ビルド cargo build --release cargo bundle --release
Linux 成果物 生バイナリ .deb パッケージ
macOS 成果物 生バイナリ .app バンドル
Windows 成果物 .exe 1 ファイル アイコン埋め込み exe
Cargo.toml [profile.release] [package.metadata.bundle] 等

CLI 版の「タグ → 3 OS マトリクス → Artifact」という骨格は同じです。 成果物の形と Cargo 設定が別物 なので、シリーズを分けました。

シリーズ 中身
タグで 3 OS ビルド CLI/TUI の生バイナリ配布
cargo-bundle GUI の cargo-bundle 配布

追加・変更したファイル

tbh_skill_simulator/
  Cargo.toml                      … [package.metadata.bundle] / winresource
  build.rs                        … Windows ICO 埋め込み(既存 codegen に追加)
  bundle.sh                       … ローカル bundle 用
  .github/workflows/build.yml     … cargo bundle + Artifact

card_capture の CI から流用したのは タグトリガー・3 OS マトリクス・MinGW クロスビルド です。
cargo build を cargo bundle に差し替え、Artifact の path を release/bundle/** に変えたのが主な差分です。


[package.metadata.bundle] — cargo-bundle の設定

[package.metadata.bundle]
identifier = "jp.ktgwya.tbh-skill-simulator"
name = "TBH Skill Simulator"
icon = ["assets/icon.png"]
category = "Game"
short_description = "Skill build simulator for TBH"
linux_use_terminal = false
deb_depends = [
    "libgtk-3-0",
    "libxcb-render0",
    "libxcb-shape0",
    "libxcb-xfixes0",
    "libxkbcommon0",
    "libvulkan1",
    "libssl3",
]
キー 意味
identifier macOS / Linux のアプリ ID
icon バンドル用アイコン(PNG)
linux_use_terminal false — GUI アプリとして .desktop を生成
deb_depends .deb インストール時に要求する システムライブラリ

eframe / wgpu 系は Linux で GTK や XCB、Vulkan 等に依存します。
CLI ゲームには不要だった deb_depends の列挙 が GUI 版の追加コストです。

Windows 向け exe の表示名は別セクションです。

[package.metadata.winresource]
ProductName = "TBH Skill Simulator"
FileDescription = "Skill build simulator for TBH"
OriginalFilename = "TBH Skill Simulator.exe"

winresource クレート(build-dependencies)と組み合わせて、 エクスプローラ上のプロパティに載る文字列を指定しています。


build.rs — PNG から ICO を生成して exe に埋め込む

cargo-bundle の icon は macOS / Linux 向けです。
Windows の exe アイコン は build.rs で別途埋め込みます。

fn embed_windows_resources(manifest_dir: &Path, out_dir: &Path) {
    if env::var("CARGO_CFG_TARGET_OS").ok().as_deref() != Some("windows") {
        return;
    }

    let png_path = manifest_dir.join("assets/icon.png");
    let ico_path = out_dir.join("app.ico");
    write_ico_from_png(&png_path, &ico_path);

    winresource::WindowsResource::new()
        .set_icon(ico_path.to_str().expect("invalid ico path"))
        .compile()
        .expect("failed to compile Windows resources");
}

PNG 1 枚から複数サイズの ICO を作ります。

for size in [256, 128, 64, 48, 32, 16] {
    let rgba = image
        .resize_exact(size, size, image::imageops::FilterType::Lanczos3)
        .to_rgba8();
    // ...
}
タイミング 対象 OS やること
build.rs Windows のみ exe リソースに ICO 埋め込み
cargo bundle -f osx macOS .app に PNG アイコン
cargo bundle -f deb Linux .deb にデスクトップエントリ + アイコン

TBH 2 本目 の JSON codegen と 同じ build.rs に同居しています。
CARGO_CFG_TARGET_OS で Windows ビルド時だけ ICO 生成が走るので、Linux/macOS の CI には影響しません。


GitHub Actions — CLI 版からの差分

トリガーとマトリクスは タグで 3 OS ビルド と同型です。

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:

cargo-bundle のインストール

- name: Install cargo-bundle
  run: cargo install cargo-bundle --locked

CLI 版では不要だったステップです。CI ごとに cargo install するので、初回ビルドはやや長く なります。

OS ごとに bundle 形式を切り替え

- name: Build and bundle
  run: |
    case "${{ matrix.job.target }}" in
      x86_64-pc-windows-gnu)
        cargo bundle --release --target x86_64-pc-windows-gnu -f exe
        # cargo-bundle 0.11.0 が .exe を二重付けするため修正
        for f in target/x86_64-pc-windows-gnu/release/bundle/exe/*.exe.exe; do
          mv "$f" "${f%.exe}"
        done
        ;;
      x86_64-unknown-linux-gnu)
        cargo bundle --release --target x86_64-unknown-linux-gnu -f deb
        ;;
      x86_64-apple-darwin)
        cargo bundle --release --target x86_64-apple-darwin -f osx
        ;;
    esac
target -f 出力例
x86_64-pc-windows-gnu exe bundle/exe/TBH Skill Simulator.exe
x86_64-unknown-linux-gnu deb bundle/deb/tbh-skill-simulator_0.1.3_amd64.deb
x86_64-apple-darwin osx bundle/osx/TBH Skill Simulator.app

CLI 版の cargo build + 生バイナリ path 指定から、release/bundle/**/* 丸ごと に変わります。

- name: Upload Artifact
  uses: actions/upload-artifact@v4
  with:
    name: ${{ github.event.repository.name }}-${{ github.ref_name }}-${{ matrix.job.artifact-name }}
    path: |
      target/${{ matrix.job.target }}/release/bundle/**/*
    if-no-files-found: error

GUI 版は bundle が空だと配布不能なので、if-no-files-found: error にしています。
CLI 版の ignore は「Linux job に .exe path がなくても落ちない」ための保険でしたが、 bundle 方式では 各 OS とも bundle ディレクトリが必須 です。


.exe.exe 二重付けの workaround

cargo-bundle 0.11.0 では、Windows クロスビルド時に Something.exe.exe になることがあります。

for f in target/x86_64-pc-windows-gnu/release/bundle/exe/*.exe.exe; do
  mv "$f" "${f%.exe}"
done

CI 内でリネームしてから Artifact に載せています。
upstream の修正を待つ間の 実務的な回避 です。


ローカル確認 — bundle.sh

CI 以外で bundle を試す用のシェルスクリプトです。

# Windows(MinGW が必要)
cargo bundle --release --target x86_64-pc-windows-gnu -f exe

# Linux
cargo bundle --release -f deb
sudo dpkg -i target/release/bundle/deb/tbh-skill-simulator_*.deb

Linux では dpkg -i まで一気に試せます。
macOS 向け -f osx は CI(macos-latest runner)側で確認する想定です。

▲ Ubuntu 等で .deb インストール後、アプリランチャーに TBH Skill Simulator が載っている状態。


CLI 版との対応表(コピペ用)

項目 CLI 版 GUI 版
ビルドコマンド cargo build --release --target … cargo bundle --release --target … -f …
追加 install なし cargo install cargo-bundle
Cargo.toml [profile.release] [package.metadata.bundle] + winresource
build.rs なし(または codegen のみ) + Windows ICO 埋め込み
Artifact path target/.../release/バイナリ target/.../release/bundle/**/*
Linux 依存 動的リンクなし想定 deb_depends 必須

タグ運用(git tag v0.1.3 && git push origin v0.1.3)と Artifact 命名規則は CLI 版と同じです。
Releases 自動作成も、現状は 手動添付 のままです。


設計で意識したこと

1. CLI 版の workflow をベースに差し替え

3 OS マトリクスと MinGW クロスビルドは実績のある構成をそのまま使い、 ビルドコマンドと Artifact path だけ GUI 向け に変えました。

2. アイコンは「実行ファイル」と「バンドル」で二系統

  • 実行中のウィンドウアイコン → main.rs の include_bytes! + with_icon
  • 配布物のアイコン → metadata.bundle + Windows は build.rs の ICO

同じ assets/icon.png を源に、用途ごとに変換しています。

3. Linux は deb、macOS は app

「バイナリを tarball で渡す」より、各 OS の慣習に近い形 を選びました。 個人ツールでも、初めて GUI を配るなら cargo-bundle の方が楽でした。

4. GUI 記事とインフラ記事を分離

TBH Skill Simulator 読者が 突然 deb_depends に遭遇しないよう、bundle / CI はこちらに集約しています。


動かしてみる

git clone https://github.com/yoshitaka-k/tbh_skill_simulator.git
cd tbh_skill_simulator

# ローカル(Linux 例)
cargo install cargo-bundle --locked
cargo bundle --release -f deb
ls target/release/bundle/deb/

# CI と同じタグ運用
git tag v0.1.3
git push origin v0.1.3
# → Actions から deb / app / exe を Artifact ダウンロード

確認ポイント:

  1. 3 job すべて bundle/ 以下にファイルができるか
  2. Windows Artifact の exe が .exe.exe になっていないか
  3. Linux deb を dpkg -i したあと GUI が起動するか
  4. exe のプロパティにアイコンと ProductName が載るか

まとめ

TBH Skill Simulator の GUI 配布は次のとおりです。

  • cargo-bundle — OS ごとに deb / osx / exe 形式で出力
  • [package.metadata.bundle] — アイコン・deb 依存・GUI フラグ
  • build.rs + winresource — Windows exe へ ICO 埋め込み
  • GitHub Actions — CLI 版と同型マトリクス + bundle path
  • .exe.exe リネーム — cargo-bundle 0.11.0 の暫定対処

CLI/TUI 向けタグビルドの延長として、GUI アプリ用の bundle レイヤ を足した形です。


関連記事


参考リンク