feat(bundler): codesign nested code on macos (#8259)

* feat(bundler): codesign nested code on macos

* chore: update changelog tag

* typo

* also sign stuff in the Libraries folder

tested this for spacedrive, which has a bunch of dylib inside the libraries folder

* Update .changes/mac-bundler-nested-code-sign.md [skip ci]

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Jason Tsai 2023-12-29 01:58:24 +09:00 committed by GitHub
parent 8f8729d918
commit 89911296e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 110 additions and 16 deletions

View File

@ -0,0 +1,6 @@
---
"tauri-cli": patch:feat
"tauri-bundler": patch:feat
---
On macOS, support for signing nested .dylib, .app, .xpc and .framework under predefined directories inside the bundled frameworks ("MacOS", "Frameworks", "Plugins", "Helpers", "XPCServices" and "Libraries").

View File

@ -39,6 +39,15 @@ use std::{
process::Command,
};
const NESTED_CODE_FOLDER: [&str; 6] = [
"MacOS",
"Frameworks",
"Plugins",
"Helpers",
"XPCServices",
"Libraries",
];
/// 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>> {
@ -77,18 +86,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
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,
}),
);
sign_paths.extend(framework_paths);
settings.copy_resources(&resources_dir)?;
@ -141,7 +139,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
fn remove_extra_attr(app_bundle_path: &Path) -> crate::Result<()> {
Command::new("xattr")
.arg("-cr")
.arg("-crs")
.arg(app_bundle_path)
.output_ok()
.context("failed to remove extra attributes from app bundle")?;
@ -265,7 +263,7 @@ fn copy_framework_from(dest_dir: &Path, framework: &str, src_dir: &Path) -> crat
fn copy_frameworks_to_bundle(
bundle_directory: &Path,
settings: &Settings,
) -> crate::Result<Vec<PathBuf>> {
) -> crate::Result<Vec<SignTarget>> {
let mut paths = Vec::new();
let frameworks = settings
@ -288,7 +286,7 @@ fn copy_frameworks_to_bundle(
.expect("Couldn't get framework filename");
let dest_path = dest_dir.join(src_name);
common::copy_dir(&src_path, &dest_path)?;
paths.push(dest_path);
add_framework_sign_path(&src_path, &dest_path, &mut paths);
continue;
} else if framework.ends_with(".dylib") {
let src_path = PathBuf::from(framework);
@ -301,7 +299,10 @@ fn copy_frameworks_to_bundle(
let src_name = src_path.file_name().expect("Couldn't get library filename");
let dest_path = dest_dir.join(src_name);
common::copy_file(&src_path, &dest_path)?;
paths.push(dest_path);
paths.push(SignTarget {
path: dest_path,
is_an_executable: false,
});
continue;
} else if framework.contains('/') {
return Err(crate::Error::GenericError(format!(
@ -330,3 +331,90 @@ fn copy_frameworks_to_bundle(
}
Ok(paths)
}
/// Recursively add framework's sign paths.
/// If the framework has multiple versions, it will sign "Current" version by default.
fn add_framework_sign_path(
framework_root: &Path,
dest_path: &Path,
sign_paths: &mut Vec<SignTarget>,
) {
if framework_root.join("Versions/Current").exists() {
add_nested_code_sign_path(
&framework_root.join("Versions/Current"),
&dest_path.join("Versions/Current"),
sign_paths,
);
} else {
add_nested_code_sign_path(framework_root, dest_path, sign_paths);
}
sign_paths.push(SignTarget {
path: dest_path.into(),
is_an_executable: false,
});
}
/// Recursively add executable bundle's sign path (.xpc, .app).
fn add_executable_bundle_sign_path(
bundle_root: &Path,
dest_path: &Path,
sign_paths: &mut Vec<SignTarget>,
) {
if bundle_root.join("Contents").exists() {
add_nested_code_sign_path(
&bundle_root.join("Contents"),
&dest_path.join("Contents"),
sign_paths,
);
} else {
add_nested_code_sign_path(bundle_root, dest_path, sign_paths);
}
sign_paths.push(SignTarget {
path: dest_path.into(),
is_an_executable: true,
});
}
fn add_nested_code_sign_path(src_path: &Path, dest_path: &Path, sign_paths: &mut Vec<SignTarget>) {
for folder_name in NESTED_CODE_FOLDER.iter() {
let src_folder_path = src_path.join(folder_name);
let dest_folder_path = dest_path.join(folder_name);
if src_folder_path.exists() {
for entry in walkdir::WalkDir::new(src_folder_path)
.min_depth(1)
.max_depth(1)
.into_iter()
.filter_map(|e| e.ok())
{
if entry.path_is_symlink() || entry.file_name().to_string_lossy().starts_with('.') {
continue;
}
let dest_path = dest_folder_path.join(entry.file_name());
let ext = entry.path().extension();
if entry.path().is_dir() {
// Bundles, like .app, .framework, .xpc
if ext == Some(OsStr::new("framework")) {
add_framework_sign_path(&entry.clone().into_path(), &dest_path, sign_paths);
} else if ext == Some(OsStr::new("xpc")) || ext == Some(OsStr::new("app")) {
add_executable_bundle_sign_path(&entry.clone().into_path(), &dest_path, sign_paths);
}
} else if entry.path().is_file() {
// Binaries, like .dylib, Mach-O executables
if ext == Some(OsStr::new("dylib")) {
sign_paths.push(SignTarget {
path: dest_path,
is_an_executable: false,
});
} else if ext.is_none() {
sign_paths.push(SignTarget {
path: dest_path,
is_an_executable: true,
});
}
}
}
}
}
}