返回博客

Tauri RunEvent::Reopen 是 macOS 专属,移植 Windows 时编译必爆

我在把一个只跑过 macOS 的 Tauri 2 桌面应用接入 Windows 构建流水线时,CI 立刻红了。本地 cargo check 全程绿灯,推上去就在 windows-latest 的 runner 上炸。编译器给出的错误让人愣了一下:枚举变体根本不存在。

现象

GitHub Actions 的 windows-latest job 在 cargo build 阶段报错:

error[E0599]: no variant named `Reopen` found for enum `tauri::RunEvent`
   --> src/lib.rs:259:37
    |
259 |             if let tauri::RunEvent::Reopen { .. } = event {
    |                                     ^^^^^^ variant not found in `tauri::RunEvent`

而在 Mac 上,cargo checkcargo build 都毫无异常。这段代码一直跑着,用于监听用户点击 Dock 图标时把隐藏到托盘的窗口重新显示出来:

.run(|app_handle, event| {
    if let tauri::RunEvent::Reopen { .. } = event {
        if let Some(w) = app_handle.get_webview_window("main") {
            let _ = w.show();
            let _ = w.set_focus();
        }
    }
})

根因

tauri::RunEvent::Reopen 在 Tauri 源码里标注了 #[cfg(target_os = "macos")]。Windows 没有 Dock,这个变体在 Windows 平台的枚举定义里根本不存在——不是运行时找不到,而是编译期字面上就没有这个 variant。

同理还有一批 macOS 专属的菜单 API:SubmenuBuilder.services().hide_others().show_all() 都是 macOS 菜单栏约定,Windows 编译同样会爆 unused import 或找不到方法。

坑:Mac 上 cargo check 通过 ≠ Windows 也能编译

cargo check / cargo build 默认只编译当前宿主平台的目标。所有被 #[cfg(target_os = "macos")] 保护的 Tauri API——包括 RunEvent::Reopen、Dock 相关菜单项——在 Mac 上永远是合法代码。错误只在交叉编译或 Windows CI 时才浮出水面。如果你的项目从来没有跑过 Windows 构建,这类炸弹可以沉睡很久。

正解

#[cfg(target_os = "macos")]Reopen 的 match arm 整体包起来。同时把闭包参数改成带下划线前缀,避免 non-mac 平台触发 unused-variable 警告:

.run(|_app_handle, _event| {
    // RunEvent::Reopen 是 macOS 专属变体,Windows 无 Dock,该变体在 win 上不存在
    #[cfg(target_os = "macos")]
    if let tauri::RunEvent::Reopen { .. } = _event {
        if let Some(w) = _app_handle.get_webview_window("main") {
            let _ = w.show();
            let _ = w.set_focus();
        }
    }
});

对应的 macOS 专属菜单栏,同样把构建块和 use 声明一起圈起来:

use tauri::menu::{Menu, MenuItem};          // 跨平台公用(托盘菜单)
#[cfg(target_os = "macos")]
use tauri::menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder};
#[cfg(target_os = "macos")]
use tauri::Emitter;
 
// 在 .setup(|app| { … }) 内部:
#[cfg(target_os = "macos")]
{
    // 含 .services() / .hide_others() / .show_all() 的菜单栏构建
    app.set_menu(menu_bar)?;
    app.on_menu_event(/* … */);
}

注意:#[cfg] 可以直接放在 if let 语句之前(语句级属性),不必额外套一层 {} 块。

正解:#[cfg(target_os = 「macos」)] 放在 if let 语句上即可

不需要把整个 run 闭包拆分,只需把 Reopen 的那条 match arm 单独圈住。闭包参数改为 _app_handle / _event,保证 Windows 构建不报 unused warning。Windows CI job 能通过编译,Mac 上行为不变。

排查思路

如果你遇到同类问题,快速排查路径:

  1. 搜索 RunEvent:: 的所有用法,查 Tauri 文档 确认哪些变体有 cfg 门控。
  2. 搜索 tauri::menu::.services() / .hide_others() / .show_all(),这三个方法是 macOS 系统菜单约定,Windows 无对应实现。
  3. 在 Mac 上本地交叉编译验证:cargo build --target x86_64-pc-windows-msvc(需提前安装 MSVC toolchain),或者直接在 CI 里尽早开一个 windows-latest job——让 Windows CI 成为 Windows 路径的 source of truth,而不是等到发布前才发现。

一句话外卖

macOS-only Tauri app 移植跨平台时,本地 cargo check 是哑的——早点在 CI 立一个 windows-latest job,让编译器帮你找出所有 #[cfg(target_os = "macos")] 漏网的调用点。