diff --git a/CHANGELOG.md b/CHANGELOG.md index 47bb6cc7..7df1e8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,8 +96,9 @@ Versioning](https://semver.org/spec/v2.0.0.html). because yabridgectl would try to run the 64-bit `yabridge-host.exe` in that prefix. - Fixed incorrect new and total plugin counts. These counts are now always - correct, even when using multiple versions of the same VST3 plugin or when a - plugin directory contains a symlink to another plugin directory. + correct, even when using multiple versions of the same VST3 plugin or when + multiple plugin directories overlap because parts of the directory were + symlinked to another plugin directory. - Aside from pruning only unmanaged VST3 bundles in `~/.vst3/yabridge`, yabridge will now also prompt you to prune unmanaged files within a VST3 bundle. This makes it easy to switch from the 64-bit version of a plugin to the 32-bit diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 110b2d0d..3f7d40f2 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -231,6 +231,9 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { // this makes everything much easier since we'll have to deal with things like a plugin // directory A containing a symlink to plugin directory B, as well as VST3 plugisn that come in // both x86 and x86_64 flavours. + // Paths added to this and to the `new_plugins` set below should be normalized with + // `utils::normalize_path()` so that the reported numbers are still correct when encountering + // overlapping symlinked paths. let mut managed_plugins: HashSet = HashSet::new(); // The plugins we created a new copy of `libyabridge-{vst2,vst3}.so` for. We don't touch these // files if they're already up to date to prevent hosts from unnecessarily rescanning the @@ -265,6 +268,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { path: plugin_path, .. }) => { let target_path = plugin_path.with_extension("so"); + 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 // actually set up @@ -275,9 +279,9 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { Some(libyabridge_vst2_hash), &target_path, )? { - new_plugins.insert(target_path.clone()); + new_plugins.insert(normalized_target_path.clone()); } - managed_plugins.insert(target_path); + managed_plugins.insert(normalized_target_path); plugin_path.clone() } @@ -292,6 +296,8 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { let target_bundle_home = module.target_bundle_home(); let target_native_module_path = module.target_native_module_path(Some(&files)); let target_windows_module_path = module.target_windows_module_path(); + let normalized_native_module_path = + utils::normalize_path(&target_native_module_path); // 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 @@ -327,9 +333,13 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { libyabridge_vst3_hash, &target_native_module_path, )? { - new_plugins.insert(target_native_module_path.clone()); + // We're counting the native `.so` files and not the Windows VST3 plugins + // because even though the 32-bit and 64-bit versions of a plugin are + // technically separate plugins, we can only use one at a time anyways + // because of how these bundles work + new_plugins.insert(normalized_native_module_path.clone()); } - managed_plugins.insert(target_native_module_path.clone()); + managed_plugins.insert(normalized_native_module_path.clone()); managed_vst3_bundle_files.insert(target_native_module_path); // We'll then symlink the Windows VST3 module to that bundle to create a merged diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index a2e7027e..5a747d12 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -176,6 +176,22 @@ pub fn hash_file(file: &Path) -> Result { Ok(hasher.finish() as i64) } +/// Resolve symlinks in a path, like the `realpath` coreutil, but don't throw any errors of `path` +/// does not exist, unlike the `realpath` libc function. +/// +/// This is used to resolve symlinked directories in the syncing process so the plugin counts are +/// correct even when one plugin directory contains a symlink to another plugin directory. +pub fn normalize_path(path: &Path) -> PathBuf { + for prefix in path.ancestors() { + // If part of `path`s prefix exists, then we'll try to resolve symlinks there + if let Ok(normalized_prefix) = fs::canonicalize(&prefix) { + return normalized_prefix.join(path.strip_prefix(prefix).unwrap()); + } + } + + path.to_owned() +} + /// Verify that `yabridge-host.exe` can be found when yabridge is run in a host launched from the /// GUI. We do this by launching a login shell, appending `~/.local/share/yabridge` to the login /// shell's search path since that's what yabridge also does, and then making the the file can be