From 7fef7dc623df86090429ab4088ac77259b345ea4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 10 Jul 2020 14:50:29 +0200 Subject: [PATCH 01/29] Add an empty Rust project with clap --- tools/yabridgectl/.gitignore | 1 + tools/yabridgectl/Cargo.lock | 259 ++++++++++++++++++++++++++++++++++ tools/yabridgectl/Cargo.toml | 10 ++ tools/yabridgectl/src/main.rs | 3 + 4 files changed, 273 insertions(+) create mode 100644 tools/yabridgectl/.gitignore create mode 100644 tools/yabridgectl/Cargo.lock create mode 100644 tools/yabridgectl/Cargo.toml create mode 100644 tools/yabridgectl/src/main.rs diff --git a/tools/yabridgectl/.gitignore b/tools/yabridgectl/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/tools/yabridgectl/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock new file mode 100644 index 00000000..788be5ae --- /dev/null +++ b/tools/yabridgectl/Cargo.lock @@ -0,0 +1,259 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "clap" +version = "3.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb51c9e75b94452505acd21d929323f5a5c6c4735a852adbd39ef5fb1b014f30" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" +dependencies = [ + "autocfg", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" + +[[package]] +name = "os_str_bytes" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06de47b848347d8c4c94219ad8ecd35eb90231704b067e67e6ae2e36ee023510" + +[[package]] +name = "proc-macro-error" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yabridgectl" +version = "0.1.0" +dependencies = [ + "clap", +] diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml new file mode 100644 index 00000000..61ed0f29 --- /dev/null +++ b/tools/yabridgectl/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "yabridgectl" +version = "0.1.0" +authors = ["Robbert van der Helm "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "3.0.0-beta.1" diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/tools/yabridgectl/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From adced8f951604ea5e4730ad3d074fca4c151b1af Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 10 Jul 2020 15:29:39 +0200 Subject: [PATCH 02/29] Add a readme for yabridgectl --- tools/yabridgectl/README.md | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tools/yabridgectl/README.md diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md new file mode 100644 index 00000000..a03f1417 --- /dev/null +++ b/tools/yabridgectl/README.md @@ -0,0 +1,62 @@ +# yabridgectl + +A small, optional utility to set up +[yabridge](https://github.com/robbert-vdh/yabridge) for several directories at +once and to keep them updated. + +## Usage + +All of the information below can also be found by running `yabridgectl --help`. + +### Installation modes + +By default, yabridgectl will use the copy-based installation method for yabridge +since this installation method works everywhere. If you are using a DAW that +supports individually sandboxed plugins, then you can choose between using +copies and symlinks using the command below. + +```shell +yabridgectl set --mode= +``` + +### Managing directories + +Yabridgectl manage Windows VST plugin install locations for you. To add, remove +and list directories, or to list the plugins currently installed inside one of +those directories, you can use the command below. + +```shell +# Add a directory to watch +# For instance, use the below command for the most common VST2 plugin directory +# yabridgectl add "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins" +yabridgectl add +# No longer watch a directory, this will ask you if you want to remove any leftover yabridge files +yabridgectl rm +# List the currently watched directories +yabridgectl ls +# Show the current settings and the installation status for all plugins in the watched directories +yabridgectl status +``` + +### Installing and updating + +Finally you can set up or update yabridge for all of your plugins at once using +the command below. By default yabridgectl will warn you if it finds `.so` files +without an accompanying `.dll` file, but it will only delete those when using +the `--prune` option. + +```shell +# Set up copies or symlinks of yabridge for all plugins under the watched directories +yabridgectl sync +# Set up yabridge, and also remove any '.so' still leftover after removing a plugin +yabridgectl sync --purge +``` + +## Building + +After installing [Rust](https://rustup.rs/), simply run the below to compile and +run: + +```shell +cargo run --release +``` From 343398be2561213d720406f5b755a831de11d708 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 10 Jul 2020 15:30:44 +0200 Subject: [PATCH 03/29] Remove the 'upload-releases' CI job It didn't work, and it wouldn't really be more convenient than adding the files to the release manually anyways. --- .github/workflows/build.yml | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8af38df4..a8f6cee5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,6 @@ on: pull_request: branches: - master - release: - types: [created] defaults: run: @@ -105,33 +103,3 @@ jobs: with: name: ${{ env.ARCHIVE_NAME }} path: ${{ env.ARCHIVE_NAME }} - - upload-releases: - name: Upload the created artifacts to the releases page - runs-on: ubuntu-latest - needs: [build-bionic, build-focal] - if: ${{ github.event_name == 'release' }} - steps: - # They don't allow you to specify multiple file names for these actions - - uses: actions/download-artifact@v2 - with: - name: ${{ needs.build-bionic.outputs.artifact-name }} - - uses: actions/download-artifact@v2 - with: - name: ${{ needs.build-focal.outputs.artifact-name }} - - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./${{ needs.build-bionic.outputs.artifact-name }} - asset_name: $${{ needs.build-bionic.outputs.artifact-name }} - asset_content_type: application/x-compressed-tar - - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./${{ needs.build-focal.outputs.artifact-name }} - asset_name: $${{ needs.build-focal.outputs.artifact-name }} - asset_content_type: application/x-compressed-tar From 642395f0bdaf175c365df5542bc6737eada475c8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 10 Jul 2020 15:47:55 +0200 Subject: [PATCH 04/29] Mention custom installation directories --- tools/yabridgectl/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index a03f1417..d21c8758 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -8,6 +8,18 @@ once and to keep them updated. All of the information below can also be found by running `yabridgectl --help`. +### Yabridge path + +Yabrdgectl will need to know where it can find `libyabridge.so`. By default it +will look for it in both `~/.local/share/yabridge` (the recommended installation +directory when using the prebuilt binaries) and in `/usr/lib` (used when using +the AUR packages). You can use the command below to override this behaviour and +to use a custom installation directory instead. + +```shell +yabridgectl set --path= +``` + ### Installation modes By default, yabridgectl will use the copy-based installation method for yabridge From 83494a13bc89d2bb9f2ec1887e56af6875273346 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 10 Jul 2020 16:02:04 +0200 Subject: [PATCH 05/29] Add a GitHub Actions job for building yabridgectl --- .github/workflows/build.yml | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a8f6cee5..92d5ed26 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,3 +103,44 @@ jobs: with: name: ${{ env.ARCHIVE_NAME }} path: ${{ env.ARCHIVE_NAME }} + + build-yabridgectl: + name: Build yabridgectl + runs-on: ubuntu-18.04 + outputs: + artifact-name: ${{ env.ARCHIVE_NAME }} + defaults: + run: + working-directory: tools/yabridgectl + steps: + - uses: actions/checkout@v2 + # Needed for git-describe to do anything useful + - name: Fetch all git history + run: git fetch --force --prune --tags --unshallow + - name: Determine build archive name + run: | + export ARCHIVE_NAME=yabridgectl-$(git describe --always).tar.gz + echo ::set-env "name=ARCHIVE_NAME::$ARCHIVE_NAME" + - name: Set up Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + default: true + - name: Build the binaries + run: cargo build --release + - name: Strip remaining debug symbols + run: strip target/release/yabridgectl + - name: Create an archive for the binaries + run: | + mkdir yabridgectl + cp target/release/yabridgectl README.md yabridgectl + + tar -caf "$ARCHIVE_NAME" yabridgectl + rm -rf yabridgectl + - uses: actions/upload-artifact@v2 + with: + name: ${{ env.ARCHIVE_NAME }} + # For some reason there's no way to tell GitHub actions to run actions + # in a subdirectory + path: tools/yabridgectl/${{ env.ARCHIVE_NAME }} From 8561da88ef73ce492b16524ffe58117256ace68c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 11:54:50 +0200 Subject: [PATCH 06/29] Add other dependencies needed yabridgectl --- tools/yabridgectl/Cargo.lock | 149 +++++++++++++++++++++++++++++++++++ tools/yabridgectl/Cargo.toml | 3 + 2 files changed, 152 insertions(+) diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index 788be5ae..84e61ab4 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + [[package]] name = "atty" version = "0.2.14" @@ -23,6 +32,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "clap" version = "3.0.0-beta.1" @@ -55,6 +70,60 @@ dependencies = [ "syn", ] +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + [[package]] name = "heck" version = "0.3.1" @@ -94,6 +163,37 @@ version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memoffset" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "os_str_bytes" version = "2.3.1" @@ -144,6 +244,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" + [[package]] name = "strsim" version = "0.10.0" @@ -190,6 +327,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + [[package]] name = "unicode-segmentation" version = "1.6.0" @@ -255,5 +401,8 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" name = "yabridgectl" version = "0.1.0" dependencies = [ + "aho-corasick", "clap", + "rayon", + "toml", ] diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index 61ed0f29..319377bf 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -7,4 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +aho-corasick = "0.7.13" clap = "3.0.0-beta.1" +rayon = "1.3.1" +toml = "0.5.6" From e047a0e77698dbabcff7f4cac841ccf6bb6beedb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 12:00:55 +0200 Subject: [PATCH 07/29] Update license and version for yabridgectl Easiest to just match yabridge itself. --- tools/yabridgectl/Cargo.lock | 2 +- tools/yabridgectl/Cargo.toml | 7 ++++++- tools/yabridgectl/src/main.rs | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index 84e61ab4..094db8c0 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -399,7 +399,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "yabridgectl" -version = "0.1.0" +version = "1.2.1" dependencies = [ "aho-corasick", "clap", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index 319377bf..5917925e 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -1,8 +1,13 @@ [package] name = "yabridgectl" -version = "0.1.0" +# This version is linked to yabridge's version for clarity's sake and because +# there's not a lot going on here +version = "1.2.1" authors = ["Robbert van der Helm "] edition = "2018" +description = "Optional command line helper to set up yabridge" +repository = "https://github.com/robbert-vdh/yabridge" +license = "GPL-3.0-or-later" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index e7a11a96..bdaf5c9d 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -1,3 +1,19 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + fn main() { println!("Hello, world!"); } From ee76da9be6d92c087077b904eee8c860fe7331eb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 13:21:15 +0200 Subject: [PATCH 08/29] Rename 'yabridgectl ls' to 'yabridgectl list' --- tools/yabridgectl/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index d21c8758..1b93c368 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -45,7 +45,7 @@ yabridgectl add # No longer watch a directory, this will ask you if you want to remove any leftover yabridge files yabridgectl rm # List the currently watched directories -yabridgectl ls +yabridgectl list # Show the current settings and the installation status for all plugins in the watched directories yabridgectl status ``` From 2b1f29cc193ec41a9410db338c1cb900a72f18e1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 13:22:47 +0200 Subject: [PATCH 09/29] Mention how to acquire yabridgectl --- tools/yabridgectl/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index 1b93c368..cdbdad2b 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -6,7 +6,9 @@ once and to keep them updated. ## Usage -All of the information below can also be found by running `yabridgectl --help`. +Yabridgectl can be downloaded from the GitHub releases section, and it can run +from anywhere. All of the information below can also be found by running +`yabridgectl --help`. ### Yabridge path From 4537b6a5b072d805f967e2e0497d2e7a5d40c208 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 13:24:28 +0200 Subject: [PATCH 10/29] Add the configuration struct for yabridgectl --- tools/yabridgectl/Cargo.lock | 28 +++++++++++ tools/yabridgectl/Cargo.toml | 4 +- tools/yabridgectl/src/config.rs | 89 +++++++++++++++++++++++++++++++++ tools/yabridgectl/src/main.rs | 2 + 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 tools/yabridgectl/src/config.rs diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index 094db8c0..7fc29473 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -269,6 +269,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -366,6 +375,17 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "winapi" version = "0.3.9" @@ -397,6 +417,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "xdg" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" + [[package]] name = "yabridgectl" version = "1.2.1" @@ -405,4 +431,6 @@ dependencies = [ "clap", "rayon", "toml", + "walkdir", + "xdg", ] diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index 5917925e..c9afb3ad 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -9,10 +9,10 @@ description = "Optional command line helper to set up yabridge" repository = "https://github.com/robbert-vdh/yabridge" license = "GPL-3.0-or-later" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] aho-corasick = "0.7.13" clap = "3.0.0-beta.1" rayon = "1.3.1" toml = "0.5.6" +walkdir = "2.3.1" +xdg = "2.2.0" diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs new file mode 100644 index 00000000..6909e959 --- /dev/null +++ b/tools/yabridgectl/src/config.rs @@ -0,0 +1,89 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use std::path::{Path, PathBuf}; +use xdg::BaseDirectories; + +const LIBYABRIDGE_NAME: &str = "libyabridge.so"; + +/// The configuration used for yabridgectl. This will be serialized to and deserialized from +/// `$XDG_CONFIG_HOME/yabridge/config.toml`. +pub struct Config { + /// The installation method to use. We will default to creating copies since that works + /// everywehre. + pub method: InstallationMethod, + /// The path to the directory containing `libyabridge.so`. If not set, then yabridgectl will + /// look in `/usr/lib` and `$XDG_DATA_HOME/yabridge` since those are the expected locations for + /// yabridge to be installed in. + pub yabridge_home: Option, + /// Directories to search for Windows VST plugins. + pub plugin_dirs: Vec, +} + +impl Config { + /// Return the path to `libyabridge.so`, or a descriptive error if it can't be found. If + /// `yabridge_home` is `None`, then we'll search in both `/usr/lib` and + /// `$XDG_DATA_HOME/yabridge`. + pub fn libyabridge(&self) -> Result { + match &self.yabridge_home { + Some(directory) => { + let candidate = directory.join(LIBYABRIDGE_NAME); + if candidate.exists() { + Ok(candidate) + } else { + Err(format!( + "Could not find {} in '{}'.", + LIBYABRIDGE_NAME, + directory.display() + )) + } + } + None => { + // Search in the two common installation locations if no path was set explicitely + let system_path = Path::new("/usr/lib"); + let user_path = BaseDirectories::new() + .map_err(|err| format!("Error while parsing base directories:\n{}", err))? + .get_data_home(); + for directory in &[system_path, &user_path] { + let candidate = directory.join(LIBYABRIDGE_NAME); + if candidate.exists() { + return Ok(candidate); + } + } + + Err(format!( + "Could not find {} in either '{}' or '{}'. You can tell yabridgectl where \ + to search for it using 'yabridgectl set --path='.", + LIBYABRIDGE_NAME, + system_path.display(), + user_path.display() + )) + } + } + } +} + +/// Specifies how yabridge will be set up for the found plugins. +pub enum InstallationMethod { + /// Create a copy of `libyabridge.so` for every Windows VST2 plugin .dll file found. After + /// updating yabridge, the user will have to rerun `yabridgectl sync` to copy over the new + /// version. + Copy, + /// This will create a symlink to `libyabridge.so` for every VST2 .dll file in the plugin + /// directories. As explained in the readme, this makes updating easier and remvoes the need to + /// modify the `PATH` environment variable. + Symlink, +} diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index bdaf5c9d..02a1189e 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +mod config; + fn main() { println!("Hello, world!"); } From 21d48c302236890332583d8433b8f67693039708 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 14:31:33 +0200 Subject: [PATCH 11/29] Implement reading and writing of config files --- tools/yabridgectl/Cargo.lock | 13 ++++++ tools/yabridgectl/Cargo.toml | 2 + tools/yabridgectl/src/config.rs | 70 +++++++++++++++++++++++++++++++-- tools/yabridgectl/src/main.rs | 17 +++++++- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index 7fc29473..755c06f8 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -290,6 +290,17 @@ version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" +[[package]] +name = "serde_derive" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "strsim" version = "0.10.0" @@ -430,6 +441,8 @@ dependencies = [ "aho-corasick", "clap", "rayon", + "serde", + "serde_derive", "toml", "walkdir", "xdg", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index c9afb3ad..5594950d 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -13,6 +13,8 @@ license = "GPL-3.0-or-later" aho-corasick = "0.7.13" clap = "3.0.0-beta.1" rayon = "1.3.1" +serde = "1.0.114" +serde_derive = "1.0.114" toml = "0.5.6" walkdir = "2.3.1" xdg = "2.2.0" diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 6909e959..9a9a49e5 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -14,13 +14,21 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use serde_derive::{Deserialize, Serialize}; +use std::fs; use std::path::{Path, PathBuf}; use xdg::BaseDirectories; +/// The name of the config file, relative to `$XDG_CONFIG_HOME/CONFIG_PREFIX`. +const CONFIG_FILE_NAME: &str = "config.toml"; +/// The name of the configuration directory, relative to `$XDG_CONFIG_HOME`. +const CONFIG_PREFIX: &str = "yabridgectl"; + const LIBYABRIDGE_NAME: &str = "libyabridge.so"; /// The configuration used for yabridgectl. This will be serialized to and deserialized from /// `$XDG_CONFIG_HOME/yabridge/config.toml`. +#[derive(Deserialize, Serialize, Debug)] pub struct Config { /// The installation method to use. We will default to creating copies since that works /// everywehre. @@ -34,6 +42,55 @@ pub struct Config { } impl Config { + /// Try to read the config file, creating a new default file if necessary. This will fail if the + /// file could not be created or if it could not be parsed. + pub fn read() -> Result { + match base_directories()?.find_config_file(CONFIG_FILE_NAME) { + Some(path) => { + let toml_str = fs::read_to_string(&path).map_err(|err| { + format!( + "Could not read config file at '{}': {}", + path.display(), + err + ) + })?; + + Ok(toml::from_str(&toml_str) + .map_err(|err| format!("Could not parse TOML: {:#?}", err))?) + } + None => { + let defaults = Config { + method: InstallationMethod::Copy, + yabridge_home: None, + plugin_dirs: Vec::new(), + }; + + // If no existing config file exists, then write a new config file with default + // values + defaults.write()?; + + Ok(defaults) + } + } + } + + /// Write the config to disk, creating the file if it does not yet exist. + pub fn write(&self) -> Result<(), String> { + let toml_str = toml::to_string_pretty(&self) + .map_err(|err| format!("Could not format TOML: {}", err))?; + let config_file = base_directories()? + .place_config_file(CONFIG_FILE_NAME) + .map_err(|err| format!("Could not write config file: {}", err))?; + + fs::write(&config_file, toml_str).map_err(|err| { + format!( + "Could not write config file to '{}': {}", + config_file.display(), + err + ) + }) + } + /// Return the path to `libyabridge.so`, or a descriptive error if it can't be found. If /// `yabridge_home` is `None`, then we'll search in both `/usr/lib` and /// `$XDG_DATA_HOME/yabridge`. @@ -54,9 +111,7 @@ impl Config { None => { // Search in the two common installation locations if no path was set explicitely let system_path = Path::new("/usr/lib"); - let user_path = BaseDirectories::new() - .map_err(|err| format!("Error while parsing base directories:\n{}", err))? - .get_data_home(); + let user_path = base_directories()?.get_data_home(); for directory in &[system_path, &user_path] { let candidate = directory.join(LIBYABRIDGE_NAME); if candidate.exists() { @@ -77,6 +132,8 @@ impl Config { } /// Specifies how yabridge will be set up for the found plugins. +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "snake_case")] pub enum InstallationMethod { /// Create a copy of `libyabridge.so` for every Windows VST2 plugin .dll file found. After /// updating yabridge, the user will have to rerun `yabridgectl sync` to copy over the new @@ -87,3 +144,10 @@ pub enum InstallationMethod { /// modify the `PATH` environment variable. Symlink, } + +/// Fetch the XDG base directories, converting any error messages if this somehow fails into a +/// printable string to reduce boiler plate. +fn base_directories() -> Result { + BaseDirectories::with_prefix(CONFIG_PREFIX) + .map_err(|err| format!("Error while parsing base directories: {}", err)) +} diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 02a1189e..90faed50 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -16,6 +16,21 @@ mod config; +use config::Config; + fn main() { - println!("Hello, world!"); + // TODO: Remove debug + match Config::read() { + Ok(config) => { + println!("Read config:\n\n{:#?}\n", config); + println!( + "Searching for libyabridge.toml:\n\n{:?}", + config.libyabridge() + ); + } + Err(err) => { + eprintln!("Error while reading config:\n\n{}", err); + std::process::exit(1); + } + } } From 5f2cb434bc2b8e8d450901d99b29687da88f25b2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 14:38:43 +0200 Subject: [PATCH 12/29] Fix searching for libyabridge.so in ~/.local/share --- tools/yabridgectl/src/config.rs | 59 ++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 9a9a49e5..0aa66990 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -21,10 +21,15 @@ use xdg::BaseDirectories; /// The name of the config file, relative to `$XDG_CONFIG_HOME/CONFIG_PREFIX`. const CONFIG_FILE_NAME: &str = "config.toml"; -/// The name of the configuration directory, relative to `$XDG_CONFIG_HOME`. -const CONFIG_PREFIX: &str = "yabridgectl"; +/// 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"; +/// 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"; /// The configuration used for yabridgectl. This will be serialized to and deserialized from /// `$XDG_CONFIG_HOME/yabridge/config.toml`. @@ -41,11 +46,25 @@ pub struct Config { pub plugin_dirs: Vec, } +/// Specifies how yabridge will be set up for the found plugins. +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum InstallationMethod { + /// Create a copy of `libyabridge.so` for every Windows VST2 plugin .dll file found. After + /// updating yabridge, the user will have to rerun `yabridgectl sync` to copy over the new + /// version. + Copy, + /// This will create a symlink to `libyabridge.so` for every VST2 .dll file in the plugin + /// directories. As explained in the readme, this makes updating easier and remvoes the need to + /// modify the `PATH` environment variable. + Symlink, +} + impl Config { /// Try to read the config file, creating a new default file if necessary. This will fail if the /// file could not be created or if it could not be parsed. pub fn read() -> Result { - match base_directories()?.find_config_file(CONFIG_FILE_NAME) { + match yabridge_directories()?.find_config_file(CONFIG_FILE_NAME) { Some(path) => { let toml_str = fs::read_to_string(&path).map_err(|err| { format!( @@ -78,7 +97,7 @@ impl Config { pub fn write(&self) -> Result<(), String> { let toml_str = toml::to_string_pretty(&self) .map_err(|err| format!("Could not format TOML: {}", err))?; - let config_file = base_directories()? + let config_file = yabridgectl_directories()? .place_config_file(CONFIG_FILE_NAME) .map_err(|err| format!("Could not write config file: {}", err))?; @@ -111,7 +130,7 @@ impl Config { None => { // Search in the two common installation locations if no path was set explicitely let system_path = Path::new("/usr/lib"); - let user_path = base_directories()?.get_data_home(); + let user_path = yabridge_directories()?.get_data_home(); for directory in &[system_path, &user_path] { let candidate = directory.join(LIBYABRIDGE_NAME); if candidate.exists() { @@ -131,23 +150,17 @@ impl Config { } } -/// Specifies how yabridge will be set up for the found plugins. -#[derive(Deserialize, Serialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum InstallationMethod { - /// Create a copy of `libyabridge.so` for every Windows VST2 plugin .dll file found. After - /// updating yabridge, the user will have to rerun `yabridgectl sync` to copy over the new - /// version. - Copy, - /// This will create a symlink to `libyabridge.so` for every VST2 .dll file in the plugin - /// directories. As explained in the readme, this makes updating easier and remvoes the need to - /// modify the `PATH` environment variable. - Symlink, -} - -/// Fetch the XDG base directories, converting any error messages if this somehow fails into a -/// printable string to reduce boiler plate. -fn base_directories() -> Result { - BaseDirectories::with_prefix(CONFIG_PREFIX) +/// 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 { + BaseDirectories::with_prefix(YABRIDGE_PREFIX) + .map_err(|err| format!("Error while parsing base directories: {}", err)) +} + +/// 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 { + BaseDirectories::with_prefix(YABRIDGECTL_PREFIX) .map_err(|err| format!("Error while parsing base directories: {}", err)) } From 7a8e0ed5cd492cdb0d5fc2b283ddd669b5715b04 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 13 Jul 2020 19:08:38 +0200 Subject: [PATCH 13/29] Add VST2 file indexing to yabridgectl --- tools/yabridgectl/Cargo.lock | 1 + tools/yabridgectl/Cargo.toml | 1 + tools/yabridgectl/src/files.rs | 106 +++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 tools/yabridgectl/src/files.rs diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index 755c06f8..c05c3344 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -440,6 +440,7 @@ version = "1.2.1" dependencies = [ "aho-corasick", "clap", + "lazy_static", "rayon", "serde", "serde_derive", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index 5594950d..f6f2c5f0 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -12,6 +12,7 @@ license = "GPL-3.0-or-later" [dependencies] aho-corasick = "0.7.13" clap = "3.0.0-beta.1" +lazy_static = "1.4.0" rayon = "1.3.1" serde = "1.0.114" serde_derive = "1.0.114" diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs new file mode 100644 index 00000000..efc965e6 --- /dev/null +++ b/tools/yabridgectl/src/files.rs @@ -0,0 +1,106 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Functions to index plugins and to set up yabridge for those plugins. + +use aho_corasick::AhoCorasick; +use lazy_static::lazy_static; +use rayon::prelude::*; +use std::path::{Path, PathBuf}; +use std::process::Command; +use walkdir::WalkDir; + +/// Stores the results from searching for Windows VST plugin `.dll` files and native Linux `.so` +/// files inside of a directory. These `.so` files are kept track of so we can report the current +/// installation status and to be able to purge orphan files. +#[derive(Debug)] +pub struct SearchResults { + /// Absolute paths to the found VST2 `.dll` files. + pub vst2_files: Vec, + /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or + /// a regular file. + pub so_files: Vec, +} + +#[derive(Debug)] +pub enum FoundFile { + Symlink(PathBuf), + Regular(PathBuf), +} + +/// Search for Windows VST2 plugins and .so files under a directory. This will return an error if +/// the directory does not exist, or if `winedump` could not be found. +pub fn index(directory: &Path) -> Result { + // First we'll find all .dll and .so files in the directory + let mut dll_files: Vec = Vec::new(); + let mut so_files: Vec = Vec::new(); + // XXX: We're silently skipping directories and files we don't have permission to read. This + // sounds like the expected behavior, but I"m not entirely sure. + for entry in WalkDir::new(directory) + .follow_links(true) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|x| !x.file_type().is_dir()) + { + match entry.path().extension().and_then(|os| os.to_str()) { + Some("dll") => dll_files.push(entry.into_path()), + Some("so") => { + if entry.path_is_symlink() { + so_files.push(FoundFile::Symlink(entry.into_path())); + } else { + so_files.push(FoundFile::Regular(entry.into_path())); + } + } + _ => (), + } + } + + // Then we'll filter out any .dll files that are not VST2 plugins by checking whether the + // exptected entry points for VST2 plugins are present + lazy_static! { + static ref VST2_AUTOMATON: AhoCorasick = + AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]); + } + + let vst2_files: Vec = dll_files + .into_par_iter() + .map(|path| { + let exported_functions = Command::new("winedump") + .arg("-j") + .arg("export") + .arg(&path) + .output()? + .stdout; + + Ok((path, exported_functions)) + }) + .filter_map(|result| match result { + Ok((path, exported_functions)) => { + if VST2_AUTOMATON.is_match(exported_functions) { + Some(Ok(path)) + } else { + None + } + } + Err(err) => Some(Err(err)), + }) + .collect::>()?; + + Ok(SearchResults { + vst2_files, + so_files, + }) +} From 286135400fc5fcf7be2402647cfa387b3e386571 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 14:59:26 +0200 Subject: [PATCH 14/29] Query installation status and orphan files --- tools/yabridgectl/src/files.rs | 53 +++++++++++++++++++++++++++++++++- tools/yabridgectl/src/main.rs | 1 + 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index efc965e6..f81d1045 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -19,6 +19,7 @@ use aho_corasick::AhoCorasick; use lazy_static::lazy_static; use rayon::prelude::*; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::process::Command; use walkdir::WalkDir; @@ -35,12 +36,62 @@ pub struct SearchResults { pub so_files: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum FoundFile { Symlink(PathBuf), Regular(PathBuf), } +impl SearchResults { + /// For every found VST2 plugin, find the associated copy or symlink of `libyabridge.so`. The + /// returned hashmap will contain a `None` value for plugins that have not yet been set up. + /// + /// These two functions could be combined into a single function, but speed isn't really an + /// issue here and it's a bit more organized this way. + pub fn installation_status(&self) -> HashMap<&Path, Option<&FoundFile>> { + let so_files: HashMap<&Path, &FoundFile> = self + .so_files + .iter() + .map(|file| (file.path(), file)) + .collect(); + + self.vst2_files + .iter() + .map( + |path| match so_files.get(path.with_extension("so").as_path()) { + Some(&file) => (path.as_path(), Some(file)), + None => (path.as_path(), None), + }, + ) + .collect() + } + + /// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file. + pub fn orphans(&self) -> Vec<&FoundFile> { + // We need to store these in a map so we can easily entries with corresponding `.dll` files + let mut orphans: HashMap<&Path, &FoundFile> = self + .so_files + .iter() + .map(|file| (file.path(), file)) + .collect(); + for vst2_path in &self.vst2_files { + orphans.remove(vst2_path.with_extension("so").as_path()); + } + + orphans.into_iter().map(|(_, file)| file).collect() + } +} + +impl FoundFile { + /// Return the path of a found `.so` file. + pub fn path(&self) -> &Path { + match &self { + FoundFile::Symlink(path) => path, + FoundFile::Regular(path) => path, + } + } +} + /// Search for Windows VST2 plugins and .so files under a directory. This will return an error if /// the directory does not exist, or if `winedump` could not be found. pub fn index(directory: &Path) -> Result { diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 90faed50..b3dc57d4 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . mod config; +mod files; use config::Config; From f288bbb9807fb10b289dff28c034cbedf0f8f64c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 16:36:56 +0200 Subject: [PATCH 15/29] Fix yabridgectl config file loading --- tools/yabridgectl/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 0aa66990..f09db536 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -64,7 +64,7 @@ impl Config { /// Try to read the config file, creating a new default file if necessary. This will fail if the /// file could not be created or if it could not be parsed. pub fn read() -> Result { - match yabridge_directories()?.find_config_file(CONFIG_FILE_NAME) { + match yabridgectl_directories()?.find_config_file(CONFIG_FILE_NAME) { Some(path) => { let toml_str = fs::read_to_string(&path).map_err(|err| { format!( From 3a7fd089b6320d0a3aac1663cff67171c404ec3d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 16:46:49 +0200 Subject: [PATCH 16/29] Add subcommands for managing directories --- tools/yabridgectl/Cargo.toml | 2 +- tools/yabridgectl/src/config.rs | 2 +- tools/yabridgectl/src/main.rs | 65 ++++++++++++++++++++++++++++----- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index f6f2c5f0..b67b1f93 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -5,7 +5,7 @@ name = "yabridgectl" version = "1.2.1" authors = ["Robbert van der Helm "] edition = "2018" -description = "Optional command line helper to set up yabridge" +description = "Optional utility to help set up yabridge" repository = "https://github.com/robbert-vdh/yabridge" license = "GPL-3.0-or-later" diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index f09db536..18e061ed 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -75,7 +75,7 @@ impl Config { })?; Ok(toml::from_str(&toml_str) - .map_err(|err| format!("Could not parse TOML: {:#?}", err))?) + .map_err(|err| format!("Could not parse TOML: {}", err))?) } None => { let defaults = Config { diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index b3dc57d4..9db334a2 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -14,24 +14,69 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use clap::{app_from_crate, App, Arg}; +use config::Config; +use std::path::Path; + mod config; mod files; -use config::Config; +// TODO: Naming and descriptions could be made clearer fn main() { - // TODO: Remove debug - match Config::read() { - Ok(config) => { - println!("Read config:\n\n{:#?}\n", config); - println!( - "Searching for libyabridge.toml:\n\n{:?}", - config.libyabridge() - ); - } + let config = match Config::read() { + Ok(config) => config, Err(err) => { eprintln!("Error while reading config:\n\n{}", err); std::process::exit(1); } + }; + + // Used for validation in `yabridgectl rm ` + let plugin_directories: Vec<&str> = config + .plugin_dirs + .iter() + .map(|path| path.to_str().expect("Path contains invalid unicode")) + .collect(); + + let matches = app_from_crate!() + .subcommand( + App::new("add").about("Add a plugin install location").arg( + Arg::with_name("path") + .about("Path to a directory containing Windows VST plugins") + .validator(validate_path) + .takes_value(true) + .required(true), + ), + ) + .subcommand( + App::new("rm") + .about("Remove a plugin install location") + .arg( + Arg::with_name("path") + .about("Path to a directory") + .possible_values(&plugin_directories) + .takes_value(true) + .required(true), + ), + ) + .subcommand(App::new("list").about("List the plugin directories")) + .get_matches(); + + // TODO: Modify the config file using these options + unimplemented!(); +} + +/// Verify that a path exists, used for validating arguments. +fn validate_path(path: &str) -> Result<(), String> { + let path = Path::new(path); + + if path.exists() { + Ok(()) + } else { + Err(format!( + "File or directory '{}' could not be found", + path.display() + )) } } From b8eb031c77fbf08bfa0c99c6ad65ff61d3478bdf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 17:16:30 +0200 Subject: [PATCH 17/29] Store paths in a BTreeSet To avoid duplicates and to ensure that the order is stable. --- tools/yabridgectl/src/config.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 18e061ed..65cea29d 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -15,6 +15,7 @@ // along with this program. If not, see . use serde_derive::{Deserialize, Serialize}; +use std::collections::BTreeSet; use std::fs; use std::path::{Path, PathBuf}; use xdg::BaseDirectories; @@ -42,8 +43,9 @@ pub struct Config { /// look in `/usr/lib` and `$XDG_DATA_HOME/yabridge` since those are the expected locations for /// yabridge to be installed in. pub yabridge_home: Option, - /// Directories to search for Windows VST plugins. - pub plugin_dirs: Vec, + /// Directories to search for Windows VST plugins. We're using an ordered set here out of + /// convenience so we can't get duplicates and the config file is always sorted. + pub plugin_dirs: BTreeSet, } /// Specifies how yabridge will be set up for the found plugins. @@ -81,7 +83,7 @@ impl Config { let defaults = Config { method: InstallationMethod::Copy, yabridge_home: None, - plugin_dirs: Vec::new(), + plugin_dirs: BTreeSet::new(), }; // If no existing config file exists, then write a new config file with default From 748d52f62a2c0b63ca92d33d689634148a7fdfdc Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 17:20:05 +0200 Subject: [PATCH 18/29] Implement the add, rm and list options --- tools/yabridgectl/src/main.rs | 49 ++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 9db334a2..058f52d3 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -14,17 +14,28 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use clap::{app_from_crate, App, Arg}; +use clap::{app_from_crate, App, AppSettings, Arg}; use config::Config; -use std::path::Path; +use std::path::{Path, PathBuf}; +use std::process::exit; mod config; mod files; +// TODO: Add the different `yabridgectl set` options +// TODO: Add `yabridgectl status` +// TODO: Add `yabridgectl sync` // TODO: Naming and descriptions could be made clearer +// TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell +// TODO: Also give a good error if winedump cannot be found +// TODO: Check for left over files when removign directory +// TODO: Warn about left over files if not using --prune +// TODO: Reward parts of the readme +// TODO: Record .dll files processed, .dll files skipped and orphan .so files. Print a summary of +// the work done, and allow a --verbose option to print everything. fn main() { - let config = match Config::read() { + let mut config = match Config::read() { Ok(config) => config, Err(err) => { eprintln!("Error while reading config:\n\n{}", err); @@ -40,6 +51,7 @@ fn main() { .collect(); let matches = app_from_crate!() + .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand( App::new("add").about("Add a plugin install location").arg( Arg::with_name("path") @@ -60,11 +72,36 @@ fn main() { .required(true), ), ) - .subcommand(App::new("list").about("List the plugin directories")) + .subcommand(App::new("list").about("List the plugin install locations")) .get_matches(); - // TODO: Modify the config file using these options - unimplemented!(); + match matches.subcommand() { + ("add", Some(options)) => { + config + .plugin_dirs + .insert(options.value_of_t_or_exit("path")); + if let Err(err) = config.write() { + eprintln!("Error while writing config file: {}", err); + exit(1); + }; + } + ("rm", Some(options)) => { + // We've already verified that this path is in `config.plugin_dirs` + config + .plugin_dirs + .remove(&options.value_of_t_or_exit::("path")); + if let Err(err) = config.write() { + eprintln!("Error while writing config file: {}", err); + exit(1); + }; + } + ("list", _) => { + for directory in config.plugin_dirs { + println!("{}", directory.display()); + } + } + _ => unreachable!(), + } } /// Verify that a path exists, used for validating arguments. From d84014203626cc72518cc3a2b2d4cb7912028bfc Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 17:52:08 +0200 Subject: [PATCH 19/29] Move the subcommands to functions --- tools/yabridgectl/src/main.rs | 52 ++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 058f52d3..9bc9bc4a 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -76,34 +76,42 @@ fn main() { .get_matches(); match matches.subcommand() { - ("add", Some(options)) => { - config - .plugin_dirs - .insert(options.value_of_t_or_exit("path")); - if let Err(err) = config.write() { - eprintln!("Error while writing config file: {}", err); - exit(1); - }; - } + ("add", Some(options)) => add_directory(&mut config, options.value_of_t_or_exit("path")), ("rm", Some(options)) => { - // We've already verified that this path is in `config.plugin_dirs` - config - .plugin_dirs - .remove(&options.value_of_t_or_exit::("path")); - if let Err(err) = config.write() { - eprintln!("Error while writing config file: {}", err); - exit(1); - }; - } - ("list", _) => { - for directory in config.plugin_dirs { - println!("{}", directory.display()); - } + remove_directory(&mut config, &options.value_of_t_or_exit::("path")) } + ("list", _) => list_directories(&config), _ => unreachable!(), } } +/// Add a direcotry to the plugin locations. Duplicates get ignord because we're using ordered sets. +fn add_directory(config: &mut Config, path: PathBuf) { + config.plugin_dirs.insert(path); + if let Err(err) = config.write() { + eprintln!("Error while writing config file: {}", err); + exit(1); + }; +} + +/// Remove a direcotry to the plugin locations. The path is assumed to be part of +/// `config.plugin_dirs`, otherwise this si silently ignored. +fn remove_directory(config: &mut Config, path: &Path) { + // We've already verified that this path is in `config.plugin_dirs` + config.plugin_dirs.remove(path); + if let Err(err) = config.write() { + eprintln!("Error while writing config file: {}", err); + exit(1); + }; +} + +/// List the plugin locations. +fn list_directories(config: &Config) { + for directory in &config.plugin_dirs { + println!("{}", directory.display()); + } +} + /// Verify that a path exists, used for validating arguments. fn validate_path(path: &str) -> Result<(), String> { let path = Path::new(path); From 26f26fc21c4544a56f4bc0830d95a9e4e8fcb998 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 18:35:54 +0200 Subject: [PATCH 20/29] Implement the status command --- tools/yabridgectl/Cargo.lock | 12 +++++++++ tools/yabridgectl/Cargo.toml | 1 + tools/yabridgectl/README.md | 4 +-- tools/yabridgectl/src/config.rs | 24 ++++++++++++++++- tools/yabridgectl/src/main.rs | 46 ++++++++++++++++++++++++++++++--- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index c05c3344..90a9ac07 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -70,6 +70,17 @@ dependencies = [ "syn", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -440,6 +451,7 @@ version = "1.2.1" dependencies = [ "aho-corasick", "clap", + "colored", "lazy_static", "rayon", "serde", diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index b67b1f93..cf7aa630 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -11,6 +11,7 @@ license = "GPL-3.0-or-later" [dependencies] aho-corasick = "0.7.13" +colored = "2.0.0" clap = "3.0.0-beta.1" lazy_static = "1.4.0" rayon = "1.3.1" diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index cdbdad2b..97f460d7 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -22,7 +22,7 @@ to use a custom installation directory instead. yabridgectl set --path= ``` -### Installation modes +### Installation methods By default, yabridgectl will use the copy-based installation method for yabridge since this installation method works everywhere. If you are using a DAW that @@ -30,7 +30,7 @@ supports individually sandboxed plugins, then you can choose between using copies and symlinks using the command below. ```shell -yabridgectl set --mode= +yabridgectl set --method= ``` ### Managing directories diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 65cea29d..d14a3f0d 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -14,12 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use rayon::prelude::*; use serde_derive::{Deserialize, Serialize}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt::Display; use std::fs; use std::path::{Path, PathBuf}; 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"; /// The name of the XDG base directory prefix for yabridgectl, relative to `$XDG_CONFIG_HOME` and @@ -62,6 +66,15 @@ pub enum InstallationMethod { Symlink, } +impl Display for InstallationMethod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + InstallationMethod::Copy => write!(f, "copy"), + InstallationMethod::Symlink => write!(f, "symlink"), + } + } +} + impl Config { /// Try to read the config file, creating a new default file if necessary. This will fail if the /// file could not be created or if it could not be parsed. @@ -150,6 +163,15 @@ impl Config { } } } + + /// Search for VST2 plugins in all of the registered plugins directories. This will return an + /// error if `winedump` could not be called. + pub fn index_directories(&self) -> Result, std::io::Error> { + self.plugin_dirs + .par_iter() + .map(|path| files::index(path).map(|search_results| (path.as_path(), search_results))) + .collect() + } } /// Fetch the XDG base directories for yabridge's own files, converting any error messages if this diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 9bc9bc4a..a1a73939 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -15,20 +15,21 @@ // along with this program. If not, see . use clap::{app_from_crate, App, AppSettings, Arg}; -use config::Config; +use colored::Colorize; use std::path::{Path, PathBuf}; use std::process::exit; +use crate::config::Config; +use crate::files::FoundFile; + mod config; mod files; // TODO: Add the different `yabridgectl set` options -// TODO: Add `yabridgectl status` // TODO: Add `yabridgectl sync` // TODO: Naming and descriptions could be made clearer // TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell -// TODO: Also give a good error if winedump cannot be found -// TODO: Check for left over files when removign directory +// TODO: Check for left over files when removing directory // TODO: Warn about left over files if not using --prune // TODO: Reward parts of the readme // TODO: Record .dll files processed, .dll files skipped and orphan .so files. Print a summary of @@ -73,6 +74,7 @@ fn main() { ), ) .subcommand(App::new("list").about("List the plugin install locations")) + .subcommand(App::new("status").about("Show the installation status for all plugins")) .get_matches(); match matches.subcommand() { @@ -81,6 +83,7 @@ fn main() { remove_directory(&mut config, &options.value_of_t_or_exit::("path")) } ("list", _) => list_directories(&config), + ("status", _) => show_status(&config), _ => unreachable!(), } } @@ -112,6 +115,41 @@ fn list_directories(config: &Config) { } } +/// Print the current configuration and the installation status for all found plugins. +fn show_status(config: &Config) { + match config.index_directories() { + Ok(results) => { + println!( + "yabridge path: {}", + config + .yabridge_home + .as_ref() + .map(|path| format!("'{}'", path.display())) + .unwrap_or(String::from("")) + ); + println!("installation method: {}", config.method); + + for (path, search_results) in results { + println!("\n{}:", path.display()); + + for (plugin, status) in search_results.installation_status() { + let status_str = match status { + Some(FoundFile::Regular(_)) => "copy".green(), + Some(FoundFile::Symlink(_)) => "symlink".green(), + None => "not installed".red(), + }; + + println!(" {} :: {}", plugin.display(), status_str); + } + } + } + Err(err) => { + eprintln!("Error while searching for plugins: {}", err); + exit(1); + } + } +} + /// Verify that a path exists, used for validating arguments. fn validate_path(path: &str) -> Result<(), String> { let path = Path::new(path); From a5c626cb242f274b30f460243f754605f635dc0e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 18:41:38 +0200 Subject: [PATCH 21/29] Ensure installation status order is stable --- tools/yabridgectl/src/files.rs | 4 ++-- tools/yabridgectl/src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index f81d1045..b3e93cc3 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -19,7 +19,7 @@ use aho_corasick::AhoCorasick; use lazy_static::lazy_static; use rayon::prelude::*; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::path::{Path, PathBuf}; use std::process::Command; use walkdir::WalkDir; @@ -48,7 +48,7 @@ impl SearchResults { /// /// These two functions could be combined into a single function, but speed isn't really an /// issue here and it's a bit more organized this way. - pub fn installation_status(&self) -> HashMap<&Path, Option<&FoundFile>> { + pub fn installation_status(&self) -> BTreeMap<&Path, Option<&FoundFile>> { let so_files: HashMap<&Path, &FoundFile> = self .so_files .iter() diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index a1a73939..9f8703e8 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -30,7 +30,6 @@ mod files; // TODO: Naming and descriptions could be made clearer // TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell // TODO: Check for left over files when removing directory -// TODO: Warn about left over files if not using --prune // TODO: Reward parts of the readme // TODO: Record .dll files processed, .dll files skipped and orphan .so files. Print a summary of // the work done, and allow a --verbose option to print everything. @@ -101,6 +100,7 @@ fn add_directory(config: &mut Config, path: PathBuf) { /// `config.plugin_dirs`, otherwise this si silently ignored. fn remove_directory(config: &mut Config, path: &Path) { // We've already verified that this path is in `config.plugin_dirs` + // XXS: Would it be a good idea to warn about leftover .so files? config.plugin_dirs.remove(path); if let Err(err) = config.write() { eprintln!("Error while writing config file: {}", err); From b94af4e7aec831f33cd48ec733d05222800f7ac6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 21:01:52 +0200 Subject: [PATCH 22/29] Print path to libyabridge.so in yabridgectl status --- tools/yabridgectl/src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 9f8703e8..89a9a033 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -125,7 +125,14 @@ fn show_status(config: &Config) { .yabridge_home .as_ref() .map(|path| format!("'{}'", path.display())) - .unwrap_or(String::from("")) + .unwrap_or_else(|| String::from("")) + ); + println!( + "libyabridge.so: {}", + config + .libyabridge() + .map(|path| format!("'{}'", path.display())) + .unwrap_or_else(|_| format!("{}", "".red())) ); println!("installation method: {}", config.method); From 53ebfcd463b744c630241688fd9018df74e8252d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 14 Jul 2020 22:26:23 +0200 Subject: [PATCH 23/29] Implement yabridgectl sync --- tools/yabridgectl/README.md | 2 +- tools/yabridgectl/src/config.rs | 4 +- tools/yabridgectl/src/files.rs | 7 +- tools/yabridgectl/src/main.rs | 203 ++++++++++++++++++++++++++------ 4 files changed, 177 insertions(+), 39 deletions(-) diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index 97f460d7..fe772045 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -63,7 +63,7 @@ the `--prune` option. # Set up copies or symlinks of yabridge for all plugins under the watched directories yabridgectl sync # Set up yabridge, and also remove any '.so' still leftover after removing a plugin -yabridgectl sync --purge +yabridgectl sync --prune ``` ## Building diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index d14a3f0d..9c54bb4c 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -136,7 +136,7 @@ impl Config { Ok(candidate) } else { Err(format!( - "Could not find {} in '{}'.", + "Could not find '{}' in '{}'.", LIBYABRIDGE_NAME, directory.display() )) @@ -154,7 +154,7 @@ impl Config { } Err(format!( - "Could not find {} in either '{}' or '{}'. You can tell yabridgectl where \ + "Could not find '{}' in either '{}' or '{}'. You can tell yabridgectl where \ to search for it using 'yabridgectl set --path='.", LIBYABRIDGE_NAME, system_path.display(), diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index b3e93cc3..35be3ef0 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -26,11 +26,14 @@ use walkdir::WalkDir; /// Stores the results from searching for Windows VST plugin `.dll` files and native Linux `.so` /// files inside of a directory. These `.so` files are kept track of so we can report the current -/// installation status and to be able to purge orphan files. +/// installation status and to be able to prune orphan files. #[derive(Debug)] pub struct SearchResults { /// Absolute paths to the found VST2 `.dll` files. pub vst2_files: Vec, + /// The number of skipped `.dll` files. Only used for printing statistics, so we don't keep + /// track of the exact files. + pub num_skipped_files: usize, /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or /// a regular file. pub so_files: Vec, @@ -126,6 +129,7 @@ pub fn index(directory: &Path) -> Result { AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]); } + let dll_file_count = dll_files.len(); let vst2_files: Vec = dll_files .into_par_iter() .map(|path| { @@ -151,6 +155,7 @@ pub fn index(directory: &Path) -> Result { .collect::>()?; Ok(SearchResults { + num_skipped_files: dll_file_count - vst2_files.len(), vst2_files, so_files, }) diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 89a9a033..c723c79b 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -16,17 +16,18 @@ use clap::{app_from_crate, App, AppSettings, Arg}; use colored::Colorize; +use std::fs; +use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; use std::process::exit; -use crate::config::Config; +use crate::config::{Config, InstallationMethod}; use crate::files::FoundFile; mod config; mod files; // TODO: Add the different `yabridgectl set` options -// TODO: Add `yabridgectl sync` // TODO: Naming and descriptions could be made clearer // TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell // TODO: Check for left over files when removing directory @@ -74,6 +75,22 @@ fn main() { ) .subcommand(App::new("list").about("List the plugin install locations")) .subcommand(App::new("status").about("Show the installation status for all plugins")) + .subcommand( + App::new("sync") + .about("Set up or update yabridge for all plugins") + .arg( + Arg::with_name("prune") + .short('p') + .long("prune") + .about("Remove unrelated or leftover '.so' files"), + ) + .arg( + Arg::with_name("verbose") + .short('v') + .long("verbose") + .about("Print every plugin yabridge has been set up for"), + ), + ) .get_matches(); match matches.subcommand() { @@ -83,6 +100,11 @@ fn main() { } ("list", _) => list_directories(&config), ("status", _) => show_status(&config), + ("sync", Some(options)) => do_sync( + &config, + options.is_present("prune"), + options.is_present("verbose"), + ), _ => unreachable!(), } } @@ -117,46 +139,157 @@ fn list_directories(config: &Config) { /// Print the current configuration and the installation status for all found plugins. fn show_status(config: &Config) { - match config.index_directories() { - Ok(results) => { - println!( - "yabridge path: {}", - config - .yabridge_home - .as_ref() - .map(|path| format!("'{}'", path.display())) - .unwrap_or_else(|| String::from("")) - ); - println!( - "libyabridge.so: {}", - config - .libyabridge() - .map(|path| format!("'{}'", path.display())) - .unwrap_or_else(|_| format!("{}", "".red())) - ); - println!("installation method: {}", config.method); - - for (path, search_results) in results { - println!("\n{}:", path.display()); - - for (plugin, status) in search_results.installation_status() { - let status_str = match status { - Some(FoundFile::Regular(_)) => "copy".green(), - Some(FoundFile::Symlink(_)) => "symlink".green(), - None => "not installed".red(), - }; - - println!(" {} :: {}", plugin.display(), status_str); - } - } - } + let results = match config.index_directories() { + Ok(results) => results, Err(err) => { eprintln!("Error while searching for plugins: {}", err); exit(1); } + }; + + println!( + "yabridge path: {}", + config + .yabridge_home + .as_ref() + .map(|path| format!("'{}'", path.display())) + .unwrap_or_else(|| String::from("")) + ); + println!( + "libyabridge.so: {}", + config + .libyabridge() + .map(|path| format!("'{}'", path.display())) + .unwrap_or_else(|_| format!("{}", "".red())) + ); + println!("installation method: {}", config.method); + + for (path, search_results) in results { + println!("\n{}:", path.display()); + + for (plugin, status) in search_results.installation_status() { + let status_str = match status { + Some(FoundFile::Regular(_)) => "copy".green(), + Some(FoundFile::Symlink(_)) => "symlink".green(), + None => "not installed".red(), + }; + + println!(" {} :: {}", plugin.display(), status_str); + } } } +/// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan +/// `.so` files if the prune option is set. +fn do_sync(config: &Config, prune: bool, verbose: bool) { + let libyabridge_path = match config.libyabridge() { + Ok(path) => { + println!("Using '{}'\n", path.display()); + path + } + Err(err) => { + // The error messages here are already formatted + eprintln!("{}", err); + exit(1); + } + }; + + let results = match config.index_directories() { + Ok(results) => results, + Err(err) => { + eprintln!("Error while searching for plugins: {}", err); + exit(1); + } + }; + + // Keep track of some global statistics + let mut num_installed = 0; + let mut num_skipped = 0; + let mut orphan_so_files: Vec = Vec::new(); + for (path, search_results) in results { + orphan_so_files.extend(search_results.orphans().into_iter().cloned()); + num_installed += search_results.vst2_files.len(); + num_skipped += search_results.num_skipped_files; + + if verbose { + println!("{}:", path.display()); + } + for plugin in search_results.vst2_files { + // If the target file already exists, we'll remove it first to prevent issues with + // mixing symlinks and regular files + let target_path = plugin.with_extension("so"); + if target_path.exists() { + fs::remove_file(&target_path).unwrap_or_else(|err| { + eprintln!("Could not remove '{}': {}", target_path.display(), err); + exit(1); + }); + } + + match config.method { + InstallationMethod::Copy => { + fs::copy(&libyabridge_path, &target_path).unwrap_or_else(|err| { + eprintln!( + "Error copying '{}' to '{}': {}", + libyabridge_path.display(), + target_path.display(), + err + ); + exit(1); + }); + } + InstallationMethod::Symlink => { + symlink(&libyabridge_path, &target_path).unwrap_or_else(|err| { + eprintln!( + "Error symlinking '{}' to '{}': {}", + libyabridge_path.display(), + target_path.display(), + err + ); + exit(1); + }); + } + } + + if verbose { + println!(" {}", plugin.display()); + } + } + if verbose { + println!(); + } + } + + if !orphan_so_files.is_empty() { + if prune { + println!("Removing {} leftover '.so' file(s):", orphan_so_files.len()); + } else { + println!( + "Found {} leftover '.so' file(s), rerun with the '--prune' option to remove them:", + orphan_so_files.len() + ); + } + + for file in orphan_so_files { + let path = file.path(); + + println!("- {}", path.display()); + if prune { + fs::remove_file(path).unwrap_or_else(|err| { + eprintln!("Error while trying to remove '{}': {}", path.display(), err); + exit(1); + }); + } + } + + println!(); + } + + println!( + "Finished setting up {} plugins, skipped {} non-plugin '.dll' files.", + num_installed, num_skipped + ) +} + /// Verify that a path exists, used for validating arguments. fn validate_path(path: &str) -> Result<(), String> { let path = Path::new(path); From f9deb8c201720e161bb8659b99206c476e2a39fc Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 15 Jul 2020 14:03:50 +0200 Subject: [PATCH 24/29] Print skipped files on `yabridgectl sync -v` --- tools/yabridgectl/src/files.rs | 38 +++++++++++++++++----------------- tools/yabridgectl/src/main.rs | 22 ++++++++++++++------ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 35be3ef0..05c2dc9e 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -31,9 +31,9 @@ use walkdir::WalkDir; pub struct SearchResults { /// Absolute paths to the found VST2 `.dll` files. pub vst2_files: Vec, - /// The number of skipped `.dll` files. Only used for printing statistics, so we don't keep - /// track of the exact files. - pub num_skipped_files: usize, + /// `.dll` files skipped over during the serach. Used for printing statistics and shown when + /// running `yabridgectl sync --verbose`. + pub skipped_files: Vec, /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or /// a regular file. pub so_files: Vec, @@ -122,15 +122,15 @@ pub fn index(directory: &Path) -> Result { } } - // Then we'll filter out any .dll files that are not VST2 plugins by checking whether the - // exptected entry points for VST2 plugins are present lazy_static! { static ref VST2_AUTOMATON: AhoCorasick = AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]); } - let dll_file_count = dll_files.len(); - let vst2_files: Vec = dll_files + // THne we'll 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. The boolean flag in + // this vector indicates whether it is a VST2 plugin. + let dll_files: Vec<(PathBuf, bool)> = dll_files .into_par_iter() .map(|path| { let exported_functions = Command::new("winedump") @@ -140,23 +140,23 @@ pub fn index(directory: &Path) -> Result { .output()? .stdout; - Ok((path, exported_functions)) - }) - .filter_map(|result| match result { - Ok((path, exported_functions)) => { - if VST2_AUTOMATON.is_match(exported_functions) { - Some(Ok(path)) - } else { - None - } - } - Err(err) => Some(Err(err)), + Ok((path, VST2_AUTOMATON.is_match(exported_functions))) }) .collect::>()?; + let mut vst2_files = Vec::new(); + let mut skipped_files = Vec::new(); + for (path, is_vst2_plugin) in dll_files { + if is_vst2_plugin { + vst2_files.push(path); + } else { + skipped_files.push(path); + } + } + Ok(SearchResults { - num_skipped_files: dll_file_count - vst2_files.len(), vst2_files, + skipped_files, so_files, }) } diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index c723c79b..5496577c 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -32,8 +32,6 @@ mod files; // TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell // TODO: Check for left over files when removing directory // TODO: Reward parts of the readme -// TODO: Record .dll files processed, .dll files skipped and orphan .so files. Print a summary of -// the work done, and allow a --verbose option to print everything. fn main() { let mut config = match Config::read() { @@ -204,12 +202,12 @@ fn do_sync(config: &Config, prune: bool, verbose: bool) { // Keep track of some global statistics let mut num_installed = 0; - let mut num_skipped = 0; + let mut skipped_dll_files: Vec = Vec::new(); let mut orphan_so_files: Vec = Vec::new(); for (path, search_results) in results { - orphan_so_files.extend(search_results.orphans().into_iter().cloned()); num_installed += search_results.vst2_files.len(); - num_skipped += search_results.num_skipped_files; + orphan_so_files.extend(search_results.orphans().into_iter().cloned()); + skipped_dll_files.extend(search_results.skipped_files); if verbose { println!("{}:", path.display()); @@ -259,6 +257,18 @@ fn do_sync(config: &Config, prune: bool, verbose: bool) { } } + // We'll print the skipped files all at once to prevetn clutter + let num_skipped_files = skipped_dll_files.len(); + if verbose && !skipped_dll_files.is_empty() { + println!("Skipped files:"); + for path in skipped_dll_files { + println!("- {}", path.display()); + } + println!(); + } + + // Always warn about leftover files sicne those might cause warnings or errors when a VST host + // tries to load them if !orphan_so_files.is_empty() { if prune { println!("Removing {} leftover '.so' file(s):", orphan_so_files.len()); @@ -286,7 +296,7 @@ fn do_sync(config: &Config, prune: bool, verbose: bool) { println!( "Finished setting up {} plugins, skipped {} non-plugin '.dll' files.", - num_installed, num_skipped + num_installed, num_skipped_files ) } From fefce517d099526265a374246d45afe87bbdfc1b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 15 Jul 2020 16:10:35 +0200 Subject: [PATCH 25/29] Wrap the help text to the terminal width --- tools/yabridgectl/Cargo.lock | 53 +++++++++++++++++++++++++++++++++--- tools/yabridgectl/Cargo.toml | 2 +- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/tools/yabridgectl/Cargo.lock b/tools/yabridgectl/Cargo.lock index 90a9ac07..8c91f2d0 100644 --- a/tools/yabridgectl/Cargo.lock +++ b/tools/yabridgectl/Cargo.lock @@ -17,7 +17,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -51,6 +51,7 @@ dependencies = [ "lazy_static", "os_str_bytes", "strsim", + "term_size 1.0.0-beta1", "termcolor", "textwrap", "unicode-width", @@ -78,7 +79,7 @@ checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" dependencies = [ "atty", "lazy_static", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -162,6 +163,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -340,6 +351,27 @@ dependencies = [ "syn", ] +[[package]] +name = "term_size" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "term_size" +version = "1.0.0-beta1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a17d8699e154863becdf18e4fd28bd0be27ca72856f54daf75c00f2566898f" +dependencies = [ + "kernel32-sys", + "libc", + "winapi 0.2.8", +] + [[package]] name = "termcolor" version = "1.1.0" @@ -355,6 +387,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ + "term_size 0.3.2", "unicode-width", ] @@ -404,10 +437,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", - "winapi", + "winapi 0.3.9", "winapi-util", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -418,6 +457,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -430,7 +475,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] diff --git a/tools/yabridgectl/Cargo.toml b/tools/yabridgectl/Cargo.toml index cf7aa630..d3d7f579 100644 --- a/tools/yabridgectl/Cargo.toml +++ b/tools/yabridgectl/Cargo.toml @@ -12,7 +12,7 @@ license = "GPL-3.0-or-later" [dependencies] aho-corasick = "0.7.13" colored = "2.0.0" -clap = "3.0.0-beta.1" +clap = { version = "3.0.0-beta.1", features = ["wrap_help"] } lazy_static = "1.4.0" rayon = "1.3.1" serde = "1.0.114" From ac5c6bab671cce9d39ab1aa6d511efea7c07d706 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 15 Jul 2020 16:10:52 +0200 Subject: [PATCH 26/29] Show the installation method used --- tools/yabridgectl/src/config.rs | 10 ++++++++++ tools/yabridgectl/src/main.rs | 8 +++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 9c54bb4c..d09b0318 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -66,6 +66,16 @@ pub enum InstallationMethod { Symlink, } +impl InstallationMethod { + /// The plural term for this installation methodd, using in string formatting. + pub fn plural(&self) -> &str { + match &self { + InstallationMethod::Copy => "copies", + InstallationMethod::Symlink => "symlinks", + } + } +} + impl Display for InstallationMethod { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self { diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 5496577c..f571788d 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -86,7 +86,7 @@ fn main() { Arg::with_name("verbose") .short('v') .long("verbose") - .about("Print every plugin yabridge has been set up for"), + .about("Print information about plugins being set up or skipped"), ), ) .get_matches(); @@ -295,8 +295,10 @@ fn do_sync(config: &Config, prune: bool, verbose: bool) { } println!( - "Finished setting up {} plugins, skipped {} non-plugin '.dll' files.", - num_installed, num_skipped_files + "Finished setting up {} plugins using {}, skipped {} non-plugin '.dll' files.", + num_installed, + config.method.plural(), + num_skipped_files ) } From cb5944b1281fe34c3455cd531db22e3fbe094d7d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 15 Jul 2020 16:56:48 +0200 Subject: [PATCH 27/29] Mention the bash snippets in yabridgectl readme --- README.md | 3 +++ tools/yabridgectl/README.md | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/README.md b/README.md index 3cd92624..db928931 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ Please let me know if there are any issues with other VST hosts. ## Usage +TODO: Refactor these sections to refer to yabridgectl for most of the setup. +Manual setup can be moved to its own section. + You can either download a prebuilt version of yabridge through the GitHub [releases](https://github.com/robbert-vdh/yabridge/releases) section, or you can compile it from source using the instructions in the [build](#Building) section diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index fe772045..6a2e17e9 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -66,6 +66,33 @@ yabridgectl sync yabridgectl sync --prune ``` +## Alternatives + +If you want to script your own installation behaviour and don't feel like using +yabridgectl, then you could use one of the below bash snippets as a base. This +approach is slightly less robust and does not do any problem detection or status +reporting, but it will get you started. + +```shell +# For use with symlinks +yabridge_home=$HOME/.local/share/yabridge +plugin_dir="$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins" + +find -L "$plugin_dir" -type f -iname '*.dll' -print0 | + xargs -0 -P$(nproc) -I{} bash -c "(winedump -j export '{}' | grep -qE 'VSTPluginMain|main|main_plugin') && printf '{}\0'" | + sed -z 's/\.dll$/.so/' | + xargs -0 -n1 ln -sf "$yabridge_home/libyabridge.so" + +# For use with copies +yabridge_home=$HOME/.local/share/yabridge +plugin_dir="$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins" + +find -L "$plugin_dir" -type f -iname '*.dll' -print0 | + xargs -0 -P$(nproc) -I{} bash -c "(winedump -j export '{}' | grep -qE 'VSTPluginMain|main|main_plugin') && printf '{}\0'" | + sed -z 's/\.dll$/.so/' | + xargs -0 -n1 cp "$yabridge_home/libyabridge.so" +``` + ## Building After installing [Rust](https://rustup.rs/), simply run the below to compile and From 1929d1a6d1e5298c5c4dd56aee2d397a9e71fd66 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 15 Jul 2020 17:35:42 +0200 Subject: [PATCH 28/29] Implement `yabridgectl set` --- tools/yabridgectl/src/main.rs | 51 +++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index f571788d..187cb36e 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use clap::{app_from_crate, App, AppSettings, Arg}; +use clap::{app_from_crate, App, AppSettings, Arg, ArgMatches}; use colored::Colorize; use std::fs; use std::os::unix::fs::symlink; @@ -27,7 +27,6 @@ use crate::files::FoundFile; mod config; mod files; -// TODO: Add the different `yabridgectl set` options // TODO: Naming and descriptions could be made clearer // TODO: When creating copies, check whether `yabridge-host.exe` is in the PATH for the login shell // TODO: Check for left over files when removing directory @@ -73,6 +72,27 @@ fn main() { ) .subcommand(App::new("list").about("List the plugin install locations")) .subcommand(App::new("status").about("Show the installation status for all plugins")) + .subcommand( + App::new("set") + .about("Change installation method or yabridge path") + .setting(AppSettings::ArgRequiredElseHelp) + .arg( + Arg::with_name("method") + .long("method") + .about("The installation method to use") + .long_about("The installation method to use.") + .possible_values(&["copy", "symlink"]) + .takes_value(true), + ) + .arg( + Arg::with_name("path") + .long("path") + .about("Path to the directory containing 'libyabridge.so'") + .long_about("Path to the directory containing 'libyabridge.so'. If this is not set, then yabridgectl will look in both '/usr/lib' and '~/.local/share/yabridge' by default.") + .validator(validate_path) + .takes_value(true), + ), + ) .subcommand( App::new("sync") .about("Set up or update yabridge for all plugins") @@ -98,6 +118,7 @@ fn main() { } ("list", _) => list_directories(&config), ("status", _) => show_status(&config), + ("set", Some(options)) => set_settings(&mut config, options), ("sync", Some(options)) => do_sync( &config, options.is_present("prune"), @@ -177,6 +198,32 @@ fn show_status(config: &Config) { } } +/// Change configuration settings. The actual options are defined in the clap [app](clap::App). +fn set_settings(config: &mut Config, options: &ArgMatches) { + match options.value_of("method") { + Some("copy") => config.method = InstallationMethod::Copy, + Some("symlink") => config.method = InstallationMethod::Symlink, + Some(s) => unimplemented!("Unexpected installation method '{}'", s), + None => (), + } + + match options.value_of_t("path") { + Ok(path) => config.yabridge_home = Some(path), + Err(clap::Error { + kind: clap::ErrorKind::ArgumentNotFound, + .. + }) => (), + // I don't think we can get any parsing errors here since we already validated that the + // argument has to be a valid path, but you never know + Err(err) => err.exit(), + } + + if let Err(err) = config.write() { + eprintln!("Error while writing config file: {}", err); + exit(1); + }; +} + /// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan /// `.so` files if the prune option is set. fn do_sync(config: &Config, prune: bool, verbose: bool) { From 6dda4f9683e306e7afe99f6952b11a286ad5bcae Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 15 Jul 2020 17:47:03 +0200 Subject: [PATCH 29/29] Add a changelog entry for yabridgectl --- CHANGELOG.md | 13 +++++++++++++ README.md | 7 +++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 888d0384..743b4d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- By somewhat popular demand, yabridge now comes with yabridgectl, a utility + that can automatically set up and manage yabridge for you. It also performs + some basic checks to ensure that everything has been set up correctly. + Yabridgectl can be downloaded separately from the GitHub releases page and its + use is completely optional, so you don't have to use it if you don't want to. + Check out the + [readme](https://github.com/robbert-vdh/yabridge/tree/feature/cli-tools/tools/yabridgectl/README.md) + for more information on how it works. + ## [1.2.1] - 2020-06-20 ### Changed diff --git a/README.md b/README.md index db928931..4428f7c4 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,11 @@ Please let me know if there are any issues with other VST hosts. ## Usage -TODO: Refactor these sections to refer to yabridgectl for most of the setup. -Manual setup can be moved to its own section. +TODO: Refactor these sections to refer to yabridgectl for most of the setup. If +you are reading this, then you can either follow the instructions below or you +can download a preview version of yabridgectl from the [automated +builds](https://github.com/robbert-vdh/yabridge/actions?query=workflow%3A%22Automated+builds%22+branch%3Amaster) +page. You can either download a prebuilt version of yabridge through the GitHub [releases](https://github.com/robbert-vdh/yabridge/releases) section, or you can