Read OSS

Tauri ツールチェーン: CLI、ビルドパイプライン、クロスプラットフォームバンドル

中級

前提知識

  • 第1回: アーキテクチャと Crate マップ
  • 第2回: アプリライフサイクルと Builder パターン
  • build.rs スクリプトと Cargo ビルドプロセスへの理解
  • プラットフォーム固有のパッケージングの基本知識

Tauri ツールチェーン: CLI、ビルドパイプライン、クロスプラットフォームバンドル

これまでの記事では、起動中の Tauri アプリケーションの内部処理を掘り下げてきました。しかし、ソースコードが配布可能なバイナリへと変わるまでの流れはどうなっているのでしょうか。その答えは、すべてを取りまとめる CLI、コンパイル時コード生成を含む多段階のビルドパイプライン、そして DMG・MSI・DEB などを生成するバンドラーの連携にあります。その道筋を順を追って見ていきましょう。

CLI のアーキテクチャとコマンド体系

Tauri CLI は crates/tauri-cli に実装されており、2 通りの方法で配布されています。Cargo バイナリ(cargo install tauri-cli)と、NAPI 経由で Rust バイナリをラップした npm パッケージ(@tauri-apps/cli)です。

エントリーポイントである main.rs は、Cargo サブコマンド(cargo tauri dev)として呼び出された際に生じる微妙な問題を処理しています。この形式で呼び出されると、バイナリには cargo-tauri tauri dev という引数が渡されるため、tauri が二重に現れてしまいます。main 関数は、バイナリ名が cargo-tauri かどうかを確認し、最初の引数を先読みすることでこれを検出します。

Some("cargo-tauri") => {
    if args.peek().and_then(|s| s.to_str()) == Some("tauri") {
        args.next(); // remove the extra cargo subcommand
        Some("cargo tauri".into())
    } else {
        Some("cargo-tauri".into())
    }
}

コマンド体系は、lib.rs において Clap の derive API を使って定義されています。

graph TD
    CLI["cargo tauri"] --> init["init"]
    CLI --> dev["dev"]
    CLI --> build["build"]
    CLI --> bundle["bundle"]
    CLI --> android["android"]
    CLI --> ios["ios"]
    CLI --> add["add"]
    CLI --> remove["remove"]
    CLI --> plugin["plugin"]
    CLI --> icon["icon"]
    CLI --> signer["signer"]
    CLI --> info["info"]
    CLI --> migrate["migrate"]
    CLI --> permission["permission"]
    CLI --> capability["capability"]
    CLI --> inspect["inspect"]

主なワークフローは devbuild の 2 つです。addremove はプラグインの依存関係を管理し、permissioncapability は ACL ファイルの管理を補助します。これらのコマンドが揃っていること自体、セキュリティシステムが開発ワークフローの中心に位置していることを示しています。

設定の解決

Tauri は 3 種類の設定フォーマットをサポートしています: JSON(tauri.conf.json)、JSON5(tauri.conf.json5config-json5 フィーチャーが必要)、TOML(Tauri.tomlconfig-toml フィーチャーが必要)です。

アプリケーション全体の設定を保持するマスター Config 構造体 は次のようになっています。

pub struct Config {
    pub schema: Option<String>,
    pub product_name: Option<String>,
    pub main_binary_name: Option<String>,
    pub version: Option<String>,
    pub identifier: String,
    pub app: AppConfig,
    pub build: BuildConfig,
    pub bundle: BundleConfig,
    pub plugins: PluginConfig,
}
flowchart LR
    FILE["tauri.conf.json<br/>tauri.conf.json5<br/>Tauri.toml"] --> PARSE["Config::parse()"]
    ENV["TAURI_CONFIG env var<br/>(JSON string)"] --> MERGE["deep merge"]
    PLATFORM["Platform-specific<br/>tauri.macos.conf.json"] --> MERGE
    PARSE --> MERGE
    MERGE --> CONFIG["Final Config struct"]
    CONFIG --> CODEGEN["tauri-codegen"]
    CONFIG --> CLI["CLI commands"]

TAURI_CONFIG 環境変数は、CLI が設定のオーバーライドを Cargo ビルドプロセスへ渡すための仕組みです。CLI は設定を読み込み、必要に応じてプラットフォーム固有のオーバーライド(例: tauri.macos.conf.json)とマージしたうえで、その結果を JSON にシリアライズして TAURI_CONFIG にセットします。コンパイル時には、tauri-codegen 内の get_config() がこれを読み取り、ファイルベースの設定とディープマージを行います。

ヒント: TAURI_CONFIG 環境変数には任意の有効な JSON 文字列を渡せます。CI/CD でのオーバーライドに活用できます。たとえば TAURI_CONFIG='{"identifier":"com.example.staging"}' cargo tauri build とすれば、設定ファイルを一切書き換えることなく identifier だけを上書きできます。

build.rs パイプライン: tauri-build

ユーザーの build.rstauri-build を呼び出し、コンパイル時の準備をまとめて処理します。

sequenceDiagram
    participant BS as build.rs
    participant TB as tauri-build
    participant ACL as ACL Resolution
    participant CG as tauri-codegen

    BS->>TB: tauri_build::build()
    TB->>ACL: Resolve capabilities & permissions
    ACL-->>TB: Resolved ACL struct
    TB->>TB: Generate Windows manifest (.rc)
    TB->>TB: Copy resources & external binaries
    TB->>TB: Set cargo:rerun-if-changed directives
    TB->>CG: Generate Context (if codegen feature)
    CG-->>TB: tauri-build-context.rs

ACL 解決ステップ(crates/tauri-build/src/acl.rs)は特に重要です。各依存クレートの $OUT_DIR からすべてのプラグインマニフェストを検索し、アプリの capability ファイルとマージしたうえで完全なパーミッションツリーを解決し、その結果をシリアライズされたデータとして書き出します。このデータは後で generate_context!(またはコード生成フィーチャー)によってバイナリに埋め込まれます。

Windows では、tauri-build はアプリケーションアイコンとバージョン情報のためのリソースファイル(.rc)も生成し、Visual C++ ランタイムのリンク方式(スタティックかダイナミックか)の処理も担います。

コンパイル時コード生成: アセットとコンテキスト

コード生成パイプラインは、設定とフロントエンドアセットを受け取り、Context 構造体を Rust コードとして生成します。第 2 回で説明したように、これは generate_context!(proc macro)または codegen フィーチャーを有効にした tauri-build によってトリガーされます。

InvokeInitializationScript テンプレートは特に注目すべき存在です。

#[derive(Template)]
#[default_template("../scripts/ipc-protocol.js")]
pub(crate) struct InvokeInitializationScript<'a> {
    pub(crate) process_ipc_message_fn: &'a str,
    pub(crate) os_name: &'a str,
    pub(crate) fetch_channel_data_command: &'a str,
    pub(crate) invoke_key: &'a str,
}

これはすべての webview で window.__TAURI_INTERNALS__ を初期化する JavaScript を生成します。process_ipc_message_fnPROCESS_IPC_MESSAGE_FN から取得されており、scripts/process-ipc-message-fn.js をインライン化した JavaScript です。invoke key は Builder::new() が生成するランダムトークンです。

バンドラー: クロスプラットフォームパッケージング

tauri-bundler クレートは、コンパイル済みバイナリをプラットフォーム固有のパッケージにラップします。CLI の build コマンドは Rust プロジェクトをリリースモードでコンパイルし、続いて設定された各ターゲットに対してバンドラーを呼び出します。

flowchart TD
    BINARY["Compiled binary"] --> BUNDLER["tauri-bundler"]
    BUNDLER --> MAC{"macOS?"}
    BUNDLER --> WIN{"Windows?"}
    BUNDLER --> LIN{"Linux?"}

    MAC -->|"Yes"| APP[".app bundle"]
    MAC -->|"Yes"| DMG["DMG installer"]
    WIN -->|"Yes"| MSI["MSI installer"]
    WIN -->|"Yes"| NSIS["NSIS installer"]
    LIN -->|"Yes"| DEB["DEB package"]
    LIN -->|"Yes"| RPM["RPM package"]
    LIN -->|"Yes"| APPIMAGE["AppImage"]

各バンドラーは BundleConfig からアプリケーションアイコン、identifier、ファイルの関連付け、著作権情報、外部署名コマンドといった設定を読み取ります。パッケージに含めるサイドカーバイナリやリソースファイルの処理も担います。

macOS バンドラーは .app バンドルのディレクトリ構造を作成し、設定値をもとに Info.plist を生成します。必要に応じてコード署名と公証のために tauri-macos-sign も呼び出します。Windows バンドラーは MSI(WiX を使用)と NSIS の両方に対応しており、マシン単位またはユーザー単位のインストールを選択できます。Linux バンドラーは適切な依存関係を持つ Debian パッケージ、RPM スペック、自己完結型の AppImage ファイルを生成します。

開発モード: tauri dev

tauri dev コマンドはホットリロードを備えた開発体験を提供します。フロントエンドの dev サーバー(Vite や webpack など)を起動し、その後 dev cargo プロファイルで Rust バックエンドをコンパイル・実行します。

重要な仕組みが PROXY_DEV_SERVER 定数です。

pub(crate) const PROXY_DEV_SERVER: bool = cfg!(all(dev, mobile));

デスクトップの dev モードでは、webview は直接 dev サーバーの URL(例: http://localhost:1420)から読み込みます。tauri:// プロトコルハンドラー は引き続き登録されていますが、主にフォールバックとして機能します。

モバイルでは事情が異なります。モバイルの webview は localhost を使えないため、PROXY_DEV_SERVERtrue となり、tauri:// プロトコルがすべてのリクエストを dev サーバーへプロキシします。これにより、モバイル webview が dev サーバーから読み込みながらも、一部の Web API が要求するセキュアコンテキストを維持できます。

ヒント: tauri dev は Rust ソースの変更を監視して自動的に再コンパイルします。一方、フロントエンドアセットはバイナリに埋め込まれるのではなく dev サーバーが配信するため、フロントエンドの変更は Rust の再コンパイルなしに即座に反映されます。この役割の分離こそが、Tauri の開発体験を高速にしている理由です。

このシリーズの最終回では、最も低いレイヤーへと降りていきます。Tauri とプラットフォームの間に位置するランタイム抽象化、スレッドセーフな通信のためのディスパッチャーパターン、そして同じコードベースがデスクトップとモバイルの両方にまたがる仕組みを解説します。