Open8

tauri1.x→tauri2.0(beta.19)migration + iOSアプリ開発(現在エラー発生中 beta.23でNG)

yunayuna

現在ベータ版のtauri2.0(v2.0.0-beta.23)で、iOSアプリ開発をしてみます。

javascriptランタイムは、bun(1.1.8)を利用します。

ネタは、chatGPTのクライアントアプリ。
win,macos,linux用のクライアントとしては既に作成済みなので、
基本的には、このプログラムをtauri2.0にmigrationしながら、変更点を確認していきます。
https://github.com/generalworksinc/ai_client

参考にさせていただいた記事
※tauri2.0の導入はこちらの記事が詳しいので、ここではある程度省略して、その先の実践的な内容をメモしていきます。
https://zenn.dev/myuna/scraps/618340df8575bb

※bunでの地雷もいくつかあったので、そのままメモしていきます。

yunayuna

プロジェクト作成&初期設定を進めます。
frontは、javascript(vue)で実装します。

$ bun create tauri-app --beta

✔ Project name · ai_client
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, bun)
✔ Choose your package manager · bun
✔ Choose your UI template · Vue - (https://vuejs.org/)
✔ Choose your UI flavor · JavaScript
✔ Would you like to setup the project for mobile as well? · yes

Template created! To get started run:
  cd ai_client
  bun install
  bun run tauri android init
  bun run tauri ios init

For Desktop development, run:
  bun run tauri dev

For Android development, run:
  bun run tauri android dev

For iOS development, run:
  bun run tauri ios dev

動作確認してみる

$ bun i
$ bun run tauri dev

OK.

yunayuna

1.xとの違い

  • バックエンドの処理が、main.rsではなく
    lib.rsに記述されている(mobile対応にしたから?)
lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

mainはこうなってた

main.rs
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

fn main() {
    ai_client_lib::run()
}
yunayuna

iOSビルドの準備

https://v2.tauri.app/start/prerequisites/#ios
rustのtargetとして、ios系を追加する

rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim

homebrew, cocoapadをインストール

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install cocoapods

create project後のインフォメーションの通り、iOS用のinitを行い、devで立ち上げる
※bunでios initをすると、現時点でバグがあり先に進めなくなります。
 回避したい場合、ここだけ、npm run tauri ios init を実行してください。

bun run tauri ios init

run.
シミュレータを選択して実行

$ bun run tauri ios dev

==> Running `brew cleanup ios-deploy`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Detected iOS simulators:
  [0] iPad (10th generation)
  [1] iPad (10th generation)
  [2] iPad Air (5th generation)
  [3] iPad Air (5th generation)
  [4] iPad Pro (11-inch) (4th generation)
  [5] iPad Pro (11-inch) (4th generation)
  [6] iPad Pro (12.9-inch) (6th generation)
  [7] iPad Pro (12.9-inch) (6th generation)
  [8] iPad mini (6th generation)
  [9] iPad mini (6th generation)
  [10] iPhone 15
  [11] iPhone 15
  [12] iPhone 15 Plus
  [13] iPhone 15 Plus
  [14] iPhone 15 Pro
  [15] iPhone 15 Pro
  [16] iPhone 15 Pro Max
  [17] iPhone 15 Pro Max
  [18] iPhone SE (3rd generation)
  [19] iPhone SE (3rd generation)
  Enter an index for a simulator above.
Simulator: 15

エラー発生。

clang: error: linker command failed with exit code 1 (use -v to see invocation)

note: Run script build phase 'Build Rust Code' will be run during every build because the option to run the script phase "Based on dependency analysis" is unchecked. (in target 'ai_client_iOS' from project 'ai_client')
** BUILD FAILED **


The following build commands failed:
        Ld /Users/masatoyuna/Library/Developer/Xcode/DerivedData/ai_client-fwvrscpltheatpeipnofdrvehucn/Build/Products/debug-iphonesimulator/ai_client.app/ai_client normal (in target 'ai_client_iOS' from project 'ai_client')
(1 failure)
    Error command ["xcodebuild"] exited with code 65
error: script "tauri" exited with code 1

tauriのissueをsearchしてみると、同様のバグが発生している事例を見つけた。
結論としては、bun initのバグっぽく、initはbunではなくnpmなど別のランタイムで構築して、
buildをbunでやることで回避できるらしい。
https://github.com/tauri-apps/tauri/issues/8860#issuecomment-2081601544

ということで、tauri ios initだけ、npmで実行することで回避した。

$ npm run tauri ios init
 $ bun run tauri ios dev

$ bun run tauri ios dev

yunayuna

chatGPT clientのロジックを構築していきます。

1.xから2.0へのmigration箇所については、こちらにまとまっています。
https://v2.tauri.app/start/upgrade--migrate/from-tauri-1/#migrate-path-to-tauri-manager

バックエンドで作成済みのapiエンドポイントを、そのままコピー

https://github.com/generalworksinc/ai_client

migrationが必要だった場所

path_resolverは、シンプルにpathに変更

app.path_resolver().app_config_dir()
↓
app.path().app_config_dir()

https://v2.tauri.app/start/upgrade--migrate/from-tauri-1/#migrate-to-menu

Menu, MenuEvent, CustomMenuItem, Submenu, WindowMenuEvent, MenuItem and Builder::on_menu_event APIs removed.

tauri::SystemTrayMenu

tauri::menu::Menu

tauri::SystemTraySubmenu

tauri::menu::Submenu

tauri::SystemTrayMenuItem

tauri::menu::PredefinedMenuItem

CustomMenuItemは、記述を変更
https://v2.tauri.app/start/upgrade--migrate/from-tauri-1/#use-taurimenumenuitembuilder

tauri::CustomMenuItem

tauri::menu::MenuItemBuilder

tauri::Builder::on_menu_event

tauri::App::on_menu_eventtauri::AppHandle::on_menu_event

実際に置き換えたコード

before(ver1.x)
    let main_page = MenuItem::new("main".to_string(), "Main");
    let settings = CustomMenuItem::new("settings".to_string(), "Settings");
    let submenu = Submenu::new("Menu", Menu::new().add_item(main_page).add_item(settings));
    let menu = tauri::Menu::os_default(&context.package_info().name).add_submenu(submenu);

〜〜省略〜〜

        .menu(menu)
        .on_menu_event(|event| match event.menu_item_id() {
            "settings" => {
                event.window().emit("open_settings", "").unwrap();
            }
            "main" => {
                event.window().emit("open_main", "").unwrap();
            }
            _ => {}
        })

after(ver2.0)
        .setup(|app| {
            let main_page = MenuItemBuilder::with_id("main","Main").build(app)?;
            let settings = MenuItemBuilder::with_id("settings", "Settings").build(app)?;
            let submenu = SubmenuBuilder::with_id(app, "menu", "Menu").items(&[&main_page, &settings]).build()?;
            let menu = MenuBuilder::new(app).items(&[&submenu]).build()?;

            app.set_menu(menu)?;
            app.on_menu_event(move |app, event| {
                let window_list = app.webview_windows();
                let window_tuple = window_list.iter().next().unwrap();
                println!("event: {:#?}, window:{:?}", event.id(), window_tuple.0);
                let window = window_tuple.1;
                match event.id().0.as_str() {
                    "settings" => {
                        window.emit("open_settings", "").unwrap();
                    }
                    "main" => {
                        window.emit("open_main", "").unwrap();
                    }
                    _ => {}
                }
            });

            init_config(app).expect("config init error");
            Ok(())
        })```
yunayuna

frontendを、旧コードから新コードにコピー

rustコードと同様に、frontendもmigrationが必要。
今回のサンプルでは、一箇所の修正だけで済んだ。
@tauri-apps/api/tauri@tauri-apps/api/core

yunayuna

tauri.conf.jsonのコピー

要素の位置が多く変更されているので、公式を見ながら変更する。
https://v2.tauri.app/start/upgrade--migrate/from-tauri-1/#tauri-configuration

allowlistの変更について

allowlistは、ver2.0になってマルチウィンドウ対応など、細かい設定を行えるようにするために大きく変更されています。

アプリの権限を有効にするには、src-tauri/capabilities フォルダー内に機能ファイルを作成する必要があるため、いったんtauri.conf.jsonの変更のみを記述します。

tauri.conf.json(before)
{
  "build": {
    "beforeDevCommand": "bun dev",
    "beforeBuildCommand": "bun build",
    "devPath": "http://localhost:1420",
    "distDir": "../dist"
  },
  "package": {
    "productName": "ai_client",
    "version": "0.0.6"
  },
  "tauri": {
    "allowlist": {
      "all": false,
      "shell": {
        "all": false,
        "open": true
      }
    },
    "windows": [
      {
        "fullscreen": false,
        "resizable": true,
        "title": "ai_client",
        "width": 1200,
        "height": 1000
      }
    ],
    "security": {
      "csp": null
    },
    "updater": {
      "active": false
    },
    "bundle": {
      "active": true,
      "targets": "all",
      "identifier": "jp.co.generalworks.ai-client",
      "icon": [
        "icons/32x32.png",
        "icons/128x128.png",
        "icons/128x128@2x.png",
        "icons/icon.icns",
        "icons/icon.ico"
      ]
    }
  }
}
tauri.conf.json(after)
{
  "productName": "ai_client",
  "version": "0.1.0",
  "identifier": "jp.co.generalworks.ai-client",
  "build": {
    "beforeDevCommand": "bun run dev",
    "devUrl": "http://localhost:1420",
    "beforeBuildCommand": "bun run build",
    "frontendDist": "../dist"
  },
  "app": {"windows": [
      {
        "fullscreen": false,
        "resizable": true,
        "title": "ai_client",
        "width": 1200,
        "height": 1000
      }
    ],
    "security": {
      "csp": null
    }
  },
  "bundle": {
    "active": true,
    "targets": "all",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ]
  }
}

アプリ権限設定

src-tauri/capabilities に権限設定を作成します。
migrate CLI command を使うと、v1.xから自動的に権限ファイルを生成してくれるので、
今回はこれを使ってみました。

旧プロジェクト上で、migrationコマンドを実行

bun add -D  @tauri-apps/cli@next
bun run tauri migrate

ソースコードが生成された。これを新プロジェクトのsrc-tauriディレクトリにコピーする。

src-tauri/capabilities/migrated.json
{
  "identifier": "migrated",
  "description": "permissions that were migrated from v1",
  "local": true,
  "windows": [
    "main"
  ],
  "permissions": [
    "path:default",
    "event:default",
    "window:default",
    "app:default",
    "resources:default",
    "menu:default",
    "tray:default",
    "shell:allow-open",
    "shell:default"
  ]
}

これで、win, linux, macosのデスクトップ版は稼働確認できた。

yunayuna

iOSビルドの再確認(エラー発生)

 bun run tauri ios dev

エラーが出るようになってしまった。

    Error [tauri_cli_node] Failed to run `cargo build`: command ["cargo", "build", "--package", "ai_client", "--manifest-path", "/Users/masatoyuna/Project/ai_client_new/ai_client/src-tauri/Cargo.toml", "--target", "aarch64-apple-ios-sim", "--features", "tauri/rustls-tls", "--lib", "--no-default-features"] exited with code 101
Command PhaseScriptExecution failed with a nonzero exit code

** BUILD FAILED **


The following build commands failed:
        PhaseScriptExecution Build\ Rust\ Code /Users/masatoyuna/Library/Developer/Xcode/DerivedData/ai_client-fwvrscpltheatpeipnofdrvehucn/Build/Intermediates.noindex/ai_client.build/debug-iphonesimulator/ai_client_iOS.build/Script-C336E540F6E8980E617A63A7.sh (in target 'ai_client_iOS' from project 'ai_client')
(1 failure)
    Error command ["xcodebuild"] exited with code 65

似たようなissueが上がっており、
どうやらdesktopアプリで実装している何らかの設定が、mobileでエラーを引き起こしていると思われます。
https://github.com/tauri-apps/cargo-mobile2/issues/291

さらに調査を進めたところ、reqwestを含むビルドに何らかの問題がありそう。
エラーの発生するminimalコードリポジトリを作成し、issueを登録しました。
現時点でiOSのai_client実装は進めなくなったため、検証は中止してバグフィックスを待つことにします。
https://github.com/tauri-apps/tauri/issues/9749

追記

iOSのSecurity.framework bindingの rust-security-framework 内のバグに起因する事が判明。
https://github.com/kornelski/rust-security-framework/pull/204

tauri2.0(beta.20)でおそらく修正されるので、そのタイミングで確認することにします。