首页 / 资讯中心 / 文章详情

Macro desktop-cef 深度解析:用 Chromium(CEF)为 macOS 构建 Tauri 桌面端

Macro desktop-cef 深度解析:用 Chromium(CEF)为 macOS 构建 Tauri 桌面端 ★ FEATURED ARTICLE
后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载Macro 的 macOS 桌面应用并没有使用系统自带的 WKWebView而是通过 Chromium Embedded FrameworkCEF渲染以获得与 Web 端一致的浏览器内核行为。本文以仓库中的 desktop-cef/AGENTS.md 为主线结合该目录的源码、Cargo 清单、Tauri 配置与 Nix 打包表达式完整讲清这个“薄壳二进制”的职责边界、构建命令、签名与打包细节以及那些一旦踩错就会让 iOS/Android 一起重编的坑。一、它是什么一个选择 CEF 运行时的薄壳二进制desktop-cef的定位可以用一句话概括它是 macOS 专属的 Chromium 渲染壳业务逻辑一行都不在这里。目录apps/web/tauri/desktop-cef产物名app[[bin]] name app包名macro-desktop-cef依赖app { path ../src-tauri, features [cef] }即在共享的 Tauri 应用 crateapp_lib上开启cef特性运行时macOS 下引入tauri-runtime-cef并用tauri::Builder::default().runtime(tauri_runtime_cef::Cef::default())选择 Chromium入口代码只有十余行却包含了两个关键设计// apps/web/tauri/desktop-cef/src/main.rs #[cfg_attr(target_os macos, tauri_runtime_cef::cef_entry_point)] fn main() { #[cfg(target_os macos)] app_lib::run_with(tauri::Builder::default().runtime(tauri_runtime_cef::Cef::default())); #[cfg(not(target_os macos))] compile_error!(desktop-cef is the macOS build; other platforms build ../src-tauri); }要点解读cef_entry_point属性宏不是装饰。CEF 会重新拉起同一个可执行文件来充当 renderer、GPU 与 utility 子进程该宏让入口点先处理这些子进程并提前返回之后才进入正常的应用启动流程。源码中的注释也明确写出了这一点。compile_error!是有意为之。若在非 macOS 目标上编译会直接报错从而在编译期阻断“把 CEF 构建误用到 Linux/Windows”的可能。app_lib::run_with是唯一入口。业务实现全部位于 apps/web/tauri/src-tauri/src/lib.rsCEF 目录只负责“选运行时”。AGENTS.md中“thin binary over../src-tauriapp_lib::run_withthat selectstauri-runtime-cef”这句话正是对上述结构的精确描述它决定了后续所有“改哪里、不该改哪里”的判断。二、职责边界它拥有什么不拥有什么归属内容对应文件本目录拥有macOS Chromium 二进制与 Cargo workspaceCargo.toml、Cargo.lock、src/main.rs本目录拥有打包配置bundle configtauri.conf.json本目录拥有macOS 权限声明entitlements.plist不在本目录应用行为插件、导航、协议、更新src-tauri/src/lib.rs不在本目录iOS / Android / Linux / Windows 构建基于../Cargo.toml与 wry 的../src-tauri这个边界有一个容易忽视的后果tauri.conf.json在这里有两份用途。CLI 从本目录的配置进行打包而运行期配置仍来自../src-tauri含tauri.macos.conf.json。AGENTS.md因此要求三者保持对齐apps/web/tauri/desktop-cef/tauri.conf.jsonapps/web/tauri/src-tauri/tauri.conf.jsonapps/web/tauri/src-tauri/tauri.macos.conf.json对比可见CEF 版本在基础配置之上多做了三件 macOS 专属的事dragDropEnabled: false、bundle.targets: [app, dmg]、以及macOS.entitlements与macOS.files.Resources/Assets.car的声明。窗口参数titleBarStyle: Overlay、trafficLightPosition {x:16,y:20}、hiddenTitle: true、backgroundThrottling: disabled与共享配置一致后者对长时间运行的协作/通话场景很重要禁用后台节流避免窗口失焦后定时器被降频。三、为什么必须是独立 workspace这是整份文档里信息密度最高的一条 GotchaA[patch]applies to every target in its workspace, so moving the CEF Tauri patches into../Cargo.tomlwould rebuild iOS and Android against the CEF branch.Cargo 的[patch]是workspace 级别的依赖重定向不区分 target。desktop-cef/Cargo.toml顶部声明了自己的[workspace]空表即“独立 workspace 根”并在其中写入[workspace] [dependencies] app { path ../src-tauri, features [cef] } tauri { version 2.11, features [devtools, protocol-asset] } [target.cfg(target_os macos).dependencies] tauri-runtime-cef { git https://github.com/macro-inc/tauri, rev 285fb6b1, features [devtools] } [patch.crates-io] tauri { git https://github.com/macro-inc/tauri, rev 285fb6b1 } tauri-build { git https://github.com/macro-inc/tauri, rev 285fb6b1 } tauri-codegen { git https://github.com/macro-inc/tauri, rev 285fb6b1 } tauri-macros { git https://github.com/macro-inc/tauri, rev 285fb6b1 } tauri-runtime { git https://github.com/macro-inc/tauri, rev 285fb6b1 } tauri-utils { git https://github.com/macro-inc/tauri, rev 285fb6b1 } dpi { git https://github.com/tauri-apps/winit-gtk4, rev 3d7c7cd19d3c752f75762ceea535444e63bdf8de }由此可以推出三条硬性约束均可从配置直接读出属实现事实../src-tauri必须以版本号引用tauri而不能写成 git 依赖。因为只有 crates.io 来源的依赖才能被[patch.crates-io]重定向如果共享清单里写成 git dependency本 workspace 就再也无法把它“掰”到 CEF 分支。dpi也必须一起 patch。CEF 运行时构建在 winit-gtk4 之上若dpi与 Tauri 使用的不是同一份类型不匹配会在编译期炸掉。tauri-runtime-cef只挂在cfg(target_os macos)下所以这个 workspace 天然只服务 macOS不需要额外的特性开关矩阵。Cargo.toml末尾还有一段容易漏读的 profile 覆盖[profile.dev.package.turso_core] opt-level 3 debug-assertions false注释写明“Match../Cargo.toml: run the GraphQL caches SQL VM like production”——本地 CEF 调试时前端 GraphQL 缓存使用的 SQL 虚拟机仍按生产优化级别编译否则 dev 下的查询行为与发布版不一致问题会被掩盖。四、cef特性到底改了什么AGENTS.md强调cef特性不增加任何依赖只做两件事。在 src-tauri/Cargo.toml 中它确实只是一个空特性# Set only by the macOS Chromium build in ../desktop-cef; adds no dependencies. cef []4.1 运行时类型别名切换lib.rs 中#[cfg(not(feature cef))] pub type AppRuntime tauri::Wry; #[cfg(feature cef)] pub type AppRuntime tauri::DynRuntime; pub fn run_with(builder: tauri::BuilderAppRuntime) { /* ... */ }wry 构建下是静态分派的tauri::WryCEF 构建下因为运行时在启动时才确定必须退化为tauri::DynRuntime。这也解释了run_with的存在意义其他平台调用run()内部run_with(tauri::Builder::default())CEF 则传入已经选好运行时的 builder。4.2 页面 origin 变成http://tauri.localhostlib.rs 对不同平台给出不同 origin// macOS/iOS/Linux: tauri://localhost // Windows/Android: https://tauri.localhost (or http://) // Chromium (the cef feature): http://tauri.localhost let window_origin if cfg!(feature cef) { http://tauri.localhost } else if cfg!(any(target_os windows, target_os android)) { https://tauri.localhost } else { tauri://localhost };原因是 Chromium 把自定义协议实现为普通 http 服务而不是 WKWebView 的注册 scheme。这个 origin 随后被传入register_asynchronous_uri_scheme_protocol(tauri, ...)再由tauri_protocol::get(app, window_origin, resolver)解析资源。同时tauri_protocol.rs 的rewrite_uri需要为 CEF 补上两条前缀规则把/app/前缀剥掉#[cfg(feature cef)] http://tauri.localhost/app/, #[cfg(feature cef)] http://tauri.localhost/app,且test.rs中对应存在http://tauri.localhost/app/login→http://tauri.localhost/login的断言。这说明该分支不只是“多两个字符串”而是被测试覆盖的真实路径重写逻辑——当你改动 origin 时必须同时改rewrite_uri与测试否则 SPA 的深链接会 404。4.3 回调签名差异最容易编译失败的一处AGENTS.md明确提示CEF fork 的on_web_content_process_terminate回调接收(webview, termination details)而普通 fork 只接收webview。共享代码用条件编译同时满足两者#[cfg(feature cef)] builder builder.on_web_content_process_terminate(move |webview, _details| on_terminate(webview)); #[cfg(not(feature cef))] builder builder.on_web_content_process_terminate(on_terminate);这段逻辑还承担了真实的容错职责WebContent 进程终止时记录诊断事件并webview.reload()。也就是说改动“共享回调注册”必须在两个 workspace 都编译验证否则 iOS 侧会静默漏掉一次编译检查。五、构建前的前置条件AGENTS.md的 Gotchas 给出了明确的工具链要求下面逐条对应的原因都可以从仓库中核实要求说明证据Rust1.95.0仓库根固定为 1.94CEF 构建必须显式指定 1.95.0RUSTUP_TOOLCHAIN1.95.0rust-toolchain.tomlnix/tauri-desktop.nix 单独 pin1.95.0cmakeninjaCEF 的 C 侧需要构建两者均在 Nix 表达式的nativeBuildInputs中首次构建约 1 GB 下载CEF 预编译产物解压到~/Library/Caches/tauri-cef文档明确给出Nix 构建里对应CEF_PATH$TMPDIR/tauri-cef自定义cargo-tauri上游 CLI 不携带 Chromium framework 与 helper appsAGENTS.md原文Nix 中cefTauriCli从tauri-cef输入构建5.1 最快的类型检查cd apps/web/tauri/desktop-cef RUSTUP_TOOLCHAIN1.95.0 cargo check --target aarch64-apple-darwin注意两个隐含前提一是必须在本目录执行否则[patch.crates-io]不生效二是该命令只能在具备 Apple SDK 的 macOS 上运行交叉编译到aarch64-apple-darwin需要对应链接器在 Linux 上做不了等价验证。六、用 Nix 构建从无签名 app 到签名 DMG仓库把 macOS CEF 构建的完整流程固化在 nix/tauri-desktop.nix 中并对 macOS aarch64 暴露两个 package见文件末尾的lib.optionalAttrs isAarch64Darwin分支Package产物是否签名.#tauri-desktop-cef-app无签名Macro.app否.#tauri-desktop-dmg签名、公证、发布的 DMG 可分发 app是本地构建无签名 app# 在仓库根目录macOS 上 nix build --option sandbox false .#tauri-desktop-cef-app发布用的签名 DMGnix build --impure --option sandbox false .#tauri-desktop-dmg--impure不是可选项因为签名身份来自环境变量。README/文档中的这一要求在打包表达式里有强校验nix/tauri-desktop.nixif [ -z $APPLE_SIGNING_IDENTITY ]; then echo APPLE_SIGNING_IDENTITY must be set when building the signed macOS DMG; use nix build --impure. 2 exit 1 fi if [ $APPLE_SIGNING_IDENTITY ! - ] [ -z $MACOS_PROVISIONING_PROFILE ]; then echo MACOS_PROVISIONING_PROFILE must name the Developer ID provisioning profile ... 2 exit 1 fi即Developer ID 签名必须同时提供MACOS_PROVISIONING_PROFILE用于通信通知 entitlement仅 ad-hoc 签名-才可省略。6.1 CEF 构建的关键配置注入Nix 并没有手改tauri.conf.json而是通过TAURI_CONFIG环境变量以 JSON 覆盖的方式注入构建期配置nix/tauri-desktop.nixtauriDesktopCefConfig builtins.toJSON { version appVersion; plugins desktopPlugins; build { frontendDist ${frontend}; beforeBuildCommand ; # 前端已在 Nix 派生中构建完成 }; bundle { active true; targets [ app ]; externalBin [ binaries/macrod ]; cef.embed true; # 把 CEF framework 嵌入 bundle }; };其中cef.embed true是 CEF 打包的核心开关externalBin声明了 sidecarmacrod本地 agent 守护进程二进制在preBuild阶段按目标三元组命名安装install -Dm0755 ${macrodBinary}/bin/macrod \ apps/web/tauri/desktop-cef/binaries/macrod-${rustHostTriple}构建命令则带上--features tauri/custom-protocolcargo tauri build --bundles app --features tauri/custom-protocol --config $TAURI_CONFIG--features tauri/custom-protocol很关键CEF 下的tauri.localhost资源服务依赖自定义协议实现。安装阶段还有一道硬断言确保没有漏掉 Chromiumtest -d $appPath/Contents/Frameworks/Chromium Embedded Framework.framework6.2 手工打包不用 Nix 的路径AGENTS.md给出了可复现的本地打包步骤在nix develop内构建前端在apps/web下执行just build-tauriapps/web/justfile它会先跑build-wasm再以MODEproduction构建并把产物打到dist。回到apps/web/tauri/desktop-cef使用从macro-inc/tauri的macro-cef分支构建出的 Tauri CLIRUSTUP_TOOLCHAIN1.95.0 cargo-tauri build --bundles app \ -c {build:{beforeBuildCommand:}}beforeBuildCommand被显式置空很重要tauri.conf.json里它默认指向just build-tauri而此处前端已构建完毕再跑一次既浪费时间也可能覆盖配置。七、entitlements 与摄像头/麦克风为什么少一项就采集不到CEF 在 Hardened Runtime 下运行 Chromium必须有 JIT 相关权限。仓库的 entitlements.plist 只包含必要项Entitlement作用com.apple.security.cs.allow-jit允许 Chromium V8 的 JITcom.apple.security.cs.allow-unsigned-executable-memory允许生成可执行内存页com.apple.security.cs.disable-library-validation允许加载 CEF framework 与 helpercom.apple.security.device.camera视频通话com.apple.security.device.audio-input通话与听写dictation而权限声明只解决“沙盒/加固运行时”的一半问题另一半是系统弹窗文案。它来自 apps/web/tauri/src-tauri/icons/macos/Info.plistkeyNSCameraUsageDescription/key stringMacro uses the camera for video calls./string keyNSMicrophoneUsageDescription/key stringMacro uses the microphone for calls and dictation./string keyNSBluetoothAlwaysUsageDescription/key stringMacro uses Bluetooth to securely authenticate a WhatsApp connection with your nearby phone./stringAGENTS.md的结论因此很直接entitlements 与 usage description 缺任何一个macOS 都会拒绝采集。这解释了为什么该 plist 同时被desktop-cef/tauri.conf.jsonbundle.macOS.infoPlist与 Nix 打包引用——两条打包路径都不能漏。7.1 签名顺序由内向外且不给 helper 加受限权限Nix 的sign_darwin_app展示了一个正确但复杂的次序nix/tauri-desktop.nix先逐个签Contents/Frameworks下的 Mach-O 文件再自内向外签.framework与.appCEF 自带一个 framework 和五个 helper apphelper app 使用带 entitlements 的参数单独签Contents/MacOS下的 helper 可执行文件即macrodsidecar因为公证要求每个可执行体都有签名最后用--entitlements签主 bundle且不使用--deep。注释给出了原因嵌套代码已经签过--deep会把 app 的受限 entitlements 传染给 CEF helper 和 sidecar导致签名与公证失败。这是“照抄网上codesign --deep教程”最容易翻车的点。另外bundle_nix_dylibs会递归把/nix/store/*.dylib复制进Contents/Frameworks并用install_name_tool改写为loader_path相对引用最后断言“不再残留绝对 Nix dylib 引用”否则 app 在用户机器上必然启动失败。八、发布链路与运行时更新签好名的 DMG 并不是终点。tooling/xtask/crates/xtask_workflows/src/workflows/scripts/build_dmg.sh 与 .github/workflows/build_desktop_on_tag.yml 展示了发布流程的实际形状nix path-info --derivation --impure \ .#packages.aarch64-darwin.tauri-desktop-apple-linker-smoke \ .#packages.aarch64-darwin.tauri-desktop-dmg \ $RUNNER_TEMP/desktop-cache-roots nix build --impure --option sandbox false --print-build-logs \ .#packages.aarch64-darwin.tauri-desktop-apple-linker-smoke nix build --impure --option sandbox false --print-build-logs \ .#packages.aarch64-darwin.tauri-desktop-dmg几个值得注意的细节VITE_POSTHOG_API_KEY必须存在。脚本用: ${VITE_POSTHOG_API_KEY:?...}强制要求因为 Vite 在构建期内联该变量缺失会导致出厂应用无法初始化 PostHog所有 feature flag 都被读成关闭。nix/tauri-desktop.nix 里甚至在构建后反向 grepdist确认 key 真的被内联否则直接失败。Apple 链接器冒烟测试。Nix 自带链接器在超大桌面二进制上会崩所以CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER指向一个走xcrun的 shimtauri-desktop-apple-linker-smoke用来验证 shim 真的被调用而不是被悄悄替换回 Nix 的 cctools。DMG 尺寸预计算。hdiutil的自动 APFS 扩容在拷贝大 app 时可能空间不足因此先按 apparent size 计算再加 25% 与 64 MiB 余量。桌面更新元数据来自 apps/web/tauri/desktop-release.json当前version: 2.5.0、bundleBuild: 1、enabled: false由 Nix 读取后注入plugins.desktop-update与updater配置。九、Cookie 与登录态不要把凭据放在缓存目录AGENTS.md要求“CEF 的 HTTP/WebSocket fork 版本与 Nix source hash 保持与共享 native workspace 对齐”原因与登录态有关。共享 README 的 Native authentication cookies 说明原生 HTTP 插件的 cookie jar 位于$APPLOCALDATA/.cookies在 macOS/iOS 上解析为Library/Application Support/bundle-identifier/.cookies不能放$APPCACHE因为 iOS 在存储压力下可能清理该目录同一个原生 jar 同时服务 HTTP 与 WebSocket与 WebView 自身的浏览器存储相互独立迁移不会自动发生首次使用新路径需要重新登录这属于 native 改动OTA 前端包无法完成。在代码中可对应到 lib.rs 的init_with_cookie_store_dir($APPLOCALDATA)。CEF 与 wry 共享这段逻辑因此任何“cookie 丢了”的问题优先排查此处。十、检查清单与常见故障对照现象高概率原因排查位置compile_error: desktop-cef is the macOS build在非 macOS 目标构建本目录src/main.rsiOS/Android 突然开始编译 CEF 分支把[patch.crates-io]挪到了../Cargo.tomldesktop-cef/Cargo.tomlcargo check找不到 CEF runtime 或类型不匹配未在desktop-cef目录执行 /dpi未一起 patch同上on_web_content_process_terminate参数数量不匹配改了共享回调但只在一个 workspace 编译lib.rs打包缺少Chromium Embedded Framework.framework使用了上游cargo-taurinix/tauri-desktop.nix/app/...深链接 404origin 改了但rewrite_uri未同步tauri_protocol.rs通话/听写拿不到设备缺 entitlements 或缺 usage descriptionentitlements.plist、icons/macos/Info.plist公证失败、签名报嵌套代码非法使用了codesign --deep或签名顺序错误nix/tauri-desktop.nix首次构建卡在下载约 1 GB正常行为缓存在~/Library/Caches/tauri-cef文档 Gotchas提交前的最小验证序列在apps/web/tauri/desktop-cef执行RUSTUP_TOOLCHAIN1.95.0 cargo check --target aarch64-apple-darwin在共享 workspaceapps/web/tauri/src-tauri按原平台编译一次确认共享代码改动没有只在 CEF 侧成立若改了tauri.conf.json与../src-tauri/tauri.conf.json、../src-tauri/tauri.macos.conf.json做 diff 对齐若改了依赖版本同步更新 nix/tauri-desktop.nix 中的outputHashes与 flake.nix 里的tauri-cefrev——两者不同步会直接导致 Nix 取源失败。十一、小结desktop-cef 的价值不在于“又写了一个 main”而在于把CEF 运行时的全部特殊性收敛进一个独立 workspace用空[workspace]隔离[patch]用cef空特性把差异收敛到运行时别名、origin 与回调签名三处用自定义 Tauri CLI 与 Nix 表达式补齐上游缺失的 Chromium framework 与 helper apps再用一份带 JIT/采集权限的 entitlements 加正确的由内向外签名顺序把它变成可通过公证的 DMG。理解了这条链路你既能安全地在 macOS 上迭代 Chromium 桌面端也不会在改一次[patch]时把 iOS、Android、Linux 与 Windows 的构建一起拖下水。赞分享后端前端协同办公即时通讯企业应用CRMAI Agent人工智能【免费下载链接】macroMacro is a unified workspace for teams: email, chat, docs, tasks, agents, calls, and CRM — -linked together with shared AI memory.项目地址https://gitcode.com/GitHub_Trending/macro3/macro点击查看免费下载相关推荐Chromium嵌入式框架CEF构建现代化桌面应用的完整指南Chromium嵌入式框架CEF构建现代化桌面应用的完整指南 Chromium Embedded Framework CEF 是一款功能强大的开源框架专门用桌面应用跨平台OpenHuman 桌面端源码构建与安装完全指南Rust 工具链、vendored Tauri/CEF 与 macOS/Windows/Linux 三平台部署OpenHuman 桌面端源码构建与安装完全指南Rust 工具链、vendored Tauri/CEF 与 macOS/Windows/Linux 三平台部署人工智能AI Agent多智能体Agent 编排工具调用桌面应用CEF核心架构深度解析如何将Chromium浏览器嵌入任何应用CEF核心架构深度解析如何将Chromium浏览器嵌入任何应用 Chromium Embedded Framework CEF 是一个强大的开源框架允许开发桌面应用跨平台上一篇Perplexity 开源 27B 决策模型后「官方评测综合分略超 Jev」到底超在哪下一篇VIGA记忆管理机制揭秘滑动窗口如何让上下文成本从O(N²)降到O(N·k)创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站