From 2164d022f5705e59a189007aec7c99cce98136d8 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com> Date: Mon, 15 Apr 2024 02:34:57 -0300 Subject: [PATCH] feat(packager): cross platform macOS app bundle packaging (#198) * feat: allow bundling macOS .app on any platform * packageoutput refactor * revert change * add change file * fmt and clippy * fix deny check --------- Co-authored-by: amr-crabnebula --- .changes/app-bundle-cross-platform.md | 5 +++ .changes/package-output-struct-refactor.md | 5 +++ .prettierignore | 1 + Cargo.lock | 27 ++++++---------- crates/packager/Cargo.toml | 12 +++----- crates/packager/src/error.rs | 10 ------ crates/packager/src/lib.rs | 6 ++-- crates/packager/src/package/app/mod.rs | 36 ++++++++++++++-------- crates/packager/src/package/mod.rs | 22 ++++++++----- crates/packager/src/util.rs | 12 +------- deny.toml | 1 + 11 files changed, 68 insertions(+), 69 deletions(-) create mode 100644 .changes/app-bundle-cross-platform.md create mode 100644 .changes/package-output-struct-refactor.md diff --git a/.changes/app-bundle-cross-platform.md b/.changes/app-bundle-cross-platform.md new file mode 100644 index 00000000..ac8a203d --- /dev/null +++ b/.changes/app-bundle-cross-platform.md @@ -0,0 +1,5 @@ +--- +"cargo-packager": minor +--- + +Allow packaging the macOS app bundle on Linux and Windows hosts (without codesign support). diff --git a/.changes/package-output-struct-refactor.md b/.changes/package-output-struct-refactor.md new file mode 100644 index 00000000..646a6e39 --- /dev/null +++ b/.changes/package-output-struct-refactor.md @@ -0,0 +1,5 @@ +--- +"cargo-packager": minor +--- + +Renamed `PackageOuput` to `PackageOutput` and added `PackageOutput::new`. \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index c6234097..b690894b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,6 +7,7 @@ build *.sh *.desktop *.xml +*.md pnpm-lock.yaml diff --git a/Cargo.lock b/Cargo.lock index b59bc92f..920c3f98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3229,9 +3229,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -3398,7 +3398,7 @@ dependencies = [ "httpdate", "itoa 1.0.10", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -4178,12 +4178,9 @@ dependencies = [ [[package]] name = "line-wrap" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] +checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" [[package]] name = "linked-hash-map" @@ -4461,9 +4458,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -5300,9 +5297,9 @@ checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "plist" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" dependencies = [ "base64 0.21.7", "indexmap 2.1.0", @@ -5980,12 +5977,6 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "salsa20" version = "0.10.2" diff --git a/crates/packager/Cargo.toml b/crates/packager/Cargo.toml index 35cf98db..31b65264 100644 --- a/crates/packager/Cargo.toml +++ b/crates/packager/Cargo.toml @@ -67,6 +67,11 @@ strsim = "0.10" schemars = { workspace = true, optional = true } native-tls = { version = "0.2", optional = true } cargo-packager-utils = { version = "0.1.0", path = "../utils", features = [ "serde" ] } +icns = { package = "tauri-icns", version = "0.1" } +time = { workspace = true, features = [ "formatting" ] } +image = "0.24" +tempfile = "3" +plist = "1" [target."cfg(target_os = \"windows\")".dependencies] winreg = "0.52" @@ -86,10 +91,3 @@ image = "0.24" md5 = "0.7" heck = "0.4" ar = "0.9" - -[target."cfg(target_os = \"macos\")".dependencies] -icns = { package = "tauri-icns", version = "0.1" } -time = { workspace = true, features = [ "formatting" ] } -plist = "1" -image = "0.24" -tempfile = "3" diff --git a/crates/packager/src/error.rs b/crates/packager/src/error.rs index b1557e49..3823fddf 100644 --- a/crates/packager/src/error.rs +++ b/crates/packager/src/error.rs @@ -119,14 +119,6 @@ pub enum Error { #[error("Wix language {0} not found. It must be one of {1}")] UnsupportedWixLanguage(String, String), /// Image crate errors. - #[cfg(any( - target_os = "macos", - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ))] #[error(transparent)] ImageError(#[from] image::ImageError), /// walkdir crate errors. @@ -140,11 +132,9 @@ pub enum Error { RelativeToError(#[from] relative_path::RelativeToError), /// Time error. #[error("`{0}`")] - #[cfg(target_os = "macos")] TimeError(#[from] time::error::Error), /// Plist error. #[error(transparent)] - #[cfg(target_os = "macos")] Plist(#[from] plist::Error), /// Framework not found. #[error("Framework {0} not found")] diff --git a/crates/packager/src/lib.rs b/crates/packager/src/lib.rs index 24d613d6..6fc5aae1 100644 --- a/crates/packager/src/lib.rs +++ b/crates/packager/src/lib.rs @@ -95,7 +95,7 @@ pub use error::{Error, Result}; use flate2::{write::GzEncoder, Compression}; pub use sign::SigningConfig; -pub use package::{package, PackageOuput}; +pub use package::{package, PackageOutput}; use util::PathExt; fn parse_log_level(verbose: u8) -> tracing::Level { @@ -136,7 +136,7 @@ pub fn init_tracing_subscriber(verbosity: u8) { #[tracing::instrument(level = "trace")] pub fn sign_outputs( config: &SigningConfig, - packages: &mut Vec, + packages: &mut Vec, ) -> crate::Result> { let mut signatures = Vec::new(); for package in packages { @@ -171,7 +171,7 @@ pub fn sign_outputs( pub fn package_and_sign( config: &Config, signing_config: &SigningConfig, -) -> crate::Result<(Vec, Vec)> { +) -> crate::Result<(Vec, Vec)> { let mut packages = package(config)?; let signatures = sign_outputs(signing_config, &mut packages)?; Ok((packages, signatures)) diff --git a/crates/packager/src/package/app/mod.rs b/crates/packager/src/package/app/mod.rs index b193c747..28f4c278 100644 --- a/crates/packager/src/package/app/mod.rs +++ b/crates/packager/src/package/app/mod.rs @@ -4,19 +4,15 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use std::{ - collections::BinaryHeap, - ffi::OsStr, - path::{Path, PathBuf}, - process::Command, -}; +use std::path::{Path, PathBuf}; use super::Context; +use crate::{config::Config, util}; + +#[cfg(target_os = "macos")] use crate::{ codesign::macos::{self as codesign, SignTarget}, - config::Config, shell::CommandExt, - util, }; #[tracing::instrument(level = "trace")] @@ -44,7 +40,8 @@ pub(crate) fn package(ctx: &Context) -> crate::Result> { let bin_dir = contents_directory.join("MacOS"); std::fs::create_dir_all(&bin_dir)?; - let mut sign_paths = BinaryHeap::new(); + #[cfg(target_os = "macos")] + let mut sign_paths = std::collections::BinaryHeap::new(); let bundle_icon_file = util::create_icns_file(&resources_dir, config)?; @@ -52,14 +49,15 @@ pub(crate) fn package(ctx: &Context) -> crate::Result> { create_info_plist(&contents_directory, bundle_icon_file, config)?; tracing::debug!("Copying frameworks"); - let framework_paths = copy_frameworks_to_bundle(&contents_directory, config)?; + let _framework_paths = copy_frameworks_to_bundle(&contents_directory, config)?; + #[cfg(target_os = "macos")] sign_paths.extend( - framework_paths + _framework_paths .into_iter() .filter(|p| { let ext = p.extension(); - ext == Some(OsStr::new("framework")) + ext == Some(std::ffi::OsStr::new("framework")) }) .map(|path| SignTarget { path, @@ -125,12 +123,14 @@ pub(crate) fn package(ctx: &Context) -> crate::Result> { continue; } + #[cfg(target_os = "macos")] sign_paths.push(SignTarget { path: file, is_native_binary: true, }); } + #[cfg(target_os = "macos")] if let Some(identity) = config .macos() .and_then(|macos| macos.signing_identity.as_ref()) @@ -329,7 +329,16 @@ fn copy_dir(from: &Path, to: &Path) -> crate::Result<()> { let dest_path = to.join(rel_path); if entry.file_type().is_symlink() { let target = std::fs::read_link(entry.path())?; + #[cfg(unix)] std::os::unix::fs::symlink(&target, &dest_path)?; + #[cfg(windows)] + { + if entry.file_type().is_file() { + std::os::windows::fs::symlink_file(&target, &dest_path)?; + } else { + std::os::windows::fs::symlink_dir(&target, &dest_path)?; + } + } } else if entry.file_type().is_dir() { std::fs::create_dir(dest_path)?; } else { @@ -416,8 +425,9 @@ fn copy_frameworks_to_bundle( Ok(paths) } +#[cfg(target_os = "macos")] fn remove_extra_attr(app_bundle_path: &Path) -> crate::Result<()> { - Command::new("xattr") + std::process::Command::new("xattr") .arg("-cr") .arg(app_bundle_path) .output_ok() diff --git a/crates/packager/src/package/mod.rs b/crates/packager/src/package/mod.rs index 9e4b7b21..5f29bab0 100644 --- a/crates/packager/src/package/mod.rs +++ b/crates/packager/src/package/mod.rs @@ -8,7 +8,6 @@ use crate::{config, shell::CommandExt, util, Config, PackageFormat}; use self::context::Context; -#[cfg(target_os = "macos")] mod app; #[cfg(any( target_os = "linux", @@ -45,16 +44,26 @@ mod context; /// Generated Package metadata. #[derive(Debug, Clone)] #[non_exhaustive] -pub struct PackageOuput { +pub struct PackageOutput { /// The package type. pub format: PackageFormat, /// All paths for this package. pub paths: Vec, } +impl PackageOutput { + /// Creates a new package output. + /// + /// This is only useful if you need to sign the packages in a different process, + /// after packaging the app and storing its paths. + pub fn new(format: PackageFormat, paths: Vec) -> Self { + Self { format, paths } + } +} + /// Package an app using the specified config. #[tracing::instrument(level = "trace")] -pub fn package(config: &Config) -> crate::Result> { +pub fn package(config: &Config) -> crate::Result> { let mut formats = config .formats .clone() @@ -93,17 +102,16 @@ pub fn package(config: &Config) -> crate::Result> { )?; let paths = match format { - #[cfg(target_os = "macos")] PackageFormat::App => app::package(&ctx), #[cfg(target_os = "macos")] PackageFormat::Dmg => { // PackageFormat::App is required for the DMG bundle if !packages .iter() - .any(|b: &PackageOuput| b.format == PackageFormat::App) + .any(|b: &PackageOutput| b.format == PackageFormat::App) { let paths = app::package(&ctx)?; - packages.push(PackageOuput { + packages.push(PackageOutput { format: PackageFormat::App, paths, }); @@ -144,7 +152,7 @@ pub fn package(config: &Config) -> crate::Result> { } }?; - packages.push(PackageOuput { + packages.push(PackageOutput { format: *format, paths, }); diff --git a/crates/packager/src/util.rs b/crates/packager/src/util.rs index 7eef18fa..fa3f9bc8 100644 --- a/crates/packager/src/util.rs +++ b/crates/packager/src/util.rs @@ -283,15 +283,7 @@ pub(crate) fn os_bitness() -> crate::Result { /// Returns true if the path has a filename indicating that it is a high-density /// "retina" icon. Specifically, returns true the file stem ends with /// "@2x" (a convention specified by the [Apple developer docs]( -/// https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html)). -#[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "macos", -))] +/// https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html)).xw pub(crate) fn is_retina>(path: P) -> bool { path.as_ref() .file_stem() @@ -303,7 +295,6 @@ pub(crate) fn is_retina>(path: P) -> bool { // Given a list of icon files, try to produce an ICNS file in the out_dir // and return the path to it. Returns `Ok(None)` if no usable icons // were provided. -#[cfg(target_os = "macos")] pub fn create_icns_file(out_dir: &Path, config: &crate::Config) -> crate::Result> { use image::GenericImageView; @@ -395,7 +386,6 @@ pub fn create_icns_file(out_dir: &Path, config: &crate::Config) -> crate::Result } // Converts an image::DynamicImage into an icns::Image. -#[cfg(target_os = "macos")] fn make_icns_image(img: image::DynamicImage) -> std::io::Result { let pixel_format = match img.color() { image::ColorType::Rgba8 => icns::PixelFormat::RGBA, diff --git a/deny.toml b/deny.toml index 91059c7a..48a55550 100644 --- a/deny.toml +++ b/deny.toml @@ -1,4 +1,5 @@ # Target triples to include when checking. This is essentially our supported target list. +[graph] targets = [ { triple = "x86_64-unknown-linux-gnu" }, { triple = "aarch64-unknown-linux-gnu" },