diff --git a/.github/workflows/hyperfine.yml b/.github/workflows/hyperfine.yml index 5121c2b5fc..0867d091d8 100644 --- a/.github/workflows/hyperfine.yml +++ b/.github/workflows/hyperfine.yml @@ -4,9 +4,9 @@ on: branches: ["main"] pull_request: branches: ["main"] - paths: - - ".github/workflows/hyperfine.yml" - - "Cargo.toml" + # paths: + # - ".github/workflows/hyperfine.yml" + # - "Cargo.toml" workflow_dispatch: concurrency: diff --git a/Cargo.lock b/Cargo.lock index f174d0dd80..d4134b22cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3654,6 +3654,7 @@ dependencies = [ "console", "contracts", "ctor", + "dashmap", "demand", "digest", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index f87c7d0dc3..a0f5b982a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ comfy-table = "7.1.3" confique = { version = "0.3", default-features = false } console = "0.15" contracts = "0.6" +dashmap = "6" demand = "1" digest = "0.10.7" dotenvy = "0.15" @@ -96,7 +97,7 @@ number_prefix = "0.4" once_cell = "1" openssl = { version = "0.10", optional = true } os-release = "0.1" -path-absolutize = "3" +path-absolutize = { version = "3", features = ["unsafe_cache"] } petgraph = "0.8" rand = "0.9" regex = "1" diff --git a/e2e/run_test b/e2e/run_test index 94f5d7ffff..28571d9806 100755 --- a/e2e/run_test +++ b/e2e/run_test @@ -66,6 +66,7 @@ within_isolated_env() { MISE_DATA_DIR="$MISE_DATA_DIR" \ MISE_DEBUG="${MISE_DEBUG:-0}" \ MISE_EXPERIMENTAL=1 \ + MISE_GPG_VERIFY="${MISE_GPG_VERIFY:-}" \ MISE_LOG_LEVEL="${MISE_LOG_LEVEL:-}" \ MISE_STATE_DIR="$MISE_STATE_DIR" \ MISE_SYSTEM_DIR="$MISE_SYSTEM_DIR" \ diff --git a/hk.pkl b/hk.pkl index 1363964ebf..690f5077df 100644 --- a/hk.pkl +++ b/hk.pkl @@ -5,10 +5,12 @@ local bash_glob = List("*.sh", "xtasks/**", "scripts/**", "e2e/**") local bash_exclude = List("*.ps1", "*.fish", "*.ts", "*.js", "*.json", "*.bat", "**/.*", "src/assets/bash_zsh_support/**") local linters = new Mapping { // uses builtin prettier linter config - ["prettier"] = Builtins.prettier + ["prettier"] = (Builtins.prettier) { + batch = false + } ["clippy"] = (Builtins.cargo_clippy) { - check = "cargo clippy --manifest-path {{workspace_indicator}} --all-features" - fix = "cargo clippy --manifest-path {{workspace_indicator}} --all-features --fix --allow-dirty --allow-staged" + check = "cargo clippy --manifest-path {{workspace_indicator}} --all-features -- -Dwarnings" + fix = "cargo clippy --manifest-path {{workspace_indicator}} --all-features --fix --allow-dirty --allow-staged -- -Dwarnings" } ["shellcheck"] = (Builtins.shellcheck) { glob = bash_glob diff --git a/mise.code-workspace b/mise.code-workspace index 2e8b0c6dc4..55ef3ed9e0 100644 --- a/mise.code-workspace +++ b/mise.code-workspace @@ -4,4 +4,9 @@ "path": ".", }, ], + "settings": { + "shellcheck.ignorePatterns": { + "completions/mise.bash": true, + }, + }, } diff --git a/schema/mise.json b/schema/mise.json index 017a86da50..ef72c8bfe3 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -243,6 +243,11 @@ "aqua": { "additionalProperties": false, "properties": { + "baked_registry": { + "default": true, + "description": "Use baked-in aqua registry (if compiled in).", + "type": "boolean" + }, "cosign": { "default": true, "description": "Use cosign to verify aqua tool signatures.", @@ -494,6 +499,10 @@ "description": "Set to true to skip checksum verification when downloading go sdk tarballs.", "type": "boolean" }, + "gpg_verify": { + "description": "Use gpg to verify all tool signatures.", + "type": "boolean" + }, "http_timeout": { "default": "30s", "description": "Timeout in seconds for all HTTP requests in mise.", diff --git a/settings.toml b/settings.toml index f2676c9351..20367dad9b 100644 --- a/settings.toml +++ b/settings.toml @@ -55,6 +55,12 @@ env = "MISE_ALWAYS_KEEP_INSTALL" type = "Bool" description = "should mise keep install files after installation even if the installation fails" +[aqua.baked_registry] +env = "MISE_AQUA_BAKED_REGISTRY" +type = "Bool" +default = true +description = "Use baked-in aqua registry (if compiled in)." + [aqua.cosign] env = "MISE_AQUA_COSIGN" type = "Bool" @@ -457,6 +463,12 @@ env = "MISE_GO_SKIP_CHECKSUM" type = "Bool" description = "Set to true to skip checksum verification when downloading go sdk tarballs." +[gpg_verify] +env = "MISE_GPG_VERIFY" +type = "Bool" +optional = true +description = "Use gpg to verify all tool signatures." + [http_timeout] env = "MISE_HTTP_TIMEOUT" type = "Duration" diff --git a/src/aqua/aqua_registry.rs b/src/aqua/aqua_registry.rs index a99b8bc1d9..d07d725f67 100644 --- a/src/aqua/aqua_registry.rs +++ b/src/aqua/aqua_registry.rs @@ -204,44 +204,14 @@ impl AquaRegistry { pub async fn package(&self, id: &str) -> Result { static CACHE: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); - static RATE_LIMITED: AtomicBool = AtomicBool::new(false); if let Some(pkg) = CACHE.lock().await.get(id) { return Ok(pkg.clone()); } let path_id = id.split('/').join(std::path::MAIN_SEPARATOR_STR); let path = self.path.join("pkgs").join(&path_id).join("registry.yaml"); - let registry: RegistryYaml = if !self.repo_exists { - if let Some(registry) = AQUA_STANDARD_REGISTRY_FILES.get(id) { - trace!("reading baked-in aqua-registry for {id}"); - serde_yaml::from_str(registry)? - } else if !path.exists() || file::modified_duration(&path)? > DAILY { - if RATE_LIMITED.load(Ordering::Relaxed) { - warn!("aqua-registry rate limited, skipping {id}"); - return Err(eyre!("aqua-registry rate limited")); - } - trace!("downloading aqua-registry for {id} to {path:?}"); - let url: Url = - format!("https://mise-versions.jdx.dev/aqua-registry/{path_id}/registry.yaml") - .parse()?; - match http::HTTP_FETCH.download_file(url, &path, None).await { - Ok(_) => {} - Err(e) if http::error_code(&e) == Some(429) => { - warn!("aqua-registry rate limited, skipping {id}"); - RATE_LIMITED.store(true, Ordering::Relaxed); - return Err(e); - } - Err(e) => return Err(e), - } - serde_yaml::from_reader(file::open(&path)?)? - } else { - trace!("reading cached aqua-registry for {id} from {path:?}"); - serde_yaml::from_reader(file::open(&path)?)? - } - } else { - trace!("reading aqua-registry for {id} from repo at {path:?}"); - serde_yaml::from_reader(file::open(&path)?)? - }; - let mut pkg = registry + let mut pkg = self + .fetch_package_yaml(id, &path, &path_id) + .await? .packages .into_iter() .next() @@ -256,6 +226,45 @@ impl AquaRegistry { pub async fn package_with_version(&self, id: &str, v: &str) -> Result { Ok(self.package(id).await?.with_version(v)) } + + async fn fetch_package_yaml( + &self, + id: &str, + path: &PathBuf, + path_id: &str, + ) -> Result { + let registry = if self.repo_exists { + trace!("reading aqua-registry for {id} from repo at {path:?}"); + serde_yaml::from_reader(file::open(path)?)? + } else if SETTINGS.aqua.baked_registry && AQUA_STANDARD_REGISTRY_FILES.contains_key(id) { + trace!("reading baked-in aqua-registry for {id}"); + serde_yaml::from_str(AQUA_STANDARD_REGISTRY_FILES.get(id).unwrap())? + } else if !path.exists() || file::modified_duration(path)? > DAILY { + static RATE_LIMITED: AtomicBool = AtomicBool::new(false); + if RATE_LIMITED.load(Ordering::Relaxed) { + warn!("aqua-registry rate limited, skipping {id}"); + return Err(eyre!("aqua-registry rate limited")); + } + trace!("downloading aqua-registry for {id} to {path:?}"); + let url = + format!("https://mise-versions.jdx.dev/aqua-registry/{path_id}/registry.yaml"); + let url: Url = url.parse()?; + match http::HTTP_FETCH.download_file(url, path, None).await { + Ok(_) => {} + Err(e) if http::error_code(&e) == Some(429) => { + warn!("aqua-registry rate limited, skipping {id}"); + RATE_LIMITED.store(true, Ordering::Relaxed); + return Err(e); + } + Err(e) => return Err(e), + } + serde_yaml::from_reader(file::open(path)?)? + } else { + trace!("reading cached aqua-registry for {id} from {path:?}"); + serde_yaml::from_reader(file::open(path)?)? + }; + Ok(registry) + } } fn fetch_latest_repo(repo: &Git) -> Result<()> { diff --git a/src/backend/aqua.rs b/src/backend/aqua.rs index 87cd554b60..5827ac99c1 100644 --- a/src/backend/aqua.rs +++ b/src/backend/aqua.rs @@ -1,6 +1,3 @@ -use crate::aqua::aqua_registry::{ - AQUA_REGISTRY, AquaChecksumType, AquaMinisignType, AquaPackage, AquaPackageType, -}; use crate::backend::Backend; use crate::backend::backend_type::BackendType; use crate::cli::args::BackendArg; @@ -10,23 +7,31 @@ use crate::config::SETTINGS; use crate::file::TarOptions; use crate::http::HTTP; use crate::install_context::InstallContext; +use crate::path::{Path, PathBuf, PathExt}; use crate::plugins::VERSION_REGEX; use crate::registry::REGISTRY; use crate::toolset::ToolVersion; +use crate::{ + aqua::aqua_registry::{ + AQUA_REGISTRY, AquaChecksumType, AquaMinisignType, AquaPackage, AquaPackageType, + }, + cache::{CacheManager, CacheManagerBuilder}, +}; use crate::{file, github, minisign}; use async_trait::async_trait; +use dashmap::DashMap; use eyre::{ContextCompat, Result, bail}; use indexmap::IndexSet; use itertools::Itertools; use regex::Regex; use std::fmt::Debug; -use std::path::{Path, PathBuf}; use std::{collections::HashSet, sync::Arc}; #[derive(Debug)] pub struct AquaBackend { ba: Arc, id: String, + bin_path_caches: DashMap>>, } #[async_trait] @@ -118,21 +123,42 @@ impl Backend for AquaBackend { } async fn list_bin_paths(&self, tv: &ToolVersion) -> Result> { - let pkg = AQUA_REGISTRY - .package_with_version(&self.id, &tv.version) - .await?; - - let srcs = self.srcs(&pkg, tv)?; - if srcs.is_empty() { - return Ok(vec![tv.install_path()]); - } + // TODO: instead of caching it would probably be better to create this as part of installation + let cache = self + .bin_path_caches + .entry(tv.version.clone()) + .or_insert_with(|| { + CacheManagerBuilder::new(tv.cache_path().join("bin_paths.msgpack.z")) + .with_fresh_duration(SETTINGS.fetch_remote_versions_cache()) + .build() + }); + let install_path = tv.install_path(); + let paths = cache + .get_or_try_init_async(async || { + let pkg = AQUA_REGISTRY + .package_with_version(&self.id, &tv.version) + .await?; - Ok(srcs + let srcs = self.srcs(&pkg, tv)?; + let paths = if srcs.is_empty() { + vec![install_path.clone()] + } else { + srcs.iter() + .map(|(_, dst)| dst.parent().unwrap().to_path_buf()) + .collect() + }; + Ok(paths + .into_iter() + .unique() + .filter(|p| p.exists()) + .map(|p| p.strip_prefix(&install_path).unwrap().to_path_buf()) + .collect()) + }) + .await? .iter() - .map(|(_, dst)| dst.parent().unwrap().to_path_buf()) - .filter(|p| p.exists()) - .unique() - .collect()) + .map(|p| p.mount(&install_path)) + .collect(); + Ok(paths) } fn fuzzy_match_filter(&self, versions: Vec, query: &str) -> eyre::Result> { @@ -175,6 +201,7 @@ impl AquaBackend { Self { id: id.to_string(), ba: Arc::new(ba), + bin_path_caches: Default::default(), } } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index a38dc37daf..d45ecefe47 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; +use dashmap::DashMap; +use std::collections::{BTreeMap, HashSet}; use std::ffi::OsString; use std::fmt::{Debug, Display, Formatter}; use std::fs::File; @@ -659,14 +660,10 @@ pub trait Backend: Debug + Send + Sync { } fn get_remote_version_cache(&self) -> Arc> { - // use a mutex to prevent deadlocks that occurs due to reentrant cache access - static REMOTE_VERSION_CACHE: Lazy< - Mutex>>>, - > = Lazy::new(Default::default); + static REMOTE_VERSION_CACHE: Lazy>>> = + Lazy::new(Default::default); REMOTE_VERSION_CACHE - .lock() - .unwrap() .entry(self.ba().full()) .or_insert_with(|| { let mut cm = CacheManagerBuilder::new( diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 8286e91451..6dae80096f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,12 +1,11 @@ -use crate::cli::args::ToolArg; +use crate::Result; use crate::cli::run::TaskOutput; use crate::config::{Config, Settings}; -use crate::env::MISE_BIN; use crate::exit::exit; use crate::ui::ctrlc; +use crate::{cli::args::ToolArg, path::PathExt}; use crate::{logger, migrate, shims}; use clap::{ArgAction, CommandFactory, Parser, Subcommand}; -use color_eyre::Result; use std::path::PathBuf; mod activate; @@ -326,11 +325,9 @@ impl Cli { ctrlc::init(); let print_version = version::print_version_if_requested(args)?; - let cli = measure!("pre_settings", { Self::pre_settings(args).await })?; + let cli = measure!("pre_settings", { Self::pre_settings().await? }); measure!("add_cli_matches", { Settings::add_cli_matches(&cli) }); - measure!("settings", { - let _ = Settings::try_get(); - }); + let _ = measure!("settings", { Settings::try_get() }); measure!("logger", { logger::init() }); measure!("migrate", { migrate::run().await }); if let Err(err) = crate::cache::auto_prune() { @@ -338,7 +335,7 @@ impl Cli { } debug!("ARGS: {}", &args.join(" ")); - trace!("MISE_BIN: {}", MISE_BIN.to_string_lossy().to_string()); + trace!("MISE_BIN: {}", crate::env::MISE_BIN.display_user()); if print_version { version::show_latest().await; exit(0); @@ -347,12 +344,16 @@ impl Cli { measure!("run {cmd}", { cmd.run().await }) } - async fn pre_settings(args: &Vec) -> Result { - let (_, cli) = tokio::try_join!( + async fn pre_settings() -> Result { + let (_, cli) = tokio::join!( measure!("install_state", { crate::install_state::init() }), - async { measure!("get_matches_from", { Ok(Cli::parse_from(args)) }) }, - )?; - Ok(cli) + tokio::task::spawn(async { + measure!("get_matches_from", { + Result::Ok(Cli::parse_from(crate::env::ARGS.read().unwrap().iter())) + }) + }), + ); + cli? } async fn get_command(self) -> Result { diff --git a/src/config/settings.rs b/src/config/settings.rs index 7764668a97..0987ebeeae 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -12,7 +12,6 @@ use itertools::Itertools; use serde::ser::Error; use serde::{Deserialize, Deserializer}; use serde_derive::Serialize; -use std::collections::{BTreeSet, HashSet}; use std::env::consts::ARCH; use std::fmt::{Debug, Display, Formatter}; use std::path::{Path, PathBuf}; @@ -20,6 +19,10 @@ use std::str::FromStr; use std::sync::LazyLock as Lazy; use std::sync::{Arc, Mutex, RwLock}; use std::time::Duration; +use std::{ + collections::{BTreeSet, HashSet}, + sync::atomic::Ordering, +}; use url::Url; pub static SETTINGS: Lazy> = Lazy::new(Settings::get); @@ -174,6 +177,10 @@ impl Settings { settings.erlang.compile = Some(true); } } + if settings.gpg_verify.is_some() { + settings.node.gpg_verify = settings.node.gpg_verify.or(settings.gpg_verify); + settings.swift.gpg_verify = settings.swift.gpg_verify.or(settings.gpg_verify); + } settings.set_hidden_configs(); if cfg!(test) { settings.experimental = true; @@ -376,12 +383,12 @@ impl Settings { } /// duration that remote version cache is kept for - /// for "fast" commands (represented by PREFER_STALE), these are always + /// for "fast" commands (represented by PREFER_OFFLINE), these are always /// cached. For "slow" commands like `mise ls-remote` or `mise install`: /// - if MISE_FETCH_REMOTE_VERSIONS_CACHE is set, use that /// - if MISE_FETCH_REMOTE_VERSIONS_CACHE is not set, use HOURLY pub fn fetch_remote_versions_cache(&self) -> Option { - if *env::PREFER_STALE { + if env::PREFER_OFFLINE.load(Ordering::Relaxed) { None } else { Some(duration::parse_duration(&self.fetch_remote_versions_cache).unwrap()) diff --git a/src/env.rs b/src/env.rs index 4e0ae07b66..80868c76d2 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,13 +1,16 @@ -use crate::cli::args::{ENV_ARG, PROFILE_ARG, ToolArg}; +use crate::Result; use crate::env_diff::{EnvDiff, EnvDiffOperation, EnvDiffPatches, EnvMap}; use crate::file::replace_path; use crate::shell::ShellType; +use crate::{ + cli::args::{ENV_ARG, PROFILE_ARG, ToolArg}, + file::display_path, +}; +use eyre::Context; use indexmap::IndexSet; use itertools::Itertools; use log::LevelFilter; pub use std::env::*; -use std::path::PathBuf; -use std::string::ToString; use std::sync::LazyLock as Lazy; use std::sync::RwLock; use std::{ @@ -16,6 +19,8 @@ use std::{ sync::Mutex, }; use std::{path, process}; +use std::{path::Path, string::ToString}; +use std::{path::PathBuf, sync::atomic::AtomicBool}; pub static ARGS: RwLock> = RwLock::new(vec![]); pub static TOOL_ARGS: RwLock> = RwLock::new(vec![]); @@ -180,7 +185,9 @@ pub static __MISE_SCRIPT: Lazy = Lazy::new(|| var_is_true("__MISE_SCRIPT") pub static __MISE_DIFF: Lazy = Lazy::new(get_env_diff); pub static __MISE_ORIG_PATH: Lazy> = Lazy::new(|| var("__MISE_ORIG_PATH").ok()); pub static LINUX_DISTRO: Lazy> = Lazy::new(linux_distro); -pub static PREFER_STALE: Lazy = Lazy::new(|| prefer_stale(&ARGS.read().unwrap())); +pub static PREFER_OFFLINE: Lazy = + Lazy::new(|| prefer_offline(&ARGS.read().unwrap()).into()); +pub static OFFLINE: Lazy = Lazy::new(|| offline(&ARGS.read().unwrap())); /// essentially, this is whether we show spinners or build output on runtime install pub static PRISTINE_ENV: Lazy = Lazy::new(|| get_pristine_env(&__MISE_DIFF, vars().collect())); @@ -443,18 +450,44 @@ fn apply_patches(env: &EnvMap, patches: &EnvDiffPatches) -> EnvMap { new_env } +fn offline(args: &[String]) -> bool { + if var_is_true("MISE_OFFLINE") { + return true; + } + + args.iter() + .take_while(|a| *a != "--") + .any(|a| a == "--offline") +} + /// returns true if new runtime versions should not be fetched -fn prefer_stale(args: &[String]) -> bool { - let binding = String::new(); - let c = args - .iter() - .filter(|a| !a.starts_with('-')) +fn prefer_offline(args: &[String]) -> bool { + // First check if MISE_PREFER_OFFLINE is set + if var_is_true("MISE_PREFER_OFFLINE") { + return true; + } + + // Otherwise fall back to the original command-based logic + args.iter() + .take_while(|a| *a != "--") + .filter(|a| !a.starts_with('-') || *a == "--prefer-offline") .nth(1) - .unwrap_or(&binding); - [ - "env", "hook-env", "x", "exec", "direnv", "activate", "current", "ls", "where", - ] - .contains(&c.as_str()) + .map(|a| { + [ + "--prefer-offline", + "activate", + "current", + "direnv", + "env", + "exec", + "hook-env", + "ls", + "where", + "x", + ] + .contains(&a.as_str()) + }) + .unwrap_or_default() } fn environment(args: &[String]) -> Vec { @@ -525,6 +558,18 @@ pub fn remove_var>(key: K) { } } +pub fn set_current_dir>(path: P) -> Result<()> { + let path = path.as_ref(); + trace!("cd {}", display_path(path)); + unsafe { + std::env::set_current_dir(path).wrap_err_with(|| { + format!("failed to set current directory to {}", display_path(path)) + })?; + path_absolutize::update_cwd(); + } + Ok(()) +} + #[cfg(test)] mod tests { use pretty_assertions::assert_eq; diff --git a/src/file.rs b/src/file.rs index a2b5a5e482..99d3698d08 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,3 +1,4 @@ +use crate::path::{Path, PathBuf, PathExt}; use std::collections::{BTreeSet, HashMap}; use std::fmt::Display; use std::fs; @@ -7,7 +8,6 @@ use std::io::Write; use std::os::unix::fs::symlink; #[cfg(unix)] use std::os::unix::prelude::*; -use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::time::Duration; @@ -220,12 +220,7 @@ pub fn create_dir_all>(path: P) -> Result<()> { /// replaces $HOME with "~" pub fn display_path>(path: P) -> String { - let home = dirs::HOME.to_string_lossy(); - let path = path.as_ref(); - match cfg!(unix) && path.starts_with(home.as_ref()) && home != "/" { - true => path.to_string_lossy().replacen(home.as_ref(), "~", 1), - false => path.to_string_lossy().to_string(), - } + path.as_ref().display_user() } pub fn display_rel_path>(path: P) -> String { diff --git a/src/http.rs b/src/http.rs index f7bfd61a3e..7d3fdde59f 100644 --- a/src/http.rs +++ b/src/http.rs @@ -2,7 +2,7 @@ use std::io::Write; use std::path::Path; use std::time::Duration; -use eyre::{Report, Result, bail}; +use eyre::{Report, Result, bail, ensure}; use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::{ClientBuilder, IntoUrl, Response}; use std::sync::LazyLock as Lazy; @@ -64,6 +64,7 @@ impl Client { url: U, headers: &HeaderMap, ) -> Result { + ensure!(!*env::OFFLINE, "offline mode is enabled"); let get = |url: Url| async move { debug!("GET {}", &url); let mut req = self.reqwest.get(url.clone()); @@ -103,6 +104,7 @@ impl Client { url: U, headers: &HeaderMap, ) -> Result { + ensure!(!*env::OFFLINE, "offline mode is enabled"); let head = |url: Url| async move { debug!("HEAD {}", &url); let mut req = self.reqwest.head(url.clone()); diff --git a/src/main.rs b/src/main.rs index 049ceab6d2..73145d3d9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,7 @@ pub(crate) mod logger; pub(crate) mod maplit; mod migrate; mod minisign; +mod path; mod path_env; mod plugins; mod rand; @@ -89,6 +90,9 @@ fn main() -> eyre::Result<()> { .build()?; rt.block_on(async { color_eyre::install()?; + unsafe { + path_absolutize::update_cwd(); + } measure!("main", { let args = env::args().collect_vec(); match Cli::run(&args) diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000000..2156cafb6a --- /dev/null +++ b/src/path.rs @@ -0,0 +1,32 @@ +pub use std::path::*; + +use crate::dirs; + +pub trait PathExt { + /// replaces $HOME with "~" + fn display_user(&self) -> String; + fn mount(&self, on: &Path) -> PathBuf; + fn is_empty(&self) -> bool; +} + +impl PathExt for Path { + fn display_user(&self) -> String { + let home = dirs::HOME.to_string_lossy(); + match cfg!(unix) && self.starts_with(home.as_ref()) && home != "/" { + true => self.to_string_lossy().replacen(home.as_ref(), "~", 1), + false => self.to_string_lossy().to_string(), + } + } + + fn mount(&self, on: &Path) -> PathBuf { + if self.is_empty() { + on.to_path_buf() + } else { + on.join(self) + } + } + + fn is_empty(&self) -> bool { + self.as_os_str().is_empty() + } +} diff --git a/src/shims.rs b/src/shims.rs index cd7001eb13..396130bbc1 100644 --- a/src/shims.rs +++ b/src/shims.rs @@ -1,8 +1,11 @@ use crate::exit; -use std::collections::{BTreeSet, HashSet}; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; +use std::{ + collections::{BTreeSet, HashSet}, + sync::atomic::Ordering, +}; use crate::backend::Backend; use crate::cli::exec::Exec; @@ -15,6 +18,7 @@ use color_eyre::eyre::{Result, bail, eyre}; use eyre::WrapErr; use indoc::formatdoc; use itertools::Itertools; +use path_absolutize::Absolutize; use tokio::task::JoinSet; // executes as if it was a shim if the command is not "mise", e.g.: "node" @@ -26,6 +30,7 @@ pub async fn handle_shim() -> Result<()> { } logger::init(); let mut args = env::ARGS.read().unwrap().clone(); + env::PREFER_OFFLINE.store(true, Ordering::Relaxed); trace!("shim[{bin_name}] args: {}", args.join(" ")); args[0] = which_shim(&env::MISE_BIN_NAME) .await? @@ -97,6 +102,7 @@ pub async fn reshim(ts: &Toolset, force: bool) -> Result<()> { .lock(); let mise_bin = file::which("mise").unwrap_or(env::MISE_BIN.clone()); + let mise_bin = mise_bin.absolutize()?; // relative paths don't work as shims if force { file::remove_all(*dirs::SHIMS)?; diff --git a/src/toolset/install_state.rs b/src/toolset/install_state.rs index 385de15e1b..33ecc8e746 100644 --- a/src/toolset/install_state.rs +++ b/src/toolset/install_state.rs @@ -28,10 +28,12 @@ static INSTALL_STATE_PLUGINS: Mutex>> = Mutex::n static INSTALL_STATE_TOOLS: Mutex>> = Mutex::new(None); pub(crate) async fn init() -> Result<()> { - tokio::try_join!( - async { measure!("init_plugins", { init_plugins() }) }, - async { measure!("init_tools", { init_tools() }) }, - )?; + let (plugins, tools) = tokio::join!( + tokio::task::spawn(async { measure!("init_plugins", { init_plugins() }) }), + tokio::task::spawn(async { measure!("init_tools", { init_tools() }) }), + ); + plugins??; + tools??; Ok(()) } diff --git a/src/toolset/tool_version.rs b/src/toolset/tool_version.rs index 02ec0571a9..54ddb53743 100644 --- a/src/toolset/tool_version.rs +++ b/src/toolset/tool_version.rs @@ -1,9 +1,9 @@ -use std::cmp::Ordering; use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; use std::fs; use std::hash::{Hash, Hasher}; use std::path::PathBuf; +use std::{cmp::Ordering, sync::LazyLock}; use crate::backend::ABackend; use crate::cli::args::BackendArg; @@ -13,6 +13,7 @@ use crate::file; use crate::hash::hash_to_str; use crate::toolset::{ToolRequest, ToolVersionOptions, tool_request}; use console::style; +use dashmap::DashMap; use eyre::Result; #[cfg(windows)] use path_absolutize::Absolutize; @@ -91,6 +92,10 @@ impl ToolVersion { if let Some(p) = &self.install_path { return p.clone(); } + static CACHE: LazyLock> = LazyLock::new(DashMap::new); + if let Some(p) = CACHE.get(self) { + return p.clone(); + } let pathname = match &self.request { ToolRequest::Path { path: p, .. } => p.to_string_lossy().to_string(), _ => self.tv_pathname(), @@ -111,6 +116,7 @@ impl ToolVersion { } } } + CACHE.insert(self.clone(), path.clone()); path } pub fn cache_path(&self) -> PathBuf { diff --git a/tasks.md b/tasks.md index 8fa1f50ee1..90c739e1b6 100644 --- a/tasks.md +++ b/tasks.md @@ -117,10 +117,6 @@ User to run as - **Usage**: `lint:cargo-fmt` -## `lint:clippy` - -- **Usage**: `lint:clippy` - ## `lint:hk` - **Usage**: `lint:hk` @@ -129,10 +125,6 @@ User to run as - **Usage**: `lint:markdownlint` -## `lint:prettier` - -- **Usage**: `lint:prettier` - ## `lint:ripgrep` - **Usage**: `lint:ripgrep` diff --git a/xtasks/lint/clippy b/xtasks/lint/clippy deleted file mode 100755 index 4b001f6b4f..0000000000 --- a/xtasks/lint/clippy +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -#MISE wait_for=["build"] -set -euo pipefail - -if [[ ${MISE_PRE_COMMIT:-} == 1 ]]; then - cargo check -q --all-features -else - cargo clippy --all-features -- -Dwarnings -fi diff --git a/xtasks/lint/prettier b/xtasks/lint/prettier deleted file mode 100755 index 8fe5bd9582..0000000000 --- a/xtasks/lint/prettier +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -#MISE wait_for=["render:settings"] -set -euo pipefail - -prettier --cache --ignore-unknown --log-level warn -c "$@" .