mise/src/backend/vfox.rs

192 lines
6.1 KiB
Rust

use crate::env;
use eyre::{Report, WrapErr};
use heck::ToKebabCase;
use rayon::prelude::*;
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::path::PathBuf;
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use tokio::runtime::Runtime;
use url::Url;
use crate::backend::{ABackend, Backend, BackendList, BackendType};
use crate::cache::{CacheManager, CacheManagerBuilder};
use crate::cli::args::BackendArg;
use crate::config::settings::SETTINGS;
use crate::config::{Config, Settings};
use crate::git::Git;
use crate::install_context::InstallContext;
use crate::toolset::{ToolVersion, Toolset};
use crate::{dirs, file, registry};
use vfox::Vfox;
#[derive(Debug)]
pub struct VfoxBackend {
ba: BackendArg,
vfox: Vfox,
plugin_path: PathBuf,
remote_version_cache: CacheManager<Vec<String>>,
exec_env_cache: CacheManager<BTreeMap<String, String>>,
repo: OnceLock<Mutex<Git>>,
pathname: String,
}
impl Backend for VfoxBackend {
fn get_type(&self) -> BackendType {
BackendType::Vfox
}
fn fa(&self) -> &BackendArg {
&self.ba
}
fn _list_remote_versions(&self) -> eyre::Result<Vec<String>> {
self.remote_version_cache
.get_or_try_init(|| {
self.ensure_plugin_installed()?;
let versions = self
.runtime()?
.block_on(self.vfox.list_available_versions(&self.pathname))?;
Ok(versions
.into_iter()
.rev()
.map(|v| v.version)
.collect::<Vec<String>>())
})
.cloned()
}
fn install_version_impl(&self, ctx: &InstallContext) -> eyre::Result<()> {
let settings = Settings::get();
settings.ensure_experimental("vfox backend")?;
self.ensure_plugin_installed()?;
self.runtime()?.block_on(self.vfox.install(
&self.pathname,
&ctx.tv.version,
ctx.tv.install_path(),
))?;
Ok(())
}
fn list_bin_paths(&self, tv: &ToolVersion) -> eyre::Result<Vec<PathBuf>> {
let path = self
._exec_env(tv)?
.iter()
.find(|(k, _)| k.to_uppercase() == "PATH")
.map(|(_, v)| v.to_string())
.unwrap_or("bin".to_string());
Ok(env::split_paths(&path).collect())
}
fn exec_env(
&self,
_config: &Config,
_ts: &Toolset,
tv: &ToolVersion,
) -> eyre::Result<BTreeMap<String, String>> {
self._exec_env(tv).cloned()
}
}
fn vfox_to_url(name: &str) -> eyre::Result<Url> {
if let Some(full) = registry::REGISTRY_VFOX.get(name) {
// bun -> version-fox/vfox-bun
return vfox_to_url(full.split_once(':').unwrap().1);
}
let res = if let Some(caps) = regex!(r#"^([^/]+)/([^/]+)$"#).captures(name) {
let user = caps.get(1).unwrap().as_str();
let repo = caps.get(2).unwrap().as_str();
format!("https://github.com/{user}/{repo}").parse()
} else {
name.to_string().parse()
};
res.wrap_err_with(|| format!("Invalid version: {name}"))
}
impl VfoxBackend {
pub fn list() -> eyre::Result<BackendList> {
Ok(file::dir_subdirs(&dirs::PLUGINS)?
.into_par_iter()
.filter(|name| dirs::PLUGINS.join(name).join("metadata.lua").exists())
.map(|name| Arc::new(Self::from_arg(name.into())) as ABackend)
.collect())
}
pub fn from_arg(ba: BackendArg) -> Self {
let mut vfox = Vfox::new();
vfox.plugin_dir = dirs::PLUGINS.to_path_buf();
vfox.cache_dir = dirs::CACHE.to_path_buf();
vfox.download_dir = dirs::DOWNLOADS.to_path_buf();
vfox.install_dir = dirs::INSTALLS.to_path_buf();
vfox.temp_dir = env::temp_dir().join("mise-vfox");
let pathname = ba.short.to_kebab_case();
let plugin_path = dirs::PLUGINS.join(&pathname);
Self {
remote_version_cache: CacheManagerBuilder::new(
ba.cache_path.join("remote_versions.msgpack.z"),
)
.with_fresh_duration(SETTINGS.fetch_remote_versions_cache())
.with_fresh_file(dirs::DATA.to_path_buf())
.with_fresh_file(plugin_path.to_path_buf())
.with_fresh_file(ba.installs_path.to_path_buf())
.build(),
exec_env_cache: CacheManagerBuilder::new(ba.cache_path.join("exec_env.msgpack.z"))
.with_fresh_file(dirs::DATA.to_path_buf())
.with_fresh_file(plugin_path.to_path_buf())
.with_fresh_file(ba.installs_path.to_path_buf())
.build(),
repo: OnceLock::new(),
ba,
vfox,
plugin_path,
pathname,
}
}
fn runtime(&self) -> eyre::Result<Runtime, Report> {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_time()
.enable_io()
.build()?;
Ok(rt)
}
fn _exec_env(&self, tv: &ToolVersion) -> eyre::Result<&BTreeMap<String, String>> {
self.exec_env_cache.get_or_try_init(|| {
self.ensure_plugin_installed()?;
Ok(self
.runtime()?
.block_on(self.vfox.env_keys(&self.pathname, &tv.version))?
.into_iter()
.map(|envkey| (envkey.key, envkey.value))
.collect())
})
}
fn get_url(&self) -> eyre::Result<Url> {
if let Ok(Some(url)) = self.repo().map(|r| r.get_remote_url()) {
return Ok(Url::parse(&url)?);
}
vfox_to_url(&self.ba.name)
}
fn repo(&self) -> eyre::Result<MutexGuard<Git>> {
if let Some(repo) = self.repo.get() {
Ok(repo.lock().unwrap())
} else {
let repo = Mutex::new(Git::new(self.plugin_path.clone()));
self.repo.set(repo).unwrap();
self.repo()
}
}
fn ensure_plugin_installed(&self) -> eyre::Result<()> {
if !self.plugin_path.exists() {
let url = self.get_url()?;
trace!("Cloning vfox plugin: {url}");
self.repo()?.clone(url.as_str())?;
}
Ok(())
}
}