use separate helper for macOS to optimize bundle size

This commit is contained in:
Lucas Nogueira
2025-12-18 11:54:39 -03:00
parent 9d143bdac2
commit 46ff50027f
4 changed files with 226 additions and 5 deletions

19
cef-helper/Cargo.toml Normal file
View File

@@ -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]

27
cef-helper/src/main.rs Normal file
View File

@@ -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(),
);
}

View File

@@ -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)
}

View File

@@ -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<Vec<PathBuf>> {
@@ -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)]
{