From 5bc268de6e86a1f3448ee41007857ab158d9693a Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sun, 9 Jun 2024 19:43:38 +0200 Subject: [PATCH] Implement fall-through mechanism for tools not managed by Rokit (#25) --- Cargo.lock | 34 ++++++++++++++++++++++++++++++++++ Cargo.toml | 3 ++- lib/discovery/mod.rs | 26 ++++++++++++++++++++++++++ src/runner/mod.rs | 31 +++++++++++++++++-------------- 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f82a9e4..beffde1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -637,6 +637,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -930,6 +936,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "1.1.0" @@ -1796,6 +1811,7 @@ dependencies = [ "tracing", "tracing-subscriber", "url", + "which", "winreg", "zip", ] @@ -2549,6 +2565,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2791,6 +2819,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "xattr" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index 4515536..76b423d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,12 +41,12 @@ filepath = "0.1" flate2 = "1.0" goblin = "0.8" once_cell = "1.8" -process-wrap = { version = "8.0", features = ["tokio1"] } semver = { version = "1.0", features = ["serde"] } tar = "0.4" tempfile = "3.3" thiserror = "1.0" url = { version = "2.5", features = ["serde"] } +which = "6.0" zip = "2.1" # Async / runtime dependencies @@ -54,6 +54,7 @@ zip = "2.1" async-once-cell = "0.5" async-signal = "0.2" futures = "0.3" +process-wrap = { version = "8.0", features = ["tokio1"] } reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls", "http2", diff --git a/lib/discovery/mod.rs b/lib/discovery/mod.rs index a967639..fb4c172 100644 --- a/lib/discovery/mod.rs +++ b/lib/discovery/mod.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + env::var_os, path::{Path, PathBuf}, }; @@ -8,6 +9,7 @@ use tokio::fs::read_to_string; use crate::{ manifests::RokitManifest, + storage::Home, system::current_dir, tool::{ToolAlias, ToolSpec}, }; @@ -164,3 +166,27 @@ pub async fn discover_tool_spec( None } + +/** + Discovers a tool explicitly **not** managed by Rokit, + by traversing the system PATH environment variable. + + This means that if Rokit manages a tool with the given alias, + and an executable for it is in the PATH, this function will + ignore that and prefer other tools on the system instead. +*/ +pub async fn discover_non_rokit_tool(home: &Home, alias: &ToolAlias) -> Option { + let cwd = current_dir().await; + + let binary_name = alias.name().to_string(); + let home_path = home.path().to_owned(); + let search_paths = var_os("PATH")?; + + let mut found_tool_paths = which::which_in_all(binary_name, Some(search_paths), &cwd) + .ok() + .into_iter() + .flatten() + .filter(|path| !path.starts_with(&home_path)); + + found_tool_paths.next() +} diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 045c3ab..2a67a84 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -1,9 +1,9 @@ use std::{env::args, process::exit, str::FromStr}; -use anyhow::{Context, Error, Result}; +use anyhow::{bail, Error, Result}; use rokit::{ - discovery::discover_tool_spec, + discovery::{discover_non_rokit_tool, discover_tool_spec}, storage::Home, system::{current_exe_name, run_interruptible}, tool::ToolAlias, @@ -32,21 +32,24 @@ impl Runner { let alias = ToolAlias::from_str(&self.exe_name)?; let home = Home::load_from_env().await?; - let spec = discover_tool_spec(&alias, false, false) - .await - .with_context(|| { - format!( + let spec = discover_tool_spec(&alias, false, false).await; + + let program_args = args().skip(1).collect::>(); + let program_path = match spec { + // TODO: Prompt for trust and install tool if not already installed + Some(spec) => home.tool_storage().tool_path(&spec), + // FUTURE: Maybe we should add some kind of "fall-through" setting in + // Rokit manifests instead of always falling through to non-rokit tools? + None => match discover_non_rokit_tool(&home, &alias).await { + Some(path) => path, + None => bail!( "Failed to find tool '{alias}' in any project manifest file.\ \nAdd the tool to a project using 'rokit add' before running it." - ) - })?; - - // TODO: Prompt for trust and install tool if not already installed - - let path = home.tool_storage().tool_path(&spec); - let args = args().skip(1).collect::>(); + ), + }, + }; - let code = run_interruptible(&path, &args) + let code = run_interruptible(&program_path, &program_args) .await .map_err(Error::from) .inspect_err(|e| inform_user_about_potential_fixes(&alias, e))?;