[yabridgectl] Set up in ~/.vst/yabridge by default

Instead of creating `.so` files next to the VST2 plugin's `.dll` files.
This behavior is still available as an option. #123
This commit is contained in:
Robbert van der Helm
2022-04-17 20:27:47 +02:00
parent 5ea5968f96
commit 3970bc7f52
5 changed files with 249 additions and 57 deletions
+31 -6
View File
@@ -8,6 +8,13 @@ Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### TODOs
- The readme documentation has not yet been updated to reflect the changes on
the master branch. The main thing that would require documentation changes is
the default VST2 plugin handling. Those are now set up in `~/.vst/yabridge` by
default, with an option to revert back to the old inline behavior.
### Added
- Yabridge 4.0 introduces a completely new way to load plugins that allows
@@ -57,8 +64,8 @@ Versioning](https://semver.org/spec/v2.0.0.html).
- Removed the `with-static-boost` build option since there's no longer a
dependency on Boost.Filesystem.
- Removed the `yabridge-group*` binaries as they are now part of the
`yabridge-host*` binaries. This saves precious megabytes.
- Removed the `yabridge-group` binaries as they are now part of the
`yabridge-host` binaries. This saves precious megabytes.
### Fixed
@@ -69,11 +76,27 @@ Versioning](https://semver.org/spec/v2.0.0.html).
### yabridgectl
- VST2 plugins are now set up in `~/.vst/yabridge` by default. This means that
you no longer have to add any directory search locations in your DAW. The
downside is that it's no longer possible for two plugin directories (perhaps
in different Wine prefixes) to provide the same plugin file. Like with
yabridgectl's VST3 handling, the subdirectory structure within the plugin
directory is preserved. You can use `yabridgectl set --vst2-location=inline`
to revert back to the old behavior of setting the plugins up right next to the
VST2 plugin `.dll` files.
If you were using a `yabridge.toml` configuration file, then you will now need
to place that file in `~/.vst/yabridge` instead.
- As mentioned above, yabridgectl will now use the new chainloading libraries
when setting up plugins. This means that once you've ran `yabridgectl sync`
once after updating to yabridge 4.0, yabridge can now be updated without
needing to rerun `yabridgectl sync`. This is particularly useful when using a
distro packaged version of yabridge.
- The VST3 subdirectory detection is more robust and can now handle arbitrary
directories, not just directories that are called `VST3`. This, of course,
should not be needed.
- The previously deprecated symlink installation method has now been removed
from yabridgectl, along with the `yabridgectl set --method` option.
- `yabridgectl status` now lists the architecture of
@@ -87,12 +110,14 @@ Versioning](https://semver.org/spec/v2.0.0.html).
- The `yabridge-group` binaries no longer exist as they are now part of the
`yabridge-host` binaries.
- The `with-bitbridge` build option has been renamed to just `bitbridge`.
- Completely removed the dependency on all Boost libraries.
- Added a dependency on the headers-only
- Both runtime and compile time dependencies on the Boost libraries have been
removed.
- There's a new dependency on the headers-only
[`ghc::filesystem`](https://github.com/gulrak/filesystem) library to replace
Boost.Filesystem.
Boost.Filesystem. A Meson wrap is included as a fallback for a distro package.
- Added a dependency on the headers-only [Asio](http://think-async.com/Asio/)
library to replace Boost.Asio.
library to replace Boost.Asio. A Meson wrap is included as a fallback for a
distro package.
- Fixed a deprecation warning in the Meson build, causing the minimum supported
Meson version to be bumped up to **Meson 0.56** from 0.55.
+2 -2
View File
@@ -41,8 +41,8 @@ class HostProcess {
/**
* Return the full path to the host application in use. The host application
* is chosen depending on the architecture of the plugin's DLL file and on
* the hosting mode.
* is chosen depending on the architecture of the plugin's `.dll` file and
* on the hosting mode.
*/
virtual ghc::filesystem::path path() = 0;
+110 -17
View File
@@ -23,7 +23,9 @@ use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
use crate::config::{yabridge_vst3_home, Config, Vst2InstallationLocation, YabridgeFiles};
use crate::config::{
yabridge_vst2_home, yabridge_vst3_home, Config, Vst2InstallationLocation, YabridgeFiles,
};
use crate::files::{self, NativeFile, Plugin, Vst2Plugin};
use crate::utils::{self, get_file_type};
use crate::utils::{verify_path_setup, verify_wine_setup};
@@ -145,7 +147,7 @@ pub fn show_status(config: &Config) -> Result<()> {
println!("\n{}", path.join("").display());
for (plugin_path, (plugin, status)) in
search_results.installation_status(files.as_ref().ok())
search_results.installation_status(config, files.as_ref().ok())
{
let plugin_type = match plugin {
Plugin::Vst2(Vst2Plugin { architecture, .. }) => {
@@ -263,14 +265,23 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
// `.so` files and unused VST3 modules we found during scanning that didn't have a corresponding
// copy or symlink of `libyabridge-chainloader-vst2.so`
let mut orphan_files: Vec<NativeFile> = Vec::new();
// When using the centralized VST2 installation location in `~/.vst/yabridge` we'll want to
// track all unmanaged files in that directory and add them to the orphans list
let mut known_centralized_vst2_files: HashSet<PathBuf> = HashSet::new();
// Since VST3 bundles contain multiple files from multiple sources (native library files from
// yabridge, and symlinks to Windows VST3 modules or bundles), cleaning up orphan VST3 files is
// a bit more complicated. We want to clean both `.vst3` bundles that weren't used by anything
// during the syncing process, so we'll keep track of which VST3 files we touched per-bundle. We
// can then at the end remove all unkonwn bundles, and all unkonwn files within a bundle.
let mut known_vst3_files: HashMap<PathBuf, HashSet<PathBuf>> = HashMap::new();
let mut known_centralized_vst3_files: HashMap<PathBuf, HashSet<PathBuf>> = HashMap::new();
for (path, search_results) in results {
orphan_files.extend(search_results.vst2_orphans().into_iter().cloned());
// Orphan files in the centralized directories need to be detected separately
orphan_files.extend(
search_results
.vst2_inline_orphans(config)
.into_iter()
.cloned(),
);
skipped_dll_files.extend(search_results.skipped_files);
if options.verbose {
@@ -282,11 +293,60 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
for plugin in search_results.plugins {
// If verbose mode is enabled we'll print the path to the plugin after setting it up
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");
// VST2 plugins can be set up in either `~/.vst/yabridge` or inline with the
// plugin's `.dll` file
Plugin::Vst2(vst2_plugin) => {
match config.vst2_location {
Vst2InstallationLocation::Centralized => {
let target_native_plugin_path = vst2_plugin.centralized_native_target();
let target_windows_plugin_path =
vst2_plugin.centralized_windows_target();
let normalized_target_native_plugin_path =
utils::normalize_path(&target_native_plugin_path);
let mut is_new = known_centralized_vst2_files
.insert(target_native_plugin_path.clone());
is_new |= known_centralized_vst2_files
.insert(target_windows_plugin_path.clone());
if !is_new {
eprintln!(
"{}",
utils::wrap(&format!(
"{}: '{}' has already been provided by another Wine prefix or plugin directory, skipping it\n",
"WARNING".red(),
target_windows_plugin_path.display(),
))
);
continue;
}
// In the centralized mode we'll create a copy of
// `libyabridge-chainloader-vst2.so` to (a subdirectory of)
// `~/.vst/yabridge`, and then we'll symlink the Windows VST2 plugin
// `.dll` file right next to it
utils::create_dir_all(target_native_plugin_path.parent().unwrap())?;
if install_file(
options.force,
InstallationMethod::Copy,
&files.vst2_chainloader,
Some(vst2_chainloader_hash),
&target_native_plugin_path,
)? {
new_plugins.insert(normalized_target_native_plugin_path.clone());
}
managed_plugins.insert(normalized_target_native_plugin_path);
install_file(
true,
InstallationMethod::Symlink,
&vst2_plugin.path,
None,
&target_windows_plugin_path,
)?;
}
Vst2InstallationLocation::Inline => {
let target_path = vst2_plugin.inline_native_target();
let normalized_target_path = utils::normalize_path(&target_path);
// Since we skip some files, we'll also keep track of how many new file we've
@@ -301,8 +361,10 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
new_plugins.insert(normalized_target_path.clone());
}
managed_plugins.insert(normalized_target_path);
}
}
plugin_path.clone()
vst2_plugin.path.clone()
}
// And then create merged bundles for the VST3 plugins:
// https://developer.steinberg.help/display/VST/Plug-in+Format+Structure#PluginFormatStructure-MergedBundle
@@ -321,7 +383,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
// 32-bit and 64-bit versions of the plugin can live inside of the same bundle),
// but it's not possible to use the exact same plugin from multiple Wine
// prefixes at the same time so we'll warn when that happens
let managed_vst3_bundle_files = known_vst3_files
let managed_vst3_bundle_files = known_centralized_vst3_files
.entry(target_bundle_home.clone())
.or_insert_with(HashSet::new);
if managed_vst3_bundle_files.contains(&target_windows_module_path) {
@@ -329,7 +391,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
"{}",
utils::wrap(&format!(
"{}: The {} version of '{}' has already been provided by another Wine \
prefix, skipping '{}'\n",
prefix or plugin directory, skipping '{}'\n",
"WARNING".red(),
module.architecture,
module.target_bundle_home().display(),
@@ -420,10 +482,30 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
println!();
}
// We want to remove both unmanaged VST3 bundles in `~/.vst3/yabridge` as well as
// unmanged files within managed bundles. That's why we'll immediately filter out
// kown files within VST3 bundles.
// We've already kept track of orphan `.dll` files in the plugin directories, but now we need to
// do something similar for orphan files in `~/.vst/yabridge` and `~/.vst3/yabridge`. For VST3
// plugins we'll want to remove both unmanaged VST3 bundles in `~/.vst3/yabridge` as well as
// unmanged files within managed bundles. That's why we'll immediately filter out known files
// within VST3 bundles. For VST2 plugins we can simply treat any file in `~/.vst/yabridge` that
// we did not add to `known_centralized_vst2_files` as an orphan. We'll want to do this
// regardless of the VST2 installation location setting so switching between the two modes and
// then pruning works as expected.
// TODO: Move this elsewhere
let centralized_vst2_files = WalkDir::new(yabridge_vst2_home())
.follow_links(true)
.same_file_system(true)
.into_iter()
.filter_map(|e| e.ok())
.filter(|entry| !entry.file_type().is_dir())
.filter(|entry| {
matches!(
entry
.path()
.extension()
.and_then(|extension| extension.to_str()),
Some("dll" | "so")
)
});
let installed_vst3_bundles = WalkDir::new(yabridge_vst3_home())
.follow_links(true)
.same_file_system(true)
@@ -437,8 +519,16 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
.and_then(|extension| extension.to_str())
== Some("vst3")
});
orphan_files.extend(centralized_vst2_files.filter_map(|entry| {
if known_centralized_vst2_files.contains(entry.path()) {
None
} else {
get_file_type(entry.path().to_owned())
}
}));
for bundle in installed_vst3_bundles {
match known_vst3_files.get(bundle.path()) {
match known_centralized_vst3_files.get(bundle.path()) {
None => orphan_files.push(NativeFile::Directory(bundle.path().to_owned())),
Some(managed_vst3_bundle_files) => {
// Find orphan files and symlinks within this bundle. We need this to be able to
@@ -480,7 +570,10 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> {
);
}
for file in orphan_files {
// NOTE: This is done in reverse lexicographical order to make sure subdirectories are
// cleaned before their parent directories
orphan_files.sort_by(|a, b| b.path().cmp(a.path()));
for file in orphan_files.into_iter() {
println!("- {}", file.path().display());
if options.prune {
match &file {
+11
View File
@@ -49,6 +49,10 @@ pub const YABRIDGE_HOST_32_EXE_NAME: &str = "yabridge-host-32.exe";
/// `$XDG_CONFIG_HOME` and `$XDG_DATA_HOME`.
const YABRIDGE_PREFIX: &str = "yabridge";
/// The path relative to `$HOME` we will set up bridged VST2 plugins in when using the centralized
/// VST2 installation location setting. By putting this in a subdirectory we can easily clean up any
/// orphan files without interfering with other native plugins.
const YABRIDGE_VST2_HOME: &str = ".vst/yabridge";
/// The path relative to `$HOME` that VST3 modules bridged by yabridgectl life in. By putting this
/// in a subdirectory we can easily clean up any orphan files without interfering with other native
/// plugins.
@@ -322,6 +326,13 @@ pub fn yabridgectl_directories() -> Result<BaseDirectories> {
BaseDirectories::with_prefix(YABRIDGECTL_PREFIX).context("Error while parsing base directories")
}
/// Get the path where bridged VST2 plugin files should be placed when using the centralized
/// installation location setting. This is a subdirectory of `~/.vst` so we can easily clean up
/// leftover files without interfering with other native plugins.
pub fn yabridge_vst2_home() -> PathBuf {
Path::new(&env::var("HOME").expect("$HOME is not set")).join(YABRIDGE_VST2_HOME)
}
/// Get the path where VST3 modules bridged by yabridgectl should be placed in. This is a
/// subdirectory of `~/.vst3` so we can easily clean up leftover files without interfering with
/// other native plugins.
+80 -17
View File
@@ -26,7 +26,7 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use walkdir::WalkDir;
use crate::config::{yabridge_vst3_home, YabridgeFiles};
use crate::config::{yabridge_vst2_home, yabridge_vst3_home, Config, YabridgeFiles};
use crate::utils::get_file_type;
/// Stores the results from searching through a directory. We'll search for Windows VST2 plugin
@@ -125,6 +125,47 @@ pub enum Vst3ModuleType {
Bundle(PathBuf),
}
impl Vst2Plugin {
/// Get the absolute path to the `.so` file we should create in `~/.vst/yabridge` for this
/// plugin when using the centralized VST installation location mode.
pub fn centralized_native_target(&self) -> PathBuf {
let file_name = self
.path
.file_name()
.unwrap()
.to_str()
.expect("Plugin name contains invalid UTF-8");
let file_name = Path::new(file_name).with_extension("so");
match &self.subdirectory {
Some(directory) => yabridge_vst2_home().join(directory).join(file_name),
None => yabridge_vst2_home().join(file_name),
}
}
/// Get the absolute path to the `.dll` file we should symlink to `~/.vst/yabridge` when setting
/// this plugin up with the centralized VST2 installation location setting.
pub fn centralized_windows_target(&self) -> PathBuf {
let file_name = self
.path
.file_name()
.unwrap()
.to_str()
.expect("Plugin name contains invalid UTF-8");
match &self.subdirectory {
Some(directory) => yabridge_vst2_home().join(directory).join(file_name),
None => yabridge_vst2_home().join(file_name),
}
}
/// Get the absolute path to the `.so` file we should create when setting this plugin up with
/// the inline VST2 installation location setting.
pub fn inline_native_target(&self) -> PathBuf {
self.path.with_extension("so")
}
}
impl Vst3Module {
/// Get the path to the Windows VST3 plugin. This can be either a file or a directory depending
/// on the type of moudle.
@@ -195,7 +236,7 @@ impl Vst3Module {
/// version of this module. The path here depends on whether we're using a 32-bit or 64-bit
/// version of yabridge. If the configuration is not given (for instance, becuase yabridge is
/// not set up properly) we'll assume the module should be 64-bit.
pub fn target_native_module_path(&self, config: Option<&YabridgeFiles>) -> PathBuf {
pub fn target_native_module_path(&self, files: Option<&YabridgeFiles>) -> PathBuf {
let native_module_name = match &self.module {
Vst3ModuleType::Legacy(path) | Vst3ModuleType::Bundle(path) => path
.with_extension("so")
@@ -210,7 +251,7 @@ impl Vst3Module {
path.push("Contents");
#[allow(clippy::wildcard_in_or_patterns)]
match config.and_then(|c| c.vst3_chainloader.as_ref()) {
match files.and_then(|c| c.vst3_chainloader.as_ref()) {
Some((_, LibArchitecture::Lib32)) => path.push("i386-linux"),
// NOTE: We'll always fall back to this if `libyabridge-chainloader-vst3.so` is not
// found, just so we cannot get any errors during `yabridgectl status` even if
@@ -280,12 +321,12 @@ impl LibArchitecture {
}
impl SearchResults {
/// Create a map out of all found plugins based on their file path that contains both a
/// reference to the plugin (so we can print information about it) and the current installation
/// status. The installation status will be `None` if the plugin has not yet been set up.
/// Create a map out of all found Windows plugins and their current installation status, if the
/// plugin has already been set up.
pub fn installation_status(
&self,
config: Option<&YabridgeFiles>,
config: &Config,
files: Option<&YabridgeFiles>,
) -> BTreeMap<PathBuf, (&Plugin, Option<NativeFile>)> {
let so_files: HashMap<&Path, &NativeFile> = self
.so_files
@@ -296,12 +337,26 @@ impl SearchResults {
self.plugins
.iter()
.map(|plugin| match plugin {
Plugin::Vst2(Vst2Plugin { path, .. }) => {
// For VST2 plugins we'll just look at the similarly named `.so` file right next
// 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()))),
None => (path.clone(), (plugin, None)),
Plugin::Vst2(vst2_plugin) => {
// For VST2 plugins depending on the VST2 installation location setting we'll
// either look for a matching file in `~/.vst` or we'll just look at the
// similarly named `.so` file right next to the plugin `.dll` file
match config.vst2_location {
crate::config::Vst2InstallationLocation::Centralized => (
vst2_plugin.path.clone(),
(
plugin,
get_file_type(vst2_plugin.centralized_native_target()),
),
),
crate::config::Vst2InstallationLocation::Inline => {
match so_files.get(vst2_plugin.inline_native_target().as_path()) {
Some(&file_type) => {
(vst2_plugin.path.clone(), (plugin, Some(file_type.clone())))
}
None => (vst2_plugin.path.clone(), (plugin, None)),
}
}
}
}
// We have not stored the paths to the corresponding `.so` files yet for VST3
@@ -310,7 +365,7 @@ impl SearchResults {
vst3_module.original_path().to_owned(),
(
plugin,
get_file_type(vst3_module.target_native_module_path(config)),
get_file_type(vst3_module.target_native_module_path(files)),
),
),
})
@@ -318,9 +373,9 @@ impl SearchResults {
}
/// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file.
/// We cannot yet do the same thing for VST3 plguins because they will all be installed in
/// `~/.vst3`.
pub fn vst2_orphans(&self) -> Vec<&NativeFile> {
/// This depends on the VST2 installation location setting. Centralized VST2 and VST3 orphans
/// should be detected separately.
pub fn vst2_inline_orphans(&self, config: &Config) -> Vec<&NativeFile> {
// We need to store these in a map so we can easily entries with corresponding `.dll` files
let mut orphans: HashMap<&Path, &NativeFile> = self
.so_files
@@ -328,11 +383,19 @@ impl SearchResults {
.map(|file_type| (file_type.path(), file_type))
.collect();
match config.vst2_location {
// When we set up the plugin in `~/.vst`, any `.so` file in a VST2 plugin search
// directory should be considered an orphan. This can happen when switching between the
// two modes.
crate::config::Vst2InstallationLocation::Centralized => (),
crate::config::Vst2InstallationLocation::Inline => {
for plugin in &self.plugins {
if let Plugin::Vst2(Vst2Plugin { path, .. }) = plugin {
orphans.remove(path.with_extension("so").as_path());
}
}
}
}
orphans.values().cloned().collect()
}