#![cfg(target_os = "macos")]
use std::path::{Path, PathBuf};
use std::process::Command;
use super::build::MacArch;
pub(crate) const CLAP_EXPORTS: &[&str] = &["_clap_entry"];
pub(crate) const VST3_EXPORTS: &[&str] = &[
"_GetPluginFactory",
"_BundleEntry",
"_bundleEntry",
"_BundleExit",
"_bundleExit",
];
pub(crate) const VST2_EXPORTS: &[&str] = &["_VSTPluginMain", "_main_macho"];
pub(crate) fn missing_staticlib_error(staticlib_path: &Path) -> String {
format!(
"macOS bundle link needs a Rust staticlib at\n \
{path}\n\
but cargo didn't emit one.\n\
\n\
Starting with truce 0.44.0, macOS bundle formats (CLAP / VST3 / VST2) \
are linked from `lib<stem>.a` via `clang -bundle`. Plugins scaffolded \
before 0.44.0 only declared `[\"cdylib\", \"rlib\"]` and need to add \
`\"staticlib\"` to the `crate-type` array in their plugin crate's \
`Cargo.toml`.\n\
\n\
Exact change:\n\
\n\
# before\n\
[lib]\n\
crate-type = [\"cdylib\", \"rlib\"]\n\
\n\
# after\n\
[lib]\n\
crate-type = [\"cdylib\", \"staticlib\", \"rlib\"]\n\
\n\
Then re-run the failing command.",
path = staticlib_path.display(),
)
}
const MACOS_PLUGIN_FRAMEWORKS: &[&str] = &[
"AppKit",
"Foundation",
"CoreFoundation",
"QuartzCore",
"Metal",
"AudioToolbox",
"AVFAudio",
"CoreAudio",
"CoreMIDI",
"CoreGraphics",
];
pub(crate) fn link_macos_bundle(
staticlibs: &[(MacArch, PathBuf)],
exports: &[&str],
deployment_target: &str,
out_bundle_bin: &Path,
) -> crate::Res {
if staticlibs.is_empty() {
return Err("link_macos_bundle: no input static archives".into());
}
for (_, p) in staticlibs {
if !p.exists() {
return Err(
format!("link_macos_bundle: missing static archive {}", p.display()).into(),
);
}
}
if let Some(parent) = out_bundle_bin.parent() {
std::fs::create_dir_all(parent)?;
}
if staticlibs.len() == 1 {
let (arch, staticlib) = &staticlibs[0];
return clang_bundle_single(*arch, staticlib, exports, deployment_target, out_bundle_bin);
}
let mut per_arch_outputs: Vec<PathBuf> = Vec::with_capacity(staticlibs.len());
for (arch, staticlib) in staticlibs {
let slice_out = out_bundle_bin.with_extension(format!("{}-slice", arch.triple()));
clang_bundle_single(*arch, staticlib, exports, deployment_target, &slice_out)?;
per_arch_outputs.push(slice_out);
}
super::build::lipo_into(&per_arch_outputs, out_bundle_bin)?;
for slice in &per_arch_outputs {
let _ = std::fs::remove_file(slice);
}
Ok(())
}
fn clang_bundle_single(
arch: MacArch,
staticlib: &Path,
exports: &[&str],
deployment_target: &str,
out: &Path,
) -> crate::Res {
let arch_flag = match arch {
MacArch::Arm64 => "arm64",
MacArch::X86_64 => "x86_64",
};
let mut cmd = Command::new("clang");
cmd.args([
"-bundle",
"-arch",
arch_flag,
&format!("-mmacosx-version-min={deployment_target}"),
"-Wl,-undefined,dynamic_lookup",
"-Wl,-all_load",
]);
for framework in MACOS_PLUGIN_FRAMEWORKS {
cmd.args(["-framework", framework]);
}
cmd.arg("-lc++");
for sym in exports {
cmd.arg(format!("-Wl,-exported_symbol,{sym}"));
}
cmd.arg(staticlib);
cmd.arg("-o").arg(out);
let output = cmd.output().map_err(|e| -> crate::CargoTruceError {
format!("invoking clang for bundle link: {e}").into()
})?;
if !output.status.success() {
return Err(format!(
"clang -bundle failed for {} ({arch_flag}):\n{}",
staticlib.display(),
String::from_utf8_lossy(&output.stderr),
)
.into());
}
Ok(())
}