Просмотр исходного кода

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>
Jason Tsai 1 год назад
Родитель
Сommit
89911296e4
2 измененных файлов с 110 добавлено и 16 удалено
  1. 6 0
      .changes/mac-bundler-nested-code-sign.md
  2. 104 16
      tooling/bundler/src/bundle/macos/app.rs

+ 6 - 0
.changes/mac-bundler-nested-code-sign.md

@@ -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").

+ 104 - 16
tooling/bundler/src/bundle/macos/app.rs

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