[yabridgectl] Unify plugin handling

VST2 and VST3 plugins are now handled in the same way, reducing a bit of
duplication. We now also store the architecture for VST2 plugins so we
can show that in `yabridgectl status` later.
This commit is contained in:
Robbert van der Helm
2021-02-26 15:56:20 +01:00
parent ebd6c95ceb
commit d11302d6b5
2 changed files with 156 additions and 130 deletions
+95 -82
View File
@@ -24,7 +24,7 @@ use std::path::{Path, PathBuf};
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::config::{yabridge_vst3_home, Config, InstallationMethod, YabridgeFiles}; use crate::config::{yabridge_vst3_home, Config, InstallationMethod, YabridgeFiles};
use crate::files::{self, LibArchitecture, NativeFile}; use crate::files::{self, LibArchitecture, NativeFile, Plugin, Vst2Plugin};
use crate::utils; use crate::utils;
use crate::utils::{verify_path_setup, verify_wine_setup}; use crate::utils::{verify_path_setup, verify_wine_setup};
@@ -121,15 +121,15 @@ pub fn show_status(config: &Config) -> Result<()> {
// be added both with and without a trailing slash // be added both with and without a trailing slash
println!("\n{}", path.join("").display()); println!("\n{}", path.join("").display());
for (plugin, (status, vst3_module)) in search_results.installation_status() { for (plugin_path, (plugin, status)) in search_results.installation_status() {
let plugin_type = match vst3_module { let plugin_type = match plugin {
Some(module) => format!( Plugin::Vst2(_) => "VST2".cyan().to_string(),
Plugin::Vst3(module) => format!(
"{}, {}, {}", "{}, {}, {}",
"VST3".magenta(), "VST3".magenta(),
module.type_str(), module.type_str(),
module.architecture module.architecture
), ),
None => "VST2".cyan().to_string(),
}; };
let status_str = match status { let status_str = match status {
@@ -141,7 +141,10 @@ pub fn show_status(config: &Config) -> Result<()> {
println!( println!(
" {} :: {}, {}", " {} :: {}, {}",
plugin.strip_prefix(path).unwrap_or(&plugin).display(), plugin_path
.strip_prefix(path)
.unwrap_or(&plugin_path)
.display(),
plugin_type, plugin_type,
status_str status_str
); );
@@ -228,8 +231,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
// modules in `~/.vst3/yabridge`. // modules in `~/.vst3/yabridge`.
let mut yabridge_vst3_bundles: BTreeMap<PathBuf, BTreeSet<LibArchitecture>> = BTreeMap::new(); let mut yabridge_vst3_bundles: BTreeMap<PathBuf, BTreeSet<LibArchitecture>> = BTreeMap::new();
for (path, search_results) in results { for (path, search_results) in results {
num_installed += search_results.vst2_files.len(); num_installed += search_results.plugins.len();
num_installed += search_results.vst3_modules.len();
orphan_files.extend(search_results.vst2_orphans().into_iter().cloned()); orphan_files.extend(search_results.vst2_orphans().into_iter().cloned());
skipped_dll_files.extend(search_results.skipped_files); skipped_dll_files.extend(search_results.skipped_files);
@@ -239,44 +241,46 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
println!("{}", path.join("").display()); println!("{}", path.join("").display());
} }
// We'll set up the copies or symlinks for VST2 plugins for plugin in search_results.plugins {
for plugin in search_results.vst2_files { // If verbose mode is enabled we'll print the path to the plugin after setting it up
let target_path = plugin.with_extension("so"); let plugin_path: PathBuf = match plugin {
// We'll set up the copies or symlinks for VST2 plugins
Plugin::Vst2(Vst2Plugin {
path: plugin_path, ..
}) => {
let target_path = plugin_path.with_extension("so");
// Since we skip some files, we'll also keep track of how many new file we've actually // Since we skip some files, we'll also keep track of how many new file we've
// set up // actually set up
if install_file( if install_file(
options.force, options.force,
config.method, config.method,
&files.libyabridge_vst2, &files.libyabridge_vst2,
Some(libyabridge_vst2_hash), Some(libyabridge_vst2_hash),
&target_path, &target_path,
)? { )? {
num_new += 1; num_new += 1;
} }
if options.verbose { plugin_path.clone()
println!( }
" {}", // And then create merged bundles for the VST3 plugins:
plugin.strip_prefix(path).unwrap_or(&plugin).display() // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles
); Plugin::Vst3(module) => {
} // Only set up VST3 plugins when yabridge has been compiled with VST3 support
} if libyabridge_vst3_hash.is_none() {
continue;
}
// And then create merged bundles for the VST3 plugins: // 32-bit and 64-bit versions of the plugin cna live inside of the same
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles // bundle), and show a warning if we come across any duplicates.
if let Some(libyabridge_vst3_hash) = libyabridge_vst3_hash { let already_installed_architectures = yabridge_vst3_bundles
for module in search_results.vst3_modules { .entry(module.target_bundle_home())
// Check if we already set up the same architecture version of the plugin (since .or_insert_with(BTreeSet::new);
// 32-bit and 64-bit versions of the plugin cna live inside of the same bundle), and if !already_installed_architectures.insert(module.architecture) {
// show a warning if we come across any duplicates. eprintln!(
let already_installed_architectures = yabridge_vst3_bundles "{}",
.entry(module.target_bundle_home()) utils::wrap(&format!(
.or_insert_with(BTreeSet::new);
if !already_installed_architectures.insert(module.architecture) {
eprintln!(
"{}",
utils::wrap(&format!(
"{}: The {} version of '{}' has already been provided by another Wine \ "{}: The {} version of '{}' has already been provided by another Wine \
prefix, skipping '{}'\n", prefix, skipping '{}'\n",
"WARNING".red(), "WARNING".red(),
@@ -284,54 +288,63 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
module.target_bundle_home().display(), module.target_bundle_home().display(),
module.original_module_path().display(), module.original_module_path().display(),
)) ))
); );
continue; continue;
} }
// We're building a merged VST3 bundle containing both a copy or symlink to // We're building a merged VST3 bundle containing both a copy or symlink to
// `libyabridge-vst3.so` and the Windows VST3 plugin // `libyabridge-vst3.so` and the Windows VST3 plugin
let native_module_path = module.target_native_module_path(); let native_module_path = module.target_native_module_path();
utils::create_dir_all(native_module_path.parent().unwrap())?; utils::create_dir_all(native_module_path.parent().unwrap())?;
if install_file( if install_file(
options.force, options.force,
config.method, config.method,
files.libyabridge_vst3.as_ref().unwrap(), files.libyabridge_vst3.as_ref().unwrap(),
Some(libyabridge_vst3_hash), libyabridge_vst3_hash,
&native_module_path, &native_module_path,
)? { )? {
num_new += 1; num_new += 1;
} }
// We'll then symlink the Windows VST3 module to that bundle to create a merged // We'll then symlink the Windows VST3 module to that bundle to create a merged
// bundle: https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles // bundle: https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles
let windows_module_path = module.target_windows_module_path(); let windows_module_path = module.target_windows_module_path();
utils::create_dir_all(windows_module_path.parent().unwrap())?; utils::create_dir_all(windows_module_path.parent().unwrap())?;
install_file(
true,
InstallationMethod::Symlink,
&module.original_module_path(),
None,
&windows_module_path,
)?;
// If `module` is a bundle, then it may contain a `Resources` directory with
// screenshots and documentation
// TODO: Also symlink presets, but this is a bit more involved. See
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#win7preset
if let Some(original_resources_dir) = module.original_resources_dir() {
install_file( install_file(
false, true,
InstallationMethod::Symlink, InstallationMethod::Symlink,
&original_resources_dir, &module.original_module_path(),
None, None,
&module.target_resources_dir(), &windows_module_path,
)?; )?;
}
if options.verbose { // If `module` is a bundle, then it may contain a `Resources` directory with
println!(" {}", module.original_path().display()); // screenshots and documentation
// TODO: Also symlink presets, but this is a bit more involved. See
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#win7preset
if let Some(original_resources_dir) = module.original_resources_dir() {
install_file(
false,
InstallationMethod::Symlink,
&original_resources_dir,
None,
&module.target_resources_dir(),
)?;
}
module.original_path().to_path_buf()
} }
};
if options.verbose {
println!(
" {}",
plugin_path
.strip_prefix(path)
.unwrap_or(&plugin_path)
.display()
);
} }
} }
+61 -48
View File
@@ -32,14 +32,12 @@ use crate::utils::get_file_type;
/// Stores the results from searching through a directory. We'll search for Windows VST2 plugin /// Stores the results from searching through a directory. We'll search for Windows VST2 plugin
/// `.dll` files, Windows VST3 plugin modules, and native Linux `.so` files inside of a directory. /// `.dll` files, Windows VST3 plugin modules, and native Linux `.so` files inside of a directory.
/// These `.so` files are kept track of so we can report the current installation status of VST2 /// These `.so` files are kept track of so we can report the current installation status of VST2
/// plugins and to be able to prune orphan files. Since VST3 plugins have to be instaleld in /// plugins and to be able to prune orphan files. Since VST3 plugins have to be installed in
/// `~/.vst3`, these orphan files are only relevant for VST2 plugins. /// `~/.vst3`, these orphan files are only relevant for VST2 plugins.
#[derive(Debug)] #[derive(Debug)]
pub struct SearchResults { pub struct SearchResults {
/// Absolute paths to the found VST2 `.dll` files. /// The plugins found during the search. This contains both VST2 plugins and VST3 modules.
pub vst2_files: Vec<PathBuf>, pub plugins: Vec<Plugin>,
/// Absolute paths to found VST3 modules. Either legacy `.vst3` DLL files or VST 3.6.10 bundles.
pub vst3_modules: Vec<Vst3Module>,
/// `.dll` files skipped over during the search. Used for printing statistics and shown when /// `.dll` files skipped over during the search. Used for printing statistics and shown when
/// running `yabridgectl sync --verbose`. /// running `yabridgectl sync --verbose`.
pub skipped_files: Vec<PathBuf>, pub skipped_files: Vec<PathBuf>,
@@ -82,10 +80,26 @@ impl NativeFile {
} }
} }
/// VST3 modules we found during a serach. /// A plugin as found during the search. This can be either a VST2 plugin or a VST3 module.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Plugin {
Vst2(Vst2Plugin),
Vst3(Vst3Module),
}
/// VST2 plugins we found during a search along with their architecture.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Vst2Plugin {
/// The absolute path to the VST2 plugin `.dll` file.
pub path: PathBuf,
/// The architecture of the VST2 plugin.
pub architecture: LibArchitecture,
}
/// VST3 modules we found during a search.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Vst3Module { pub struct Vst3Module {
/// The actual VST3 module and its type. /// The absolute path to the actual VST3 module and its type.
pub module: Vst3ModuleType, pub module: Vst3ModuleType,
/// The architecture of the VST3 module. /// The architecture of the VST3 module.
pub architecture: LibArchitecture, pub architecture: LibArchitecture,
@@ -251,45 +265,38 @@ impl LibArchitecture {
} }
impl SearchResults { impl SearchResults {
/// For every found VST2 plugin and VST3 module, find the associated copy or symlink of /// Create a map out of all found plugins based on their file path that contains both a
/// `libyabridge-{vst2,vst3}.so`. The returned hashmap will contain a `None` value for plugins /// reference to the plugin (so we can print information about it) and the current installation
/// that have not yet been set up. For VST3 modules we'll also return the actual module since /// status. The installation status will be `None` if the plugin has not yet been set up.
/// this contains a lot of additional information we might want to print during `yabridgectl pub fn installation_status(&self) -> BTreeMap<PathBuf, (&Plugin, Option<NativeFile>)> {
/// status`.
pub fn installation_status(
&self,
) -> BTreeMap<PathBuf, (Option<NativeFile>, Option<&Vst3Module>)> {
let so_files: HashMap<&Path, &NativeFile> = self let so_files: HashMap<&Path, &NativeFile> = self
.so_files .so_files
.iter() .iter()
.map(|file| (file.path(), file)) .map(|file| (file.path(), file))
.collect(); .collect();
// Do this for the VST2 plugins self.plugins
let mut installation_status: BTreeMap<PathBuf, (_, _)> = self
.vst2_files
.iter() .iter()
.map( .map(|plugin| match plugin {
|path| match so_files.get(path.with_extension("so").as_path()) { Plugin::Vst2(Vst2Plugin { path, .. }) => {
Some(&file_type) => (path.clone(), (Some(file_type.clone()), None)), // For VST2 plugins we'll just look at the similarly named `.so` file right next
None => (path.clone(), (None, None)), // to the plugin `.dll` file.
}, match so_files.get(path.with_extension("so").as_path()) {
) Some(&file_type) => (path.clone(), (plugin, Some(file_type.clone()))),
.collect(); None => (path.clone(), (plugin, None)),
}
// And for VST3 modules. We have not stored the paths to the corresponding `.so` files yet }
// because they are not in any of the directories we're indexing. // We have not stored the paths to the corresponding `.so` files yet for VST3
installation_status.extend(self.vst3_modules.iter().map(|module| { // modules because they are not in any of the directories we're indexing
( Plugin::Vst3(vst3_module) => (
module.original_path().to_owned(), vst3_module.original_path().to_owned(),
( (
get_file_type(module.target_native_module_path()), plugin,
Some(module), get_file_type(vst3_module.target_native_module_path()),
),
), ),
) })
})); .collect()
installation_status
} }
/// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file. /// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file.
@@ -303,8 +310,10 @@ impl SearchResults {
.map(|file_type| (file_type.path(), file_type)) .map(|file_type| (file_type.path(), file_type))
.collect(); .collect();
for vst2_path in &self.vst2_files { for plugin in &self.plugins {
orphans.remove(vst2_path.with_extension("so").as_path()); if let Plugin::Vst2(Vst2Plugin { path, .. }) = plugin {
orphans.remove(path.with_extension("so").as_path());
}
} }
orphans.values().cloned().collect() orphans.values().cloned().collect()
@@ -389,12 +398,18 @@ impl SearchIndex {
// We'll have to figure out which `.dll` files are VST2 plugins and which should be skipped // We'll have to figure out which `.dll` files are VST2 plugins and which should be skipped
// by checking whether the file contains one of the VST2 entry point functions. This vector // by checking whether the file contains one of the VST2 entry point functions. This vector
// will contain an `Err(path)` if `path` was not a valid VST2 plugin. // will contain an `Err(path)` if `path` was not a valid VST2 plugin.
let is_vst2_plugin: Vec<Result<PathBuf, PathBuf>> = self let is_vst2_plugin: Vec<Result<Vst2Plugin, PathBuf>> = self
.dll_files .dll_files
.into_par_iter() .into_par_iter()
.map(|path| { .map(|path| {
let architecture = if DLL32_AUTOMATON.is_match(pe32_info(&path)?) {
LibArchitecture::Dll32
} else {
LibArchitecture::Dll64
};
if VST2_AUTOMATON.is_match(exported_functions(&path)?) { if VST2_AUTOMATON.is_match(exported_functions(&path)?) {
Ok(Ok(path)) Ok(Ok(Vst2Plugin { path, architecture }))
} else { } else {
Ok(Err(path)) Ok(Err(path))
} }
@@ -481,27 +496,25 @@ impl SearchIndex {
}) })
.collect::<Result<_>>()?; .collect::<Result<_>>()?;
let mut plugins: Vec<Plugin> = Vec::new();
let mut skipped_files: Vec<PathBuf> = Vec::new(); let mut skipped_files: Vec<PathBuf> = Vec::new();
let mut vst2_files: Vec<PathBuf> = Vec::new();
for dandidate in is_vst2_plugin { for dandidate in is_vst2_plugin {
match dandidate { match dandidate {
Ok(path) => vst2_files.push(path), Ok(plugin) => plugins.push(Plugin::Vst2(plugin)),
Err(path) => skipped_files.push(path), Err(path) => skipped_files.push(path),
} }
} }
let mut vst3_modules: Vec<Vst3Module> = Vec::new();
for candidate in is_vst3_module { for candidate in is_vst3_module {
match candidate { match candidate {
Ok(module) => vst3_modules.push(module), Ok(module) => plugins.push(Plugin::Vst3(module)),
Err(path) => skipped_files.push(path), Err(path) => skipped_files.push(path),
} }
} }
Ok(SearchResults { Ok(SearchResults {
vst2_files, plugins,
vst3_modules,
skipped_files, skipped_files,
so_files: self.so_files, so_files: self.so_files,
}) })