// Copyright 2016-2019 Cargo-Bundle developers // Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use super::{app, icon::create_icns_file}; use crate::{ PackageType, Settings, bundle::{Bundle, settings::Arch}, error::{Context, ErrorExt}, utils::CommandExt, }; use std::{ env, fs::{self, write}, path::PathBuf, process::{Command, Stdio}, }; pub struct Bundled { pub dmg: Vec, pub app: Vec, } /// Bundles the project. /// Returns a vector of PathBuf that shows where the DMG was created. pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result { // generate the .app bundle if needed let app_bundle_paths = if !bundles .iter() .any(|bundle| bundle.package_type == PackageType::MacOsBundle) { app::bundle_project(settings)? } else { Vec::new() }; // get the target path let output_path = settings.project_out_directory().join("bundle/dmg"); let package_base_name = format!( "{}_{}_{}", settings.product_name(), settings.version_string(), match settings.binary_arch() { Arch::X86_64 => "x64", Arch::AArch64 => "aarch64", Arch::Universal => "universal", target => { return Err(crate::Error::ArchError(format!( "Unsupported architecture: {target:?}" ))); } } ); let dmg_name = format!("{package_base_name}.dmg"); let dmg_path = output_path.join(&dmg_name); let product_name = settings.product_name(); let bundle_file_name = format!("{product_name}.app"); let bundle_dir = settings.project_out_directory().join("bundle/macos"); let support_directory_path = output_path .parent() .unwrap() .join("share/create-dmg/support"); for path in &[&support_directory_path, &output_path] { if path.exists() { fs::remove_dir_all(path).fs_context("failed to remove old dmg", path.to_path_buf())?; } fs::create_dir_all(path).fs_context("failed to create output directory", path.to_path_buf())?; } // create paths for script let bundle_script_path = output_path.join("bundle_dmg.sh"); log::info!(action = "Bundling"; "{} ({})", dmg_name, dmg_path.display()); // write the scripts write(&bundle_script_path, include_str!("./bundle_dmg"))?; write( support_directory_path.join("template.applescript"), include_str!("./template.applescript"), )?; write( support_directory_path.join("eula-resources-template.xml"), include_str!("./eula-resources-template.xml"), )?; // chmod script for execution Command::new("chmod") .arg("777") .arg(&bundle_script_path) .current_dir(&output_path) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .output() .expect("Failed to chmod script"); let dmg_settings = settings.dmg(); let app_position = &dmg_settings.app_position; let application_folder_position = &dmg_settings.application_folder_position; let window_size = &dmg_settings.window_size; let app_position_x = app_position.x.to_string(); let app_position_y = app_position.y.to_string(); let application_folder_position_x = application_folder_position.x.to_string(); let application_folder_position_y = application_folder_position.y.to_string(); let window_size_width = window_size.width.to_string(); let window_size_height = window_size.height.to_string(); let mut bundle_dmg_cmd = Command::new(&bundle_script_path); bundle_dmg_cmd.args([ "--volname", product_name, "--icon", &bundle_file_name, &app_position_x, &app_position_y, "--app-drop-link", &application_folder_position_x, &application_folder_position_y, "--window-size", &window_size_width, &window_size_height, "--hide-extension", &bundle_file_name, ]); let window_position = dmg_settings .window_position .as_ref() .map(|position| (position.x.to_string(), position.y.to_string())); if let Some(window_position) = &window_position { bundle_dmg_cmd.arg("--window-pos"); bundle_dmg_cmd.arg(&window_position.0); bundle_dmg_cmd.arg(&window_position.1); } let background_path = if let Some(background_path) = &dmg_settings.background { Some(env::current_dir()?.join(background_path)) } else { None }; if let Some(background_path) = &background_path { bundle_dmg_cmd.arg("--background"); bundle_dmg_cmd.arg(background_path); } let icns_icon_path = create_icns_file(&output_path, settings)?; if let Some(icon) = &icns_icon_path { bundle_dmg_cmd.arg("--volicon"); bundle_dmg_cmd.arg(icon); } let license_path = if let Some(license_path) = settings.license_file() { Some(env::current_dir()?.join(license_path)) } else { None }; if let Some(license_path) = &license_path { bundle_dmg_cmd.arg("--eula"); bundle_dmg_cmd.arg(license_path); } // Issue #592 - Building MacOS dmg files on CI // https://github.com/tauri-apps/tauri/issues/592 if env::var_os("TAURI_BUNDLER_DMG_IGNORE_CI").unwrap_or_default() != "true" { if let Some(value) = env::var_os("CI") { if value == "true" { bundle_dmg_cmd.arg("--skip-jenkins"); } } } log::info!(action = "Running"; "bundle_dmg.sh"); // execute the bundle script bundle_dmg_cmd .current_dir(bundle_dir.clone()) .args(vec![dmg_name.as_str(), bundle_file_name.as_str()]) .output_ok() .context("error running bundle_dmg.sh")?; fs::rename(bundle_dir.join(dmg_name), dmg_path.clone())?; // Sign DMG if needed // skipping self-signing DMGs https://github.com/tauri-apps/tauri/issues/12288 let identity = settings.macos().signing_identity.as_deref(); if !settings.no_sign() && identity != Some("-") { if let Some(keychain) = super::sign::keychain(identity)? { super::sign::sign( &keychain, vec![super::sign::SignTarget { path: dmg_path.clone(), is_an_executable: false, }], settings, )?; } } Ok(Bundled { dmg: vec![dmg_path], app: app_bundle_paths, }) }