diff --git a/cef-helper/Cargo.toml b/cef-helper/Cargo.toml new file mode 100644 index 000000000..d7d7b9c42 --- /dev/null +++ b/cef-helper/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tauri-cef-helper" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0 OR MIT" +publish = false + +[dependencies] +cef = { version = "142.2.1", default-features = false } + +[features] +default = ["sandbox"] +# Mirrors `tauri-runtime-cef` default feature to enable CEF sandbox support on macOS. +sandbox = ["cef/sandbox"] + +# Ensure this crate is NOT treated as part of the repo workspace. +[workspace] + + diff --git a/cef-helper/src/main.rs b/cef-helper/src/main.rs new file mode 100644 index 000000000..89002c24c --- /dev/null +++ b/cef-helper/src/main.rs @@ -0,0 +1,27 @@ +use cef::{args::Args, *}; + +fn main() { + let args = Args::new(); + + #[cfg(all(target_os = "macos", feature = "sandbox"))] + let _sandbox = { + let mut sandbox = cef::sandbox::Sandbox::new(); + sandbox.initialize(args.as_main_args()); + sandbox + }; + + #[cfg(target_os = "macos")] + let _loader = { + let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true); + assert!(loader.load()); + loader + }; + + execute_process( + Some(args.as_main_args()), + None::<&mut App>, + std::ptr::null_mut(), + ); +} + + diff --git a/crates/tauri-bundler/build.rs b/crates/tauri-bundler/build.rs new file mode 100644 index 000000000..257efb46b --- /dev/null +++ b/crates/tauri-bundler/build.rs @@ -0,0 +1,109 @@ +// Copyright 2019-2025 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + env, fs, + path::{Path, PathBuf}, + process::Command, +}; + +fn main() { + let target = env::var("TARGET").unwrap_or_default(); + let host = env::var("HOST").unwrap_or_default(); + + // Only build/embed the CEF helper when compiling `tauri-bundler` for macOS. + if !target.contains("apple-darwin") { + return; + } + + // We need `lipo` and a functioning macOS toolchain to produce a universal Mach-O. + if !host.contains("apple-darwin") { + panic!( + "Building tauri-bundler for macOS requires a macOS host to build/embed the CEF helper binary" + ); + } + + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); + let bundler_manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + + let helper_root = bundler_manifest_dir + .parent() // crates/ + .and_then(|p| p.parent()) // repo root + .map(|p| p.join("cef-helper")) + .expect("failed to compute cef-helper path"); + + let helper_manifest = helper_root.join("Cargo.toml"); + let helper_main = helper_root.join("src").join("main.rs"); + + // Rebuild if the helper crate changes. + println!("cargo:rerun-if-changed={}", helper_manifest.display()); + println!("cargo:rerun-if-changed={}", helper_main.display()); + + // Copy the helper crate sources into OUT_DIR so any generated files (Cargo.lock, target dir) + // stay out of the repo checkout. + let helper_src_dir = out_dir.join("cef-helper-src"); + let helper_src_manifest = helper_src_dir.join("Cargo.toml"); + let helper_src_main = helper_src_dir.join("src").join("main.rs"); + fs::create_dir_all(helper_src_main.parent().unwrap()) + .expect("failed to create cef-helper-src directory"); + fs::copy(&helper_manifest, &helper_src_manifest).expect("failed to copy cef-helper Cargo.toml"); + fs::copy(&helper_main, &helper_src_main).expect("failed to copy cef-helper main.rs"); + + let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".into()); + + let helper_target_dir = out_dir.join("cef-helper-target"); + let aarch64 = build_helper( + &cargo, + &helper_src_manifest, + &helper_target_dir, + "aarch64-apple-darwin", + "tauri-cef-helper", + ); + let x86_64 = build_helper( + &cargo, + &helper_src_manifest, + &helper_target_dir, + "x86_64-apple-darwin", + "tauri-cef-helper", + ); + + // Generate a small rust shim that exposes the embedded helper bytes. + let shim_path = out_dir.join("cef_helpers.rs"); + let shim = format!( + "pub const CEF_HELPER_AARCH64: &[u8] = include_bytes!(r#\"{}\"#);\n\ +pub const CEF_HELPER_X86_64: &[u8] = include_bytes!(r#\"{}\"#);\n", + aarch64.display(), + x86_64.display() + ); + fs::write(&shim_path, shim).expect("failed to write cef_helpers.rs"); +} + +fn build_helper( + cargo: &str, + manifest_path: &Path, + target_dir: &Path, + target: &str, + bin_name: &str, +) -> PathBuf { + let mut cmd = Command::new(cargo); + cmd + .arg("build") + .arg("--release") + .arg("--manifest-path") + .arg(manifest_path) + .arg("--bin") + .arg(bin_name) + .arg("--target") + .arg(target) + .env("CARGO_TARGET_DIR", target_dir); + + let status = cmd + .status() + .expect("failed to spawn cargo build for CEF helper"); + if !status.success() { + panic!("failed to build CEF helper for target {target}"); + } + + target_dir.join(target).join("release").join(bin_name) +} diff --git a/crates/tauri-bundler/src/bundle/macos/app.rs b/crates/tauri-bundler/src/bundle/macos/app.rs index a131da10b..303ea52ad 100644 --- a/crates/tauri-bundler/src/bundle/macos/app.rs +++ b/crates/tauri-bundler/src/bundle/macos/app.rs @@ -52,6 +52,23 @@ const NESTED_CODE_FOLDER: [&str; 6] = [ const CEF_FRAMEWORK: &str = "Chromium Embedded Framework.framework"; +mod embedded_cef_helper { + // Generated by `crates/tauri-bundler/build.rs` when compiling on macOS. + include!(concat!(env!("OUT_DIR"), "/cef_helpers.rs")); +} + +fn lipo_create_universal(output: &Path, aarch64: &Path, x86_64: &Path) -> crate::Result<()> { + Command::new("lipo") + .arg("-create") + .arg("-output") + .arg(output) + .arg(aarch64) + .arg(x86_64) + .output_ok() + .with_context(|| "failed to create universal CEF helper binary using lipo")?; + Ok(()) +} + /// Bundles the project. /// Returns a vector of PathBuf that shows where the .app was created. pub fn bundle_project(settings: &Settings) -> crate::Result> { @@ -650,6 +667,8 @@ fn create_cef_helpers( frameworks_dir.to_path_buf(), )?; + let arch = settings.binary_arch(); + // Create helper .app bundles let helpers = vec![ format!("{} Helper (GPU)", exec_name), @@ -659,7 +678,33 @@ fn create_cef_helpers( format!("{} Helper", exec_name), ]; - let main_binary_path = bundle_directory.join("MacOS").join(exec_name); + // If building an universal app bundle, create a universal helper binary once (outside the loop), + // then copy it into each helper .app. + let universal_helper: Option<(tempfile::TempDir, PathBuf)> = + if arch == crate::bundle::settings::Arch::Universal { + let tmp = tempfile::tempdir().map_err(|e| { + crate::Error::GenericError(format!( + "failed to create temp dir for CEF helper lipo: {e}" + )) + })?; + let aarch64 = tmp.path().join("tauri-cef-helper.aarch64"); + let x86_64 = tmp.path().join("tauri-cef-helper.x86_64"); + let universal = tmp.path().join("tauri-cef-helper.universal"); + + fs::write(&aarch64, embedded_cef_helper::CEF_HELPER_AARCH64).fs_context( + "failed to write embedded CEF helper (aarch64)", + aarch64.clone(), + )?; + fs::write(&x86_64, embedded_cef_helper::CEF_HELPER_X86_64).fs_context( + "failed to write embedded CEF helper (x86_64)", + x86_64.clone(), + )?; + + lipo_create_universal(&universal, &aarch64, &x86_64)?; + Some((tmp, universal)) + } else { + None + }; for helper_name in helpers { let helper_app = frameworks_dir.join(format!("{helper_name}.app")); @@ -709,10 +754,31 @@ fn create_cef_helpers( let helper_exec = helper_macos.join(&helper_name); - std::fs::copy(&main_binary_path, &helper_exec).fs_context( - "failed to copy main binary to CEF helper", - helper_exec.clone(), - )?; + match arch { + crate::bundle::settings::Arch::AArch64 => { + fs::write(&helper_exec, embedded_cef_helper::CEF_HELPER_AARCH64).fs_context( + "failed to write embedded CEF helper executable", + helper_exec.clone(), + )?; + } + crate::bundle::settings::Arch::X86_64 => { + fs::write(&helper_exec, embedded_cef_helper::CEF_HELPER_X86_64).fs_context( + "failed to write embedded CEF helper executable", + helper_exec.clone(), + )?; + } + crate::bundle::settings::Arch::Universal => { + let (_temp_dir, universal_path) = universal_helper + .as_ref() + .expect("universal helper binary was not generated"); + fs_utils::copy_file(universal_path, &helper_exec)?; + } + other => { + return Err(GenericError(format!( + "CEF helper embedding is only supported for aarch64, x86_64, and universal on macOS (got {other:?})" + ))); + } + } #[cfg(unix)] {