mod core;
mod gleam;
mod go;
mod haskell;
mod js;
mod ocaml;
mod php;
mod python;
mod ruby;
mod rust;
mod system;
mod versioner;
mod wrapper;
mod zig;
pub(crate) use core::{
search_paths::{get_search_locations, scan_common_directories},
version::normalize_version,
};
pub use core::{
types::{
Categorizable, Category, Detector, GroupedPmInfo, InstallMethod, PmInfo, Tool, ToolLister,
},
version::{UNKNOWN_VERSION, is_broken_version},
};
use std::{collections::HashSet, hash::Hash, path::Path, process::Command};
use dashmap::DashMap;
pub use gleam::Gleam;
pub use go::Go;
pub use haskell::{Cabal, Stack};
pub use js::{Bun, Deno, Npm, Pnpm, Yarn};
pub use ocaml::Opam;
pub use php::{Composer, Pecl};
pub use python::{Conda, Pdm, Pip, Pipenv, Pipx, Poetry, Uv};
pub use ruby::{Bundle, Bundler, Gem};
pub use rust::Cargo;
pub use system::{Homebrew, Macports, Nix};
pub use versioner::{Asdf, Mise, Nvm, Phpbrew, Pyenv, Rbenv, Rvm, Volta};
use which::which_all;
pub use wrapper::{Corepack, Ni};
pub use zig::Zig;
#[derive(Hash, Eq, PartialEq, Clone)]
struct CacheKey {
name: String,
version_args: Vec<String>,
}
impl CacheKey {
fn new(name: &str, version_args: &[&str]) -> Self {
Self {
name: name.to_string(),
version_args: version_args.iter().map(|s| s.to_string()).collect(),
}
}
}
static PM_DISCOVERY_CACHE: std::sync::OnceLock<DashMap<CacheKey, Vec<PmInfo>>> =
std::sync::OnceLock::new();
#[inline]
pub fn determine_install_method(path: &Path) -> InstallMethod {
core::install_method::detect(path)
}
#[must_use]
pub fn find_all_pms(name: &str) -> Vec<PmInfo> {
find_all_pms_with_args(name, &["--version"])
}
#[must_use]
pub(super) fn find_all_pms_with_args(name: &str, version_args: &[&str]) -> Vec<PmInfo> {
let cache = PM_DISCOVERY_CACHE.get_or_init(DashMap::new);
let cache_key = CacheKey::new(name, version_args);
if let Some(cached_results) = cache.get(&cache_key) {
return cached_results.clone();
}
let results = find_all_installations(name, version_args);
cache.insert(cache_key, results.clone());
results
}
fn find_all_installations(name: &str, version_args: &[&str]) -> Vec<PmInfo> {
let mut instances = Vec::new();
let mut seen_canonical_paths = HashSet::new();
if let Ok(paths) = which_all(name) {
for path in paths {
if let Some(pm_info) = try_detect_at_path(&path, name, version_args)
&& let Ok(canonical) = std::fs::canonicalize(&path)
&& !seen_canonical_paths.contains(&canonical)
{
seen_canonical_paths.insert(canonical);
instances.push(pm_info);
}
}
}
let search_locations = get_search_locations(name);
for location in search_locations {
if location.exists()
&& let Ok(canonical_location) = std::fs::canonicalize(&location)
{
if seen_canonical_paths.contains(&canonical_location) {
continue;
}
if let Some(pm_info) = try_detect_at_path(&location, name, version_args) {
seen_canonical_paths.insert(canonical_location);
instances.push(pm_info);
}
}
}
if instances.is_empty() {
instances.extend(scan_common_directories(name, &mut seen_canonical_paths));
}
instances
}
fn try_detect_at_path(path: &std::path::Path, name: &str, version_args: &[&str]) -> Option<PmInfo> {
let output = Command::new(path).args(version_args).output().ok()?;
if !output.status.success() {
return None;
}
let version = normalize_version(output.stdout);
let install_method = determine_install_method(path);
Some(PmInfo {
name: name.to_string(),
version,
install_method,
path: path.display().to_string(),
latest_version: None,
})
}
pub fn scan_managed_tools() -> std::collections::HashMap<String, Vec<Tool>> {
use std::collections::HashMap;
let mut all_tools = HashMap::new();
for lister in tool_listers() {
if lister.is_available() {
let tools = lister.list();
if !tools.is_empty() {
all_tools.insert(lister.name().to_string(), tools);
}
}
}
all_tools
}
pub fn get_package_managers_in_category(category: Category) -> Vec<&'static str> {
all_package_managers()
.into_iter()
.filter(|detector| detector.category() == category)
.map(|detector| detector.name())
.collect()
}
pub fn all_package_managers() -> Vec<Box<dyn Detector>> {
vec![
Box::new(Homebrew),
Box::new(Macports),
Box::new(Nix),
Box::new(Gleam),
Box::new(Go),
Box::new(Cabal),
Box::new(Stack),
Box::new(Bun),
Box::new(Deno),
Box::new(Npm),
Box::new(Pnpm),
Box::new(Yarn),
Box::new(Opam),
Box::new(Composer),
Box::new(Pecl),
Box::new(Conda),
Box::new(Pdm),
Box::new(Pip),
Box::new(Pipenv),
Box::new(Poetry),
Box::new(Uv),
Box::new(Bundle),
Box::new(Bundler),
Box::new(Gem),
Box::new(Cargo),
Box::new(Zig),
Box::new(Asdf),
Box::new(Mise),
Box::new(Nvm),
Box::new(Phpbrew),
Box::new(Pyenv),
Box::new(Rbenv),
Box::new(Rvm),
Box::new(Volta),
Box::new(Corepack),
Box::new(Ni),
]
}
pub fn tool_listers() -> Vec<Box<dyn ToolLister>> {
vec![
Box::new(Pip),
Box::new(Pipx),
Box::new(Cargo),
Box::new(Go),
]
}