From c69037b649e52682b10f309a0c008bab79f09f5d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 20 Nov 2020 02:20:41 +0100 Subject: [PATCH] Always search for host in ~/.local/share/yabridge --- CHANGELOG.md | 6 +++++ README.md | 44 ++++++++++++++----------------- src/plugin/utils.cpp | 17 +++++++++++- src/plugin/utils.h | 13 +++++++++- tools/yabridgectl/Cargo.lock | 10 +++++++ tools/yabridgectl/Cargo.toml | 5 ++-- tools/yabridgectl/src/config.rs | 14 +++++----- tools/yabridgectl/src/main.rs | 12 +++++++++ tools/yabridgectl/src/utils.rs | 46 ++++++++++++++++++++++++++------- 9 files changed, 122 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16f82a94..c7bce3d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- Yabridge will now always search for `yabridge-host.exe` in + `~/.local/share/yabridge`, even if that directory is not in the search path. + This should make setup easier. + ### Fixed - Fixed an issue where _Renoise_ would show an error message when trying to load diff --git a/README.md b/README.md index dffd936d..980dda32 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,12 @@ or by running `yabridgectl --help`. First, yabridgectl needs to know where it can find yabridge's files. If you have downloaded the prebuilt binaries, then you can simply extract the archive to -`~/.local/share` and yabridgectl will pick up the files in -`~/.local/share/yabridge` automatically[\*](#why-local-share-yabridge). You also -won't have to do any additional work if you're using one of the AUR packages. If -you have compiled yabridge from source or if you installed the files to some -other location, then you can use `yabridgectl set --path=` to tell -yabridgectl where it can find the files. +`~/.local/share` and both yabridge and yabridgectl will pick up the files in +`~/.local/share/yabridge` automatically. You also won't have to do any +additional work if you're using one of the AUR packages. If you have compiled +yabridge from source or if you installed the files to some other location, then +you can use `yabridgectl set --path=` to tell yabridgectl where it can +find the files. Secondly, yabridgectl will default to the copy-based installation method. If you are using a VST host with individually sandboxed plugins such as Bitwig Studio @@ -107,16 +107,6 @@ tell your VST host to search for plugins in the directories you just added and you'll be good to go. _Don't forget to rerun `yabridgectl sync` whenever you update yabridge if you are using the copy-based installation method._ - - *Instead of copying yabridge's files to ~/.local/share, it would - also be possible to install yabridge to /usr/local/bin and - /usr/local/lib. While this does avoid the need to modify your - PATH environment variable when using the copy-based installation - method, it could also cause other issues if you're not careful. This is why - it's recommended to install yabridge to your home directory if you're not - using one of the AUR packages. - - ### Manual setup To set up yabridge without using yabridgectl, first download and extract @@ -143,15 +133,21 @@ update yabridge. ### Search path setup -If you're using the _copy-based_ installation method and you're not using any of -the AUR packages, then you may have to modify your _login shell_'s `PATH` -environment variable so that yabridge is able to find the files in the directory -you've extracted yabridge's files to. Yabridgectl will automatically check -whether this is set up correctly when you run `yabridgectl sync`, and it will -show a warning if it detects any issues. _If you do not see such a warning after -running `yabridgectl sync`, then you can skip this section._ +This section is only relevant if you're using the _copy-based_ installation +method and your yabridge files are located somewhere other than in +`~/.local/share/yabridge`. If you're using one of the AUR packages then you can +also skip this section. -To do this, you'll want to add yabridge's installation directory to your login +Yabridge needs to know where it can find `yabridge-host.exe`. By default +yabridge will search your through search path as well as in +`~/.local/share/yabridge` if that exists. When loading yabridge from a +non-standard location, such as when building from source, you may have to modify +your _login shell_'s `PATH` environment variable so that yabridge is able to +find its files. Yabridgectl will automatically check whether this is set up +correctly when you run `yabridgectl sync`, and it will show a warning if it +detects any issues. _If you do not see such a warning after running `yabridgectl sync`, then you can skip this section._ + +To set this, you'll want to add yabridge's installation directory to your login shell's `PATH` environment variable. If you're unsure what your login shell is, then you can open a terminal and run `echo $SHELL` to find out. For the below examples I'll assume you're using the default installation location at diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index 567e1284..6eb381c9 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -118,7 +118,8 @@ fs::path find_vst_host(PluginArchitecture plugin_arch, bool use_plugin_groups) { // Boost will return an empty path if the file could not be found in the // search path - const fs::path vst_host_path = bp::search_path(host_name); + const fs::path vst_host_path = + bp::search_path(host_name, get_modified_search_path()); if (vst_host_path == "") { throw std::runtime_error("Could not locate '" + std::string(host_name) + "'"); @@ -176,6 +177,20 @@ boost::filesystem::path generate_group_endpoint( return get_temporary_directory() / socket_name.str(); } +std::vector get_modified_search_path() { + std::vector search_path = + boost::this_process::path(); + + const bp::environment environment = boost::this_process::environment(); + if (auto home_directory = environment.find("HOME"); + home_directory != environment.end()) { + search_path.push_back(fs::path(home_directory->to_string()) / ".local" / + "share" / "yabridge"); + } + + return search_path; +} + fs::path get_this_file_location() { // HACK: Not sure why, but `boost::dll::this_line_location()` returns a path // starting with a double slash on some systems. I've seen this happen diff --git a/src/plugin/utils.h b/src/plugin/utils.h index 8e2d6fbb..96cc000e 100644 --- a/src/plugin/utils.h +++ b/src/plugin/utils.h @@ -80,7 +80,8 @@ PluginArchitecture find_vst_architecture(boost::filesystem::path); * when developing, as you can simply symlink the the libyabridge.so * file in the build directory without having to install anything to * /usr. - * 2. In the regular search path. + * 2. In the regular search path, augmented with `~/.local/share/yabridge` to + * ease the setup process. * * @param plugin_arch The architecture of the plugin, either 64-bit or 32-bit. * Used to determine which host application to use, if available. @@ -143,6 +144,16 @@ boost::filesystem::path generate_group_endpoint( const boost::filesystem::path& wine_prefix, const PluginArchitecture architecture); +/** + * Return the search path as defined in `$PATH`, with `~/.local/share/yabridge` + * appended to the end. I'd rather not do this since more magic makes things + * harder to comprehend, but I can understand that modifying your login shell's + * `PATH` environment variable can be a big hurdle if you've never done anything + * like that before. And since this is the recommended installation location, it + * makes sense to also search there by default. + */ +std::vector get_modified_search_path(); + /** * Return a path to this `.so` file. This can be used to find out from where * this link to or copy of `libyabridge.so` was loaded. diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index a7714670..ae2fd39e 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -242,6 +242,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "is_executable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d553b8abc8187beb7d663e34c065ac4570b273bc9511a50e940e99409c577" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -704,6 +713,7 @@ dependencies = [ "anyhow", "clap", "colored", + "is_executable", "lazy_static", "promptly", "rayon", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index 381bc142..af673650 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -10,10 +10,11 @@ repository = "https://github.com/robbert-vdh/yabridge" license = "GPL-3.0-or-later" [dependencies] -anyhow = "1.0.31" aho-corasick = "0.7.13" -colored = "2.0.0" +anyhow = "1.0.31" clap = { version = "3.0.0-beta.1", features = ["wrap_help"] } +colored = "2.0.0" +is_executable = "0.1.2" lazy_static = "1.4.0" promptly = "0.3.0" rayon = "1.3.1" diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index dbf3c2b5..fbb6b979 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -29,15 +29,15 @@ use xdg::BaseDirectories; use crate::files::{self, SearchResults}; /// The name of the config file, relative to `$XDG_CONFIG_HOME/CONFIG_PREFIX`. -const CONFIG_FILE_NAME: &str = "config.toml"; +pub const CONFIG_FILE_NAME: &str = "config.toml"; /// The name of the XDG base directory prefix for yabridgectl, relative to `$XDG_CONFIG_HOME` and /// `$XDG_DATA_HOME`. const YABRIDGECTL_PREFIX: &str = "yabridgectl"; /// The name of the library file we're searching for. -const LIBYABRIDGE_NAME: &str = "libyabridge.so"; +pub const LIBYABRIDGE_NAME: &str = "libyabridge.so"; /// The name of the script we're going to run to verify that everything's working correctly. -const YABRIDGE_HOST_EXE_NAME: &str = "yabridge-host.exe"; +pub const YABRIDGE_HOST_EXE_NAME: &str = "yabridge-host.exe"; /// The name of the XDG base directory prefix for yabridge's own files, relative to /// `$XDG_CONFIG_HOME` and `$XDG_DATA_HOME`. const YABRIDGE_PREFIX: &str = "yabridge"; @@ -191,8 +191,8 @@ impl Config { } Err(anyhow!( - "Could not find '{}' in either '{}' or '{}'. You can tell yabridgectl where \ - to search for it using 'yabridgectl set --path='.", + "Could not find '{}' in either '{}' or '{}'. You can override the default \ + search path using 'yabridgectl set --path='.", LIBYABRIDGE_NAME, system_path.display(), user_path.display() @@ -229,12 +229,12 @@ impl Config { /// Fetch the XDG base directories for yabridge's own files, converting any error messages if this /// somehow fails into a printable string to reduce boiler plate. This is only used when searching /// for `libyabridge.so` when no explicit search path has been set. -fn yabridge_directories() -> Result { +pub fn yabridge_directories() -> Result { BaseDirectories::with_prefix(YABRIDGE_PREFIX).context("Error while parsing base directories") } /// Fetch the XDG base directories used for yabridgectl, converting any error messages if this /// somehow fails into a printable string to reduce boiler plate. -fn yabridgectl_directories() -> Result { +pub fn yabridgectl_directories() -> Result { BaseDirectories::with_prefix(YABRIDGECTL_PREFIX).context("Error while parsing base directories") } diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 3c419ce1..b08bd8f3 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -17,6 +17,7 @@ use anyhow::Result; use clap::{app_from_crate, App, AppSettings, Arg}; use colored::Colorize; +use std::env; use std::path::{Path, PathBuf}; use crate::config::Config; @@ -27,6 +28,17 @@ mod files; mod utils; fn main() -> Result<()> { + // We'll modify our `PATH` environment variable so it matches up with + // `get_modified_search_path()` from `src/plugin/utils.h` for easier setup + let yabridge_home = config::yabridge_directories()?.get_data_home(); + env::set_var( + "PATH", + match env::var("PATH") { + Ok(path) => format!("{}:{}", path, yabridge_home.display()), + _ => format!("{}", yabridge_home.display()), + }, + ); + let mut config = Config::read()?; // Used for validation in `yabridgectl rm ` diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index cbc025c5..e135a037 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -18,6 +18,7 @@ use anyhow::{Context, Result}; use colored::Colorize; +use is_executable::IsExecutable; use std::collections::hash_map::DefaultHasher; use std::env; use std::fs; @@ -28,7 +29,7 @@ use std::path::Path; use std::process::{Command, Stdio}; use textwrap::Wrapper; -use crate::config::{Config, KnownConfig}; +use crate::config::{self, Config, KnownConfig, YABRIDGE_HOST_EXE_NAME}; /// (Part of) the expected output when running `yabridge-host.exe`. Used to verify that everything's /// working correctly. We'll only match this prefix so we can modify the exact output at a later @@ -82,15 +83,33 @@ pub fn hash_file(file: &Path) -> Result { Ok(hasher.finish() as i64) } -/// Verify that `yabridge-host.exe` is accessible in a login shell. Returns unit if it is, or if we -/// the login shell is set to an unknown shell. In the last case we'll just print a warning since we -/// don't know how to invoke the shell as a login shell. This is needed when using copies to ensure -/// that yabridge can find the host binaries when the VST host is launched from the desktop -/// enviornment. +/// 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 +/// found. Returns unit if it can be found, or if we the login shell is set to an unknown shell. In +/// the last case we'll just print a warning since we don't know how to invoke the shell as a login +/// shell. This is needed when using copies to ensure that yabridge can find the host binaries when +/// the VST host is launched from the desktop enviornment. /// /// When we could not find `yabridge-host.exe`, we'll return `Err(shell_name)` so we can print a /// descriptive warning message. pub fn verify_path_setup() -> Result<(), String> { + // First we'll check `~/.local/share/yabridge`, since that's a special location where yabridge + // will always search + if config::yabridge_directories() + .ok() + .and_then(|dirs| { + dirs.get_data_home() + .push(YABRIDGE_HOST_EXE_NAME) + .is_executable() + }) + .unwrap_or(false) + { + return Ok(()); + } + + // Then we'll check the login shell, since DAWs launched from the GUI will have the same + // environment match env::var("SHELL") { Ok(shell_path) => { // `$SHELL` will often contain a full path, but it doesn't have to @@ -113,13 +132,20 @@ pub fn verify_path_setup() -> Result<(), String> { | "zsh" => command .arg("-l") .arg("-c") - .arg("command -v yabridge-host.exe"), + .arg(format!("command -v {}", YABRIDGE_HOST_EXE_NAME)), // These shells either have their own implementation of `which` and don't support // `command`, or they don't have a seperate login shell flag - "elvish" | "oil" => command.arg("-c").arg("command -v yabridge-host.exe"), + "elvish" | "oil" => command + .arg("-c") + .arg(format!("command -v {}", YABRIDGE_HOST_EXE_NAME)), // xonsh's which implementation is broken as of writing this, so I left it out - "pwsh" => command.arg("-l").arg("-c").arg("which yabridge-host.exe"), - "nu" => command.arg("-c").arg("which yabridge-host.exe"), + "pwsh" => command + .arg("-l") + .arg("-c") + .arg(format!("which {}", YABRIDGE_HOST_EXE_NAME)), + "nu" => command + .arg("-c") + .arg(format!("which {}", YABRIDGE_HOST_EXE_NAME)), shell => { eprintln!( "\n{}",