1use anyhow::Context;
2use serde::Deserialize;
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8#[cfg(unix)]
9use std::os::unix::fs::PermissionsExt;
10
11mod symbols;
12mod util;
13
14pub use anyhow::Result;
16
17fn build_usage_string(command_name: &str) -> String {
18 format!(
19 "Usage:
20 {command_name} bundle <package> [--release]
21 {command_name} bundle -p <package1> -p <package2> ... [--release]
22
23 {command_name} bundle-universal <package> [--release] (macOS only)
24 {command_name} bundle-universal -p <package1> -p <package2> ... [--release] (macOS only)
25
26 All other 'cargo build' options are supported, including '--target' and '--profile'."
27 )
28}
29
30type BundlerConfig = HashMap<String, PackageConfig>;
33
34#[derive(Debug, Clone, Deserialize)]
35struct PackageConfig {
36 name: Option<String>,
37}
38
39#[derive(Debug, Clone, Copy)]
43pub enum CompilationTarget {
44 Linux(Architecture),
45 MacOS(Architecture),
46 MacOSUniversal,
48 Windows(Architecture),
49}
50
51#[derive(Debug, Clone, Copy)]
52pub enum Architecture {
53 X86,
54 X86_64,
55 RISCV64,
56 AArch64,
59}
60
61#[derive(Debug, Clone, Copy)]
63pub enum BundleType {
64 Plugin,
65 Binary,
66}
67
68pub fn main() -> Result<()> {
70 let args = std::env::args().skip(1);
71 main_with_args("cargo xtask", args)
72}
73
74pub fn main_with_args(command_name: &str, args: impl IntoIterator<Item = String>) -> Result<()> {
78 chdir_workspace_root()?;
79 let cargo_metadata = cargo_metadata::MetadataCommand::new()
80 .manifest_path("./Cargo.toml")
81 .exec()
82 .context("Could not parse `cargo-metadata`")?;
83 let target_dir = cargo_metadata.target_directory.as_std_path();
84
85 let mut args = args.into_iter();
86 let usage_string = build_usage_string(command_name);
87 let command = args
88 .next()
89 .with_context(|| format!("Missing command name\n\n{usage_string}",))?;
90 match command.as_str() {
91 "bundle" => {
92 let (packages, other_args) = split_bundle_args(args, &usage_string)?;
97
98 build(&packages, &other_args)?;
100
101 bundle(target_dir, &packages[0], &other_args, false)?;
102 for package in packages.into_iter().skip(1) {
103 bundle(target_dir, &package, &other_args, false)?;
104 }
105
106 Ok(())
107 }
108 "bundle-universal" => {
109 let (packages, other_args) = split_bundle_args(args, &usage_string)?;
113
114 for arg in &other_args {
115 if arg == "--target" || arg.starts_with("--target=") {
116 anyhow::bail!(
117 "'{command_name} xtask bundle-universal' is incompatible with the '{arg}' \
118 option."
119 )
120 }
121 }
122
123 let mut x86_64_args = other_args.clone();
131 x86_64_args.push(String::from("--target=x86_64-apple-darwin"));
132 build(&packages, &x86_64_args)?;
133 let mut aarch64_args = other_args.clone();
134 aarch64_args.push(String::from("--target=aarch64-apple-darwin"));
135 build(&packages, &aarch64_args)?;
136
137 bundle(target_dir, &packages[0], &other_args, true)?;
140 for package in packages.into_iter().skip(1) {
141 bundle(target_dir, &package, &other_args, true)?;
142 }
143
144 Ok(())
145 }
146 "known-packages" => list_known_packages(),
149 _ => anyhow::bail!("Unknown command '{command}'\n\n{usage_string}"),
150 }
151}
152
153pub fn chdir_workspace_root() -> Result<()> {
159 let project_dir = std::env::var("CARGO_MANIFEST_DIR")
162 .map(PathBuf::from)
163 .or_else(|_| std::env::current_dir())
164 .context(
165 "'$CARGO_MANIFEST_DIR' was not set and the current working directory could not be \
166 found",
167 )?;
168
169 let workspace_root = project_dir
170 .ancestors()
171 .filter(|dir| dir.join("Cargo.toml").exists())
172 .last()
175 .with_context(|| {
176 format!(
177 "Could not find a 'Cargo.toml' file in '{}' or any of its parent directories",
178 project_dir.display()
179 )
180 })?;
181
182 std::env::set_current_dir(workspace_root)
183 .context("Could not change to workspace root directory")
184}
185
186pub fn build(packages: &[String], args: &[String]) -> Result<()> {
190 let package_args = packages.iter().flat_map(|package| ["-p", package]);
191
192 let status = Command::new("cargo")
193 .arg("build")
194 .args(package_args)
195 .args(args)
196 .status()
197 .with_context(|| format!("Could not call cargo to build {}", packages.join(", ")))?;
198 if !status.success() {
199 anyhow::bail!("Could not build {}", packages.join(", "));
200 } else {
201 Ok(())
202 }
203}
204
205pub fn bundle(target_dir: &Path, package: &str, args: &[String], universal: bool) -> Result<()> {
220 let mut build_type_dir = "debug";
221 let mut cross_compile_target: Option<String> = None;
222 for arg_idx in (0..args.len()).rev() {
223 let arg = &args[arg_idx];
224 match arg.as_str() {
225 "--profile" => {
226 build_type_dir = args.get(arg_idx + 1).context("Missing profile name")?;
228 }
229 "--release" => build_type_dir = "release",
230 "--target" => {
231 cross_compile_target = Some(
233 args.get(arg_idx + 1)
234 .context("Missing cross-compile target")?
235 .to_owned(),
236 );
237 }
238 arg if arg.starts_with("--profile=") => {
239 build_type_dir = arg
240 .strip_prefix("--profile=")
241 .context("Missing profile name")?;
242 }
243 arg if arg.starts_with("--target=") => {
244 cross_compile_target = Some(
245 arg.strip_prefix("--target=")
246 .context("Missing cross-compile target")?
247 .to_owned(),
248 );
249 }
250 _ => (),
251 }
252 }
253
254 if universal {
257 let x86_64_target_base =
258 target_base(target_dir, Some("x86_64-apple-darwin"))?.join(build_type_dir);
259 let x86_64_bin_path = x86_64_target_base.join(binary_basename(
260 package,
261 CompilationTarget::MacOS(Architecture::X86_64),
262 ));
263 let x86_64_lib_path = x86_64_target_base.join(library_basename(
264 package,
265 CompilationTarget::MacOS(Architecture::X86_64),
266 ));
267
268 let aarch64_target_base =
269 target_base(target_dir, Some("aarch64-apple-darwin"))?.join(build_type_dir);
270 let aarch64_bin_path = aarch64_target_base.join(binary_basename(
271 package,
272 CompilationTarget::MacOS(Architecture::AArch64),
273 ));
274 let aarch64_lib_path = aarch64_target_base.join(library_basename(
275 package,
276 CompilationTarget::MacOS(Architecture::AArch64),
277 ));
278
279 let build_bin = x86_64_bin_path.exists() && aarch64_bin_path.exists();
280 let build_lib = x86_64_lib_path.exists() && aarch64_lib_path.exists();
281 if !build_bin && !build_lib {
282 anyhow::bail!("Could not find built libraries for universal build.");
283 }
284
285 eprintln!();
286 if build_bin {
287 bundle_binary(
288 target_dir,
289 package,
290 &[&x86_64_bin_path, &aarch64_bin_path],
291 CompilationTarget::MacOSUniversal,
292 )?;
293 }
294 if build_lib {
295 bundle_plugin(
296 target_dir,
297 package,
298 &[&x86_64_lib_path, &aarch64_lib_path],
299 CompilationTarget::MacOSUniversal,
300 )?;
301 }
302 } else {
303 let compilation_target = compilation_target(cross_compile_target.as_deref())?;
304 let target_base =
305 target_base(target_dir, cross_compile_target.as_deref())?.join(build_type_dir);
306 let bin_path = target_base.join(binary_basename(package, compilation_target));
307 let lib_path = target_base.join(library_basename(package, compilation_target));
308 if !bin_path.exists() && !lib_path.exists() {
309 anyhow::bail!(
310 r#"Could not find a built library at '{}'.
311
312Hint: Maybe you forgot to add:
313
314[lib]
315crate-type = ["cdylib"]
316
317to your Cargo.toml file?"#,
318 lib_path.display()
319 );
320 }
321
322 eprintln!();
323 if bin_path.exists() {
324 bundle_binary(target_dir, package, &[&bin_path], compilation_target)?;
325 }
326 if lib_path.exists() {
327 bundle_plugin(target_dir, package, &[&lib_path], compilation_target)?;
328 }
329 }
330
331 Ok(())
332}
333
334fn bundle_binary(
338 target_dir: &Path,
339 package: &str,
340 bin_paths: &[&Path],
341 compilation_target: CompilationTarget,
342) -> Result<()> {
343 let bundle_home_dir = bundle_home(target_dir);
344 let bundle_name = match load_bundler_config()?.and_then(|c| c.get(package).cloned()) {
345 Some(PackageConfig { name: Some(name) }) => name,
346 _ => package.to_string(),
347 };
348
349 let standalone_bundle_binary_name =
351 standalone_bundle_binary_name(&bundle_name, compilation_target);
352 let standalone_binary_path = bundle_home_dir.join(&standalone_bundle_binary_name);
353
354 fs::create_dir_all(standalone_binary_path.parent().unwrap())
355 .context("Could not create standalone bundle directory")?;
356 util::reflink_or_combine(bin_paths, &standalone_binary_path, compilation_target)
357 .context("Could not create standalone bundle")?;
358
359 #[cfg(unix)]
362 if let Ok(metadata) = fs::metadata(&standalone_binary_path) {
363 let mut permissions = metadata.permissions();
365 permissions.set_mode(permissions.mode() | 0b0001001001);
366
367 fs::set_permissions(&standalone_binary_path, permissions).with_context(|| {
368 format!(
369 "Could not make '{}' executable",
370 standalone_binary_path.display()
371 )
372 })?;
373 }
374
375 let standalone_bundle_home = bundle_home_dir.join(
376 Path::new(&standalone_bundle_binary_name)
377 .components()
378 .next()
379 .expect("Malformed standalone binary path"),
380 );
381 maybe_create_macos_bundle_metadata(
382 package,
383 &bundle_name,
384 &standalone_bundle_home,
385 compilation_target,
386 BundleType::Binary,
387 )?;
388 maybe_codesign(&standalone_bundle_home, compilation_target);
389
390 eprintln!(
391 "Created a standalone bundle at '{}'",
392 standalone_bundle_home.display()
393 );
394
395 Ok(())
396}
397
398fn bundle_plugin(
402 target_dir: &Path,
403 package: &str,
404 lib_paths: &[&Path],
405 compilation_target: CompilationTarget,
406) -> Result<()> {
407 let bundle_home_dir = bundle_home(target_dir);
408 let bundle_name = match load_bundler_config()?.and_then(|c| c.get(package).cloned()) {
409 Some(PackageConfig { name: Some(name) }) => name,
410 _ => package.to_string(),
411 };
412
413 let first_lib_path = lib_paths.first().context("Empty library paths slice")?;
418
419 let bundle_clap = symbols::exported(first_lib_path, "clap_entry")
420 .with_context(|| format!("Could not parse '{}'", first_lib_path.display()))?;
421 let bundle_vst2 = symbols::exported(first_lib_path, "VSTPluginMain")
426 .with_context(|| format!("Could not parse '{}'", first_lib_path.display()))?;
427 let bundle_vst3 = symbols::exported(first_lib_path, "GetPluginFactory")
428 .with_context(|| format!("Could not parse '{}'", first_lib_path.display()))?;
429 let bundled_plugin = bundle_clap || bundle_vst2 || bundle_vst3;
430
431 if bundle_clap {
432 let clap_bundle_library_name = clap_bundle_library_name(&bundle_name, compilation_target);
433 let clap_lib_path = bundle_home_dir.join(&clap_bundle_library_name);
434
435 fs::create_dir_all(clap_lib_path.parent().unwrap())
436 .context("Could not create CLAP bundle directory")?;
437 util::reflink_or_combine(lib_paths, &clap_lib_path, compilation_target)
438 .context("Could not create CLAP bundle")?;
439
440 let clap_bundle_home = bundle_home_dir.join(
443 Path::new(&clap_bundle_library_name)
444 .components()
445 .next()
446 .expect("Malformed CLAP library path"),
447 );
448 maybe_create_macos_bundle_metadata(
449 package,
450 &bundle_name,
451 &clap_bundle_home,
452 compilation_target,
453 BundleType::Plugin,
454 )?;
455 maybe_codesign(&clap_bundle_home, compilation_target);
456
457 eprintln!("Created a CLAP bundle at '{}'", clap_bundle_home.display());
458 }
459 if bundle_vst2 {
460 let vst2_bundle_library_name = vst2_bundle_library_name(&bundle_name, compilation_target);
461 let vst2_lib_path = bundle_home_dir.join(&vst2_bundle_library_name);
462
463 fs::create_dir_all(vst2_lib_path.parent().unwrap())
464 .context("Could not create VST2 bundle directory")?;
465 util::reflink_or_combine(lib_paths, &vst2_lib_path, compilation_target)
466 .context("Could not create VST2 bundle")?;
467
468 let vst2_bundle_home = bundle_home_dir.join(
471 Path::new(&vst2_bundle_library_name)
472 .components()
473 .next()
474 .expect("Malformed VST2 library path"),
475 );
476 maybe_create_macos_bundle_metadata(
477 package,
478 &bundle_name,
479 &vst2_bundle_home,
480 compilation_target,
481 BundleType::Plugin,
482 )?;
483 maybe_codesign(&vst2_bundle_home, compilation_target);
484
485 eprintln!("Created a VST2 bundle at '{}'", vst2_bundle_home.display());
486 }
487 if bundle_vst3 {
488 let vst3_lib_path =
489 bundle_home_dir.join(vst3_bundle_library_name(&bundle_name, compilation_target));
490
491 fs::create_dir_all(vst3_lib_path.parent().unwrap())
492 .context("Could not create VST3 bundle directory")?;
493 util::reflink_or_combine(lib_paths, &vst3_lib_path, compilation_target)
494 .context("Could not create VST3 bundle")?;
495
496 let vst3_bundle_home = vst3_lib_path
497 .parent()
498 .unwrap()
499 .parent()
500 .unwrap()
501 .parent()
502 .unwrap();
503 maybe_create_macos_bundle_metadata(
504 package,
505 &bundle_name,
506 vst3_bundle_home,
507 compilation_target,
508 BundleType::Plugin,
509 )?;
510 maybe_codesign(vst3_bundle_home, compilation_target);
511
512 eprintln!("Created a VST3 bundle at '{}'", vst3_bundle_home.display());
513 }
514 if !bundled_plugin {
515 eprintln!("Not creating any plugin bundles because the package does not export any plugins")
516 }
517
518 Ok(())
519}
520
521pub fn list_known_packages() -> Result<()> {
524 if let Some(config) = load_bundler_config()? {
525 for package in config.keys() {
526 println!("{package}");
527 }
528 }
529
530 Ok(())
531}
532
533fn load_bundler_config() -> Result<Option<BundlerConfig>> {
536 let bundler_config_path = Path::new("bundler.toml");
538 if !bundler_config_path.exists() {
539 return Ok(None);
540 }
541
542 let result = toml::from_str(
543 &fs::read_to_string(bundler_config_path)
544 .with_context(|| format!("Could not read '{}'", bundler_config_path.display()))?,
545 )
546 .with_context(|| format!("Could not parse '{}'", bundler_config_path.display()))?;
547
548 Ok(Some(result))
549}
550
551fn split_bundle_args(
555 args: impl Iterator<Item = String>,
556 usage_string: &str,
557) -> Result<(Vec<String>, Vec<String>)> {
558 let mut args = args.peekable();
559 let mut packages = Vec::new();
560 if args.peek().map(|s| s.as_str()) == Some("-p") {
561 while args.peek().map(|s| s.as_str()) == Some("-p") {
562 packages.push(
563 args.nth(1)
564 .with_context(|| format!("Missing package name after -p\n\n{usage_string}"))?,
565 );
566 }
567 } else {
568 packages.push(
569 args.next()
570 .with_context(|| format!("Missing package name\n\n{usage_string}"))?,
571 );
572 };
573 let other_args: Vec<_> = args.collect();
574
575 Ok((packages, other_args))
576}
577
578fn compilation_target(cross_compile_target: Option<&str>) -> Result<CompilationTarget> {
581 match cross_compile_target {
582 Some("i686-unknown-linux-gnu") => Ok(CompilationTarget::Linux(Architecture::X86)),
583 Some("i686-apple-darwin") => Ok(CompilationTarget::MacOS(Architecture::X86)),
584 Some("i686-pc-windows-gnu") | Some("i686-pc-windows-msvc") => {
585 Ok(CompilationTarget::Windows(Architecture::X86))
586 }
587 Some("x86_64-unknown-linux-gnu") => Ok(CompilationTarget::Linux(Architecture::X86_64)),
588 Some("x86_64-apple-darwin") => Ok(CompilationTarget::MacOS(Architecture::X86_64)),
589 Some("x86_64-pc-windows-gnu") | Some("x86_64-pc-windows-msvc") => {
590 Ok(CompilationTarget::Windows(Architecture::X86_64))
591 }
592 Some("aarch64-unknown-linux-gnu") => Ok(CompilationTarget::Linux(Architecture::AArch64)),
593 Some("aarch64-apple-darwin") => Ok(CompilationTarget::MacOS(Architecture::AArch64)),
594 Some("aarch64-pc-windows-gnu") | Some("aarch64-pc-windows-msvc") => {
595 Ok(CompilationTarget::Windows(Architecture::AArch64))
596 }
597 Some(target) => anyhow::bail!("Unhandled cross-compilation target: {}", target),
598 None => {
599 #[cfg(target_arch = "x86")]
600 let architecture = Architecture::X86;
601 #[cfg(target_arch = "x86_64")]
602 let architecture = Architecture::X86_64;
603 #[cfg(target_arch = "aarch64")]
604 let architecture = Architecture::AArch64;
605 #[cfg(target_arch = "riscv64")]
606 let architecture = Architecture::RISCV64;
607
608 #[cfg(all(target_family = "unix", not(target_os = "macos")))]
609 return Ok(CompilationTarget::Linux(architecture));
610 #[cfg(target_os = "macos")]
611 return Ok(CompilationTarget::MacOS(architecture));
612 #[cfg(target_os = "windows")]
613 return Ok(CompilationTarget::Windows(architecture));
614 }
615 }
616}
617
618fn bundle_home(target_directory: &Path) -> PathBuf {
620 target_directory.join("bundled")
621}
622
623fn target_base(target_directory: &Path, cross_compile_target: Option<&str>) -> Result<PathBuf> {
626 match cross_compile_target {
627 Some(target) => Ok(target_directory.join(target)),
629 None => Ok(target_directory.to_owned()),
630 }
631}
632
633fn binary_basename(package: &str, target: CompilationTarget) -> String {
635 let bin_name = package.replace('-', "_");
637
638 match target {
639 CompilationTarget::Linux(_)
640 | CompilationTarget::MacOS(_)
641 | CompilationTarget::MacOSUniversal => bin_name,
642 CompilationTarget::Windows(_) => format!("{bin_name}.exe"),
643 }
644}
645
646fn library_basename(package: &str, target: CompilationTarget) -> String {
648 let lib_name = package.replace('-', "_");
650
651 match target {
652 CompilationTarget::Linux(_) => format!("lib{lib_name}.so"),
653 CompilationTarget::MacOS(_) | CompilationTarget::MacOSUniversal => {
654 format!("lib{lib_name}.dylib")
655 }
656 CompilationTarget::Windows(_) => format!("{lib_name}.dll"),
657 }
658}
659
660fn standalone_bundle_binary_name(package: &str, target: CompilationTarget) -> String {
662 match target {
663 CompilationTarget::Linux(_) => package.to_owned(),
664 CompilationTarget::MacOS(_) | CompilationTarget::MacOSUniversal => {
665 format!("{package}.app/Contents/MacOS/{package}")
666 }
667 CompilationTarget::Windows(_) => format!("{package}.exe"),
668 }
669}
670
671fn clap_bundle_library_name(package: &str, target: CompilationTarget) -> String {
674 match target {
675 CompilationTarget::Linux(_) | CompilationTarget::Windows(_) => format!("{package}.clap"),
676 CompilationTarget::MacOS(_) | CompilationTarget::MacOSUniversal => {
677 format!("{package}.clap/Contents/MacOS/{package}")
678 }
679 }
680}
681
682fn vst2_bundle_library_name(package: &str, target: CompilationTarget) -> String {
685 match target {
686 CompilationTarget::Linux(_) => format!("{package}.so"),
687 CompilationTarget::MacOS(_) | CompilationTarget::MacOSUniversal => {
688 format!("{package}.vst/Contents/MacOS/{package}")
689 }
690 CompilationTarget::Windows(_) => format!("{package}.dll"),
691 }
692}
693
694fn vst3_bundle_library_name(package: &str, target: CompilationTarget) -> String {
699 match target {
700 CompilationTarget::Linux(Architecture::X86) => {
701 format!("{package}.vst3/Contents/i386-linux/{package}.so")
702 }
703 CompilationTarget::Linux(Architecture::X86_64) => {
704 format!("{package}.vst3/Contents/x86_64-linux/{package}.so")
705 }
706 CompilationTarget::Linux(Architecture::RISCV64) => {
707 format!("{package}.vst3/Contents/riscv64-linux/{package}.so")
708 }
709 CompilationTarget::Linux(Architecture::AArch64) => {
710 format!("{package}.vst3/Contents/aarch64-linux/{package}.so")
711 }
712 CompilationTarget::MacOS(_) | CompilationTarget::MacOSUniversal => {
713 format!("{package}.vst3/Contents/MacOS/{package}")
714 }
715 CompilationTarget::Windows(Architecture::X86) => {
716 format!("{package}.vst3/Contents/x86-win/{package}.vst3")
717 }
718 CompilationTarget::Windows(Architecture::X86_64) => {
719 format!("{package}.vst3/Contents/x86_64-win/{package}.vst3")
720 }
721 CompilationTarget::Windows(Architecture::AArch64) => {
722 format!("{package}.vst3/Contents/arm_64-win/{package}.vst3")
723 }
724 CompilationTarget::Windows(Architecture::RISCV64) => {
725 panic!("riscv64 are not supported by windows currently!")
726 }
727 }
728}
729
730pub fn maybe_create_macos_bundle_metadata(
735 package: &str,
736 display_name: &str,
737 bundle_home: &Path,
738 target: CompilationTarget,
739 bundle_type: BundleType,
740) -> Result<()> {
741 if !matches!(
742 target,
743 CompilationTarget::MacOS(_) | CompilationTarget::MacOSUniversal
744 ) {
745 return Ok(());
746 }
747
748 let package_type = match bundle_type {
749 BundleType::Plugin => "BNDL",
750 BundleType::Binary => "APPL",
751 };
752
753 fs::write(
756 bundle_home.join("Contents").join("PkgInfo"),
757 format!("{package_type}????"),
758 )
759 .context("Could not create PkgInfo file")?;
760 fs::write(
761 bundle_home.join("Contents").join("Info.plist"),
762 format!(r#"<?xml version="1.0" encoding="UTF-8"?>
763
764<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
765<plist>
766 <dict>
767 <key>CFBundleExecutable</key>
768 <string>{display_name}</string>
769 <key>CFBundleIconFile</key>
770 <string></string>
771 <key>CFBundleIdentifier</key>
772 <string>com.nice-plug.{package}</string>
773 <key>CFBundleName</key>
774 <string>{display_name}</string>
775 <key>CFBundleDisplayName</key>
776 <string>{display_name}</string>
777 <key>CFBundlePackageType</key>
778 <string>{package_type}</string>
779 <key>CFBundleSignature</key>
780 <string>????</string>
781 <key>CFBundleShortVersionString</key>
782 <string>1.0.0</string>
783 <key>CFBundleVersion</key>
784 <string>1.0.0</string>
785 <key>NSHumanReadableCopyright</key>
786 <string></string>
787 <key>NSHighResolutionCapable</key>
788 <true/>
789 </dict>
790</plist>
791"#),
792 )
793 .context("Could not create Info.plist file")?;
794
795 Ok(())
796}
797
798pub fn maybe_codesign(bundle_home: &Path, target: CompilationTarget) {
804 if !matches!(
805 target,
806 CompilationTarget::MacOS(_) | CompilationTarget::MacOSUniversal
807 ) {
808 return;
809 }
810
811 let success = Command::new("codesign")
812 .arg("-f")
813 .arg("-s")
814 .arg("-")
815 .arg(bundle_home)
816 .status()
817 .is_ok();
818 if !success {
819 eprintln!(
820 "WARNING: Could not self-sign '{}', it may fail to run depending on the environment",
821 bundle_home.display()
822 )
823 }
824}