diff --git a/.changes/bundler-xattr.md b/.changes/bundler-xattr.md new file mode 100644 index 000000000..561bb20e7 --- /dev/null +++ b/.changes/bundler-xattr.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": patch:bug +--- + +Remove extended attributes on the macOS app bundle using `xattr -cr $PATH`. diff --git a/.changes/codesign-additional-files.md b/.changes/codesign-additional-files.md new file mode 100644 index 000000000..17a972c80 --- /dev/null +++ b/.changes/codesign-additional-files.md @@ -0,0 +1,5 @@ +--- +"tauri-bundler": patch:bug +--- + +Code sign sidecars and frameworks on macOS. diff --git a/core/tests/app-updater/frameworks/test.framework/Headers b/core/tests/app-updater/frameworks/test.framework/Headers deleted file mode 100644 index a177d2a6b..000000000 --- a/core/tests/app-updater/frameworks/test.framework/Headers +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Headers \ No newline at end of file diff --git a/core/tests/app-updater/frameworks/test.framework/Headers b/core/tests/app-updater/frameworks/test.framework/Headers new file mode 120000 index 000000000..a177d2a6b --- /dev/null +++ b/core/tests/app-updater/frameworks/test.framework/Headers @@ -0,0 +1 @@ +Versions/Current/Headers \ No newline at end of file diff --git a/core/tests/app-updater/frameworks/test.framework/Modules b/core/tests/app-updater/frameworks/test.framework/Modules new file mode 120000 index 000000000..5736f3186 --- /dev/null +++ b/core/tests/app-updater/frameworks/test.framework/Modules @@ -0,0 +1 @@ +Versions/Current/Modules \ No newline at end of file diff --git a/core/tests/app-updater/frameworks/test.framework/Resources b/core/tests/app-updater/frameworks/test.framework/Resources deleted file mode 100644 index 953ee36f3..000000000 --- a/core/tests/app-updater/frameworks/test.framework/Resources +++ /dev/null @@ -1 +0,0 @@ -Versions/Current/Resources \ No newline at end of file diff --git a/core/tests/app-updater/frameworks/test.framework/Resources b/core/tests/app-updater/frameworks/test.framework/Resources new file mode 120000 index 000000000..953ee36f3 --- /dev/null +++ b/core/tests/app-updater/frameworks/test.framework/Resources @@ -0,0 +1 @@ +Versions/Current/Resources \ No newline at end of file diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h b/core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h index fed3381b2..3539143d0 100644 --- a/core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h +++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/Headers/test.h @@ -1 +1,18 @@ -// Testing that a header can be included +// +// test.h +// test +// +// Created by Trey Smith on 9/15/23. +// + +#import + +//! Project version number for test. +FOUNDATION_EXPORT double testVersionNumber; + +//! Project version string for test. +FOUNDATION_EXPORT const unsigned char testVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/Modules/module.modulemap b/core/tests/app-updater/frameworks/test.framework/Versions/A/Modules/module.modulemap new file mode 100644 index 000000000..f1545257a --- /dev/null +++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/Modules/module.modulemap @@ -0,0 +1,6 @@ +framework module test { + umbrella header "test.h" + + export * + module * { export * } +} diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist b/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist index 593ddefdb..6f736f0a2 100644 --- a/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist +++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/Info.plist @@ -2,12 +2,14 @@ + BuildMachineOSBuild + 22D68 CFBundleDevelopmentRegion en CFBundleExecutable test CFBundleIdentifier - com.tauri.test.framework + com.tauri.test CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -15,18 +17,30 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.0 - CFBundleVersion - 1.0.0 - CFBundleSignature - ???? - LSMinimumSystemVersion - 10.15 + 1.0 CFBundleSupportedPlatforms MacOSX - NSPrincipalClass + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + DTPlatformName + macosx + DTPlatformVersion + 13.3 + DTSDKBuild + 22E245 + DTSDKName + macosx13.3 + DTXcode + 1431 + DTXcodeBuild + 14E300c + LSMinimumSystemVersion + 13.2 diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/LICENSE b/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/LICENSE deleted file mode 100644 index eb609b5b8..000000000 --- a/core/tests/app-updater/frameworks/test.framework/Versions/A/Resources/LICENSE +++ /dev/null @@ -1 +0,0 @@ -Test that a LICENSE file can be included diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/_CodeSignature/CodeResources b/core/tests/app-updater/frameworks/test.framework/Versions/A/_CodeSignature/CodeResources new file mode 100644 index 000000000..82d206327 --- /dev/null +++ b/core/tests/app-updater/frameworks/test.framework/Versions/A/_CodeSignature/CodeResources @@ -0,0 +1,142 @@ + + + + + files + + Resources/Info.plist + + /aPV7Q20g0elr7OiZJoUNggTOcg= + + + files2 + + Headers/test.h + + hash2 + + 5RA6Mnq5sNoaC4wKcFe6zymVmEL5Vb44G4BGqFjgZMM= + + + Modules/module.modulemap + + hash2 + + C6uLLSnQu9M2qLElVCkeo2JpnvWMxtArinQzmlh3v2A= + + + Resources/Info.plist + + hash2 + + nPMotNIMgvMfHtkRdpeehzfBiCZLnksfiD3nldUPzTE= + + + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/A/test b/core/tests/app-updater/frameworks/test.framework/Versions/A/test old mode 100644 new mode 100755 index 5b0be073c..687eb01d8 Binary files a/core/tests/app-updater/frameworks/test.framework/Versions/A/test and b/core/tests/app-updater/frameworks/test.framework/Versions/A/test differ diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/Current b/core/tests/app-updater/frameworks/test.framework/Versions/Current deleted file mode 100644 index 8c7e5a667..000000000 --- a/core/tests/app-updater/frameworks/test.framework/Versions/Current +++ /dev/null @@ -1 +0,0 @@ -A \ No newline at end of file diff --git a/core/tests/app-updater/frameworks/test.framework/Versions/Current b/core/tests/app-updater/frameworks/test.framework/Versions/Current new file mode 120000 index 000000000..8c7e5a667 --- /dev/null +++ b/core/tests/app-updater/frameworks/test.framework/Versions/Current @@ -0,0 +1 @@ +A \ No newline at end of file diff --git a/core/tests/app-updater/frameworks/test.framework/test b/core/tests/app-updater/frameworks/test.framework/test deleted file mode 120000 index 0ffb41cb9..000000000 --- a/core/tests/app-updater/frameworks/test.framework/test +++ /dev/null @@ -1 +0,0 @@ -Versions/Current \ No newline at end of file diff --git a/core/tests/app-updater/tests/update.rs b/core/tests/app-updater/tests/update.rs index aab499136..dd3024367 100644 --- a/core/tests/app-updater/tests/update.rs +++ b/core/tests/app-updater/tests/update.rs @@ -318,12 +318,12 @@ fn update_app() { .first() .unwrap() .1 - .join("Contents/Frameworks/test.framework/test"), + .join("Contents/Frameworks/test.framework/Modules"), ) - .expect("test.framework/test metadata"); + .expect("test.framework/Modules metadata"); assert!( meta.file_type().is_symlink(), - "test.framework/test should be a symlink" + "test.framework/Modules should be a symlink" ); } diff --git a/tooling/bundler/src/bundle/macos/app.rs b/tooling/bundler/src/bundle/macos/app.rs index 809825e54..cae46785d 100644 --- a/tooling/bundler/src/bundle/macos/app.rs +++ b/tooling/bundler/src/bundle/macos/app.rs @@ -23,9 +23,9 @@ // files into the `Contents` directory of the bundle. use super::{ - super::common, + super::common::{self, CommandExt}, icon::create_icns_file, - sign::{notarize, notarize_auth, sign}, + sign::{notarize, notarize_auth, sign, SignTarget}, }; use crate::Settings; @@ -33,8 +33,10 @@ use anyhow::Context; use log::{info, warn}; use std::{ + ffi::OsStr, fs, path::{Path, PathBuf}, + process::Command, }; /// Bundles the project. @@ -65,6 +67,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { let resources_dir = bundle_directory.join("Resources"); let bin_dir = bundle_directory.join("MacOS"); + let mut sign_paths = Vec::new(); let bundle_icon_file: Option = { create_icns_file(&resources_dir, settings).with_context(|| "Failed to create app icon")? }; @@ -72,20 +75,52 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { create_info_plist(&bundle_directory, bundle_icon_file, settings) .with_context(|| "Failed to create Info.plist")?; - copy_frameworks_to_bundle(&bundle_directory, settings) + let framework_paths = copy_frameworks_to_bundle(&bundle_directory, settings) .with_context(|| "Failed to bundle frameworks")?; + sign_paths.extend( + framework_paths + .into_iter() + .filter(|p| { + let ext = p.extension(); + ext == Some(OsStr::new("framework")) || ext == Some(OsStr::new("dylib")) + }) + .map(|path| SignTarget { + path, + is_an_executable: false, + }), + ); settings.copy_resources(&resources_dir)?; - settings + let bin_paths = settings .copy_binaries(&bin_dir) .with_context(|| "Failed to copy external binaries")?; + sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget { + path, + is_an_executable: true, + })); - copy_binaries_to_bundle(&bundle_directory, settings)?; + let bin_paths = copy_binaries_to_bundle(&bundle_directory, settings)?; + sign_paths.extend(bin_paths.into_iter().map(|path| SignTarget { + path, + is_an_executable: true, + })); if let Some(identity) = &settings.macos().signing_identity { + // Sign frameworks and sidecar binaries first, per apple, signing must be done inside out + // https://developer.apple.com/forums/thread/701514 + sign_paths.push(SignTarget { + path: app_bundle_path.clone(), + is_an_executable: true, + }); + + // Remove extra attributes, which could cause codesign to fail + // https://developer.apple.com/library/archive/qa/qa1940/_index.html + remove_extra_attr(&app_bundle_path)?; + // sign application - sign(app_bundle_path.clone(), identity, settings, true)?; + sign(sign_paths, identity, settings)?; + // notarization is required for distribution match notarize_auth() { Ok(auth) => { @@ -100,15 +135,30 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { Ok(vec![app_bundle_path]) } +fn remove_extra_attr(app_bundle_path: &Path) -> crate::Result<()> { + Command::new("xattr") + .arg("-cr") + .arg(app_bundle_path) + .output_ok() + .context("failed to remove extra attributes from app bundle")?; + Ok(()) +} + // Copies the app's binaries to the bundle. -fn copy_binaries_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> { +fn copy_binaries_to_bundle( + bundle_directory: &Path, + settings: &Settings, +) -> crate::Result> { + let mut paths = Vec::new(); let dest_dir = bundle_directory.join("MacOS"); for bin in settings.binaries() { let bin_path = settings.binary_path(bin); - common::copy_file(&bin_path, &dest_dir.join(bin.name())) + let dest_path = dest_dir.join(bin.name()); + common::copy_file(&bin_path, &dest_path) .with_context(|| format!("Failed to copy binary from {:?}", bin_path))?; + paths.push(dest_path); } - Ok(()) + Ok(paths) } // Creates the Info.plist file. @@ -208,7 +258,12 @@ fn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crat } // Copies the macOS application bundle frameworks to the .app -fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> crate::Result<()> { +fn copy_frameworks_to_bundle( + bundle_directory: &Path, + settings: &Settings, +) -> crate::Result> { + let mut paths = Vec::new(); + let frameworks = settings .macos() .frameworks @@ -216,7 +271,7 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr .cloned() .unwrap_or_default(); if frameworks.is_empty() { - return Ok(()); + return Ok(paths); } let dest_dir = bundle_directory.join("Frameworks"); fs::create_dir_all(bundle_directory) @@ -227,7 +282,9 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr let src_name = src_path .file_name() .expect("Couldn't get framework filename"); - common::copy_dir(&src_path, &dest_dir.join(src_name))?; + let dest_path = dest_dir.join(src_name); + common::copy_dir(&src_path, &dest_path)?; + paths.push(dest_path); continue; } else if framework.ends_with(".dylib") { let src_path = PathBuf::from(framework); @@ -238,7 +295,9 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr ))); } let src_name = src_path.file_name().expect("Couldn't get library filename"); - common::copy_file(&src_path, &dest_dir.join(src_name))?; + let dest_path = dest_dir.join(src_name); + common::copy_file(&src_path, &dest_path)?; + paths.push(dest_path); continue; } else if framework.contains('/') { return Err(crate::Error::GenericError(format!( @@ -265,5 +324,5 @@ fn copy_frameworks_to_bundle(bundle_directory: &Path, settings: &Settings) -> cr framework ))); } - Ok(()) + Ok(paths) } diff --git a/tooling/bundler/src/bundle/macos/dmg.rs b/tooling/bundler/src/bundle/macos/dmg.rs index ba98c4e65..3748a5cfe 100644 --- a/tooling/bundler/src/bundle/macos/dmg.rs +++ b/tooling/bundler/src/bundle/macos/dmg.rs @@ -153,7 +153,14 @@ pub fn bundle_project(settings: &Settings, bundles: &[Bundle]) -> crate::Result< // Sign DMG if needed if let Some(identity) = &settings.macos().signing_identity { - super::sign::sign(dmg_path.clone(), identity, settings, false)?; + super::sign::sign( + vec![super::sign::SignTarget { + path: dmg_path.clone(), + is_an_executable: false, + }], + identity, + settings, + )?; } Ok(Bundled { diff --git a/tooling/bundler/src/bundle/macos/sign.rs b/tooling/bundler/src/bundle/macos/sign.rs index a4e5ec9c0..b2ab2f79d 100644 --- a/tooling/bundler/src/bundle/macos/sign.rs +++ b/tooling/bundler/src/bundle/macos/sign.rs @@ -144,13 +144,13 @@ pub fn delete_keychain() { .output_ok(); } -pub fn sign( - path_to_sign: PathBuf, - identity: &str, - settings: &Settings, - is_an_executable: bool, -) -> crate::Result<()> { - info!(action = "Signing"; "{} with identity \"{}\"", path_to_sign.display(), identity); +pub struct SignTarget { + pub path: PathBuf, + pub is_an_executable: bool, +} + +pub fn sign(targets: Vec, identity: &str, settings: &Settings) -> crate::Result<()> { + info!(action = "Signing"; "with identity \"{}\"", identity); let setup_keychain = if let (Some(certificate_encoded), Some(certificate_password)) = ( var_os("APPLE_CERTIFICATE"), @@ -164,20 +164,24 @@ pub fn sign( false }; - let res = try_sign( - path_to_sign, - identity, - settings, - is_an_executable, - setup_keychain, - ); + info!("Signing app bundle..."); + + for target in targets { + try_sign( + target.path, + identity, + settings, + target.is_an_executable, + setup_keychain, + )?; + } if setup_keychain { // delete the keychain again after signing delete_keychain(); } - res + Ok(()) } fn try_sign( @@ -187,6 +191,8 @@ fn try_sign( is_an_executable: bool, tauri_keychain: bool, ) -> crate::Result<()> { + info!(action = "Signing"; "{}", path_to_sign.display()); + let mut args = vec!["--force", "-s", identity]; if tauri_keychain { @@ -205,13 +211,9 @@ fn try_sign( args.push("runtime"); } - if path_to_sign.is_dir() { - args.push("--deep"); - } - Command::new("codesign") .args(args) - .arg(path_to_sign.to_string_lossy().to_string()) + .arg(path_to_sign) .output_ok() .context("failed to sign app")?; @@ -260,7 +262,14 @@ pub fn notarize( // sign the zip file if let Some(identity) = &settings.macos().signing_identity { - sign(zip_path.clone(), identity, settings, false)?; + sign( + vec![SignTarget { + path: zip_path.clone(), + is_an_executable: false, + }], + identity, + settings, + )?; }; let notarize_args = vec![ diff --git a/tooling/bundler/src/bundle/settings.rs b/tooling/bundler/src/bundle/settings.rs index 46d67d8be..09936a74f 100644 --- a/tooling/bundler/src/bundle/settings.rs +++ b/tooling/bundler/src/bundle/settings.rs @@ -761,7 +761,11 @@ impl Settings { } /// Copies external binaries to a path. - pub fn copy_binaries(&self, path: &Path) -> crate::Result<()> { + /// + /// Returns the list of destination paths. + pub fn copy_binaries(&self, path: &Path) -> crate::Result> { + let mut paths = Vec::new(); + for src in self.external_binaries() { let src = src?; let dest = path.join( @@ -771,9 +775,10 @@ impl Settings { .to_string_lossy() .replace(&format!("-{}", self.target), ""), ); - common::copy_file(&src, dest)?; + common::copy_file(&src, &dest)?; + paths.push(dest); } - Ok(()) + Ok(paths) } /// Copies resources to a path.