use crate::util::fs_ctx;
use crate::{Config, PluginDef, Res, cargo_build, release_lib};
use std::path::Path;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BuildFormat {
Clap,
Vst3,
Vst2,
Lv2,
Au2,
Aax,
}
impl BuildFormat {
fn feature(self) -> &'static str {
match self {
BuildFormat::Clap => "clap",
BuildFormat::Vst3 => "vst3",
BuildFormat::Vst2 => "vst2",
BuildFormat::Lv2 => "lv2",
BuildFormat::Au2 => "au",
BuildFormat::Aax => "aax",
}
}
fn label(self) -> &'static str {
match self {
BuildFormat::Clap => "CLAP",
BuildFormat::Vst3 => "VST3",
BuildFormat::Vst2 => "VST2",
BuildFormat::Lv2 => "LV2",
BuildFormat::Au2 => "AU v2",
BuildFormat::Aax => "AAX",
}
}
fn dylib_suffix(self) -> &'static str {
match self {
BuildFormat::Clap => "_clap",
BuildFormat::Vst3 => "_vst3",
BuildFormat::Vst2 => "_vst2",
BuildFormat::Lv2 => "_lv2",
BuildFormat::Au2 => "_au",
BuildFormat::Aax => "_aax",
}
}
fn name_override_env(self) -> &'static str {
match self {
BuildFormat::Clap => "TRUCE_CLAP_NAME_OVERRIDE",
BuildFormat::Vst3 => "TRUCE_VST3_NAME_OVERRIDE",
BuildFormat::Vst2 => "TRUCE_VST2_NAME_OVERRIDE",
BuildFormat::Lv2 => "TRUCE_LV2_NAME_OVERRIDE",
BuildFormat::Au2 => "TRUCE_AU_NAME_OVERRIDE",
BuildFormat::Aax => "TRUCE_AAX_NAME_OVERRIDE",
}
}
fn name_override(self, p: &PluginDef) -> Option<&str> {
match self {
BuildFormat::Clap => p.clap_name.as_deref(),
BuildFormat::Vst3 => p.vst3_name.as_deref(),
BuildFormat::Vst2 => p.vst2_name.as_deref(),
BuildFormat::Lv2 => p.lv2_name.as_deref(),
BuildFormat::Au2 => p.au_name.as_deref(),
BuildFormat::Aax => p.aax_name.as_deref(),
}
}
}
fn aax_skip_reason(config: &Config) -> Option<String> {
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
let _ = config;
Some("AAX: not supported on this platform. Use macOS or Windows to build AAX.".to_string())
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
if crate::resolve_aax_sdk_path(config).is_some() {
return None;
}
let hint = if cfg!(target_os = "windows") {
"[windows].aax_sdk_path"
} else {
"[macos].aax_sdk_path"
};
Some(format!(
"AAX: SDK not configured. Set {hint} in truce.toml or the AAX_SDK_PATH env var."
))
}
}
pub(crate) fn build_format_dylibs(
format: BuildFormat,
plugins: &[&PluginDef],
extra_features: &[&str],
config: &Config,
root: &Path,
deployment_target: &str,
) -> Res {
match format {
BuildFormat::Au2 => {
#[cfg(not(target_os = "macos"))]
{
crate::log_skip(
"AU v2: not supported on this platform. Audio Unit is macOS-only.".to_string(),
);
return Ok(());
}
}
BuildFormat::Aax => {
if let Some(reason) = aax_skip_reason(config) {
crate::log_skip(reason);
return Ok(());
}
}
_ => {}
}
if extra_features.is_empty() {
crate::vprintln!("Building {}...", format.label());
} else {
let extras = extra_features.join(" + ");
crate::vprintln!("Building {} ({extras})...", format.label());
}
let mut format_features: Vec<&str> = vec![format.feature()];
format_features.extend_from_slice(extra_features);
let combined = format_features.join(",");
for p in plugins {
let mut env_pairs: Vec<(&str, &str)> = Vec::new();
if format == BuildFormat::Au2 {
env_pairs.push(("TRUCE_AU_VERSION", "2"));
env_pairs.push(("TRUCE_AU_PLUGIN_ID", &p.bundle_id));
}
if let Some(n) = format.name_override(p) {
env_pairs.push((format.name_override_env(), n));
}
cargo_build(
&env_pairs,
&[
"-p",
&p.crate_name,
"--no-default-features",
"--features",
&combined,
],
deployment_target,
)?;
let src = release_lib(root, &p.dylib_stem());
let dst = release_lib(
root,
&format!("{}{}", p.dylib_stem(), format.dylib_suffix()),
);
if src.exists() {
fs_ctx::copy(&src, &dst)?;
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
if format == BuildFormat::Aax {
crate::commands::install::aax::emit_aax_bundle(root, p, config, false)?;
}
}
Ok(())
}
pub(crate) fn build_logic_dylibs(
plugins: &[&PluginDef],
logic_profile: &str,
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))] deployment_target: &str,
) -> Res {
use std::process::Command;
for p in plugins {
crate::vprintln!(
"Building {} logic dylib for {}...",
logic_profile,
p.crate_name
);
let mut cmd = Command::new("cargo");
cmd.arg("build").arg("-p").arg(&p.crate_name);
match logic_profile {
"debug" => {} "release" => {
cmd.arg("--release");
}
other => {
cmd.arg("--profile").arg(other);
}
}
#[cfg(target_os = "macos")]
cmd.env("MACOSX_DEPLOYMENT_TARGET", deployment_target);
let status = cmd.status()?;
if !status.success() {
return Err(format!("{logic_profile} build of {} failed", p.crate_name).into());
}
}
Ok(())
}