diff --git a/crates/vfox/plugins/dummy/hooks/pre_uninstall.lua b/crates/vfox/plugins/dummy/hooks/pre_uninstall.lua new file mode 100644 index 0000000000..cabfaf2269 --- /dev/null +++ b/crates/vfox/plugins/dummy/hooks/pre_uninstall.lua @@ -0,0 +1,9 @@ +function PLUGIN:PreUninstall(ctx) + local main = ctx.main + local marker = main.path .. "/pre_uninstall_marker" + local marker_file = io.open(marker, "w") + if marker_file then + marker_file:write(main.name .. ":" .. main.version .. ":" .. string.gsub(main.path, "\\", "/")) + marker_file:close() + end +end diff --git a/crates/vfox/src/hooks/mod.rs b/crates/vfox/src/hooks/mod.rs index 165b29619d..bdd51e95e1 100644 --- a/crates/vfox/src/hooks/mod.rs +++ b/crates/vfox/src/hooks/mod.rs @@ -8,4 +8,5 @@ pub mod mise_path; pub mod parse_legacy_file; pub mod post_install; pub mod pre_install; +pub mod pre_uninstall; pub mod pre_use; diff --git a/crates/vfox/src/hooks/pre_uninstall.rs b/crates/vfox/src/hooks/pre_uninstall.rs new file mode 100644 index 0000000000..8f3990a9a6 --- /dev/null +++ b/crates/vfox/src/hooks/pre_uninstall.rs @@ -0,0 +1,30 @@ +use crate::Plugin; +use crate::error::Result; +use crate::sdk_info::SdkInfo; +use mlua::{IntoLua, Lua, Value}; +use std::collections::BTreeMap; + +impl Plugin { + pub async fn pre_uninstall(&self, ctx: PreUninstallContext) -> Result<()> { + debug!("[vfox:{}] pre_uninstall", &self.name); + self.exec_async(chunk! { + require "hooks/pre_uninstall" + PLUGIN:PreUninstall($ctx) + }) + .await + } +} + +pub struct PreUninstallContext { + pub main: SdkInfo, + pub sdk_info: BTreeMap, +} + +impl IntoLua for PreUninstallContext { + fn into_lua(self, lua: &Lua) -> mlua::Result { + let table = lua.create_table()?; + table.set("main", self.main)?; + table.set("sdkInfo", self.sdk_info)?; + Ok(Value::Table(table)) + } +} diff --git a/crates/vfox/src/snapshots/vfox__vfox__tests__metadata.snap b/crates/vfox/src/snapshots/vfox__vfox__tests__metadata.snap index 43205feec0..fc5395ed44 100644 --- a/crates/vfox/src/snapshots/vfox__vfox__tests__metadata.snap +++ b/crates/vfox/src/snapshots/vfox__vfox__tests__metadata.snap @@ -2,4 +2,4 @@ source: crates/vfox/src/vfox.rs expression: out --- -Metadata { name: "dummy", legacy_filenames: [".dummy-version"], depends: [], version: "0.3.0", description: Some("Dummy plugin for testing."), author: None, license: Some("Apache 2.0"), homepage: Some("https://github.com/version-fox/vfox-nodejs"), hooks: {"available", "env_keys", "parse_legacy_file", "post_install", "pre_install"} } +Metadata { name: "dummy", legacy_filenames: [".dummy-version"], depends: [], version: "0.3.0", description: Some("Dummy plugin for testing."), author: None, license: Some("Apache 2.0"), homepage: Some("https://github.com/version-fox/vfox-nodejs"), hooks: {"available", "env_keys", "parse_legacy_file", "post_install", "pre_install", "pre_uninstall"} } diff --git a/crates/vfox/src/vfox.rs b/crates/vfox/src/vfox.rs index 240bfcf77b..42a5f0cc85 100644 --- a/crates/vfox/src/vfox.rs +++ b/crates/vfox/src/vfox.rs @@ -19,6 +19,7 @@ use crate::hooks::mise_path::MisePathContext; use crate::hooks::parse_legacy_file::ParseLegacyFileResponse; use crate::hooks::post_install::PostInstallContext; use crate::hooks::pre_install::{PreInstall, PreInstallAttestation, VerifiedAttestation}; +use crate::hooks::pre_uninstall::PreUninstallContext; use crate::http::{CLIENT, retry_async}; use crate::metadata::Metadata; use crate::plugin::Plugin; @@ -234,6 +235,24 @@ impl Vfox { }) } + pub async fn pre_uninstall>( + &self, + sdk: &str, + version: &str, + install_dir: ID, + ) -> Result<()> { + let sdk = self.get_sdk_with_env(sdk)?; + if sdk.get_metadata()?.hooks.contains("pre_uninstall") { + let sdk_info = sdk.sdk_info(version.to_string(), install_dir.as_ref().to_path_buf())?; + sdk.pre_uninstall(PreUninstallContext { + main: sdk_info.clone(), + sdk_info: BTreeMap::from([(sdk_info.name.clone(), sdk_info)]), + }) + .await?; + } + Ok(()) + } + pub fn uninstall(&self, sdk: &str, version: &str) -> Result<()> { let path = self.install_dir.join(sdk).join(version); file::remove_dir_all(&path)?; @@ -682,6 +701,28 @@ mod tests { file::remove_dir_all(vfox.download_dir).unwrap(); } + #[tokio::test] + async fn test_pre_uninstall() { + let temp_dir = tempfile::tempdir().unwrap(); + let mut vfox = Vfox::test(); + vfox.install_dir = temp_dir.path().join("installs"); + let install_dir = vfox.install_dir.join("dummy").join("1.0.0"); + std::fs::create_dir_all(&install_dir).unwrap(); + + vfox.pre_uninstall("dummy", "1.0.0", &install_dir) + .await + .unwrap(); + + let marker = std::fs::read_to_string(install_dir.join("pre_uninstall_marker")).unwrap(); + assert_eq!( + marker, + format!( + "dummy:1.0.0:{}", + install_dir.to_string_lossy().replace('\\', "/") + ) + ); + } + #[tokio::test] #[ignore] // disable for now async fn test_install_cmake() { diff --git a/e2e/backend/test_vfox_pre_uninstall b/e2e/backend/test_vfox_pre_uninstall new file mode 100644 index 0000000000..302ca08261 --- /dev/null +++ b/e2e/backend/test_vfox_pre_uninstall @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +PLUGIN_DIR="$PWD/vfox-pre-uninstall" +MARKER="$MISE_DATA_DIR/pre-uninstall-marker" + +mkdir -p "$PLUGIN_DIR/hooks" + +cat >"$PLUGIN_DIR/metadata.lua" <<'LUA' +PLUGIN = {} +PLUGIN.name = "pre-uninstall" +PLUGIN.version = "0.1.0" +PLUGIN.homepage = "https://example.com/pre-uninstall" +PLUGIN.license = "MIT" +PLUGIN.description = "pre uninstall test plugin" +LUA + +cat >"$PLUGIN_DIR/hooks/available.lua" <<'LUA' +function PLUGIN:Available(ctx) + return { + { version = "1.0.0" }, + } +end +LUA + +cat >"$PLUGIN_DIR/hooks/pre_install.lua" <<'LUA' +function PLUGIN:PreInstall(ctx) + return { + version = ctx.version, + } +end +LUA + +cat >"$PLUGIN_DIR/hooks/post_install.lua" <<'LUA' +function PLUGIN:PostInstall(ctx) + os.execute("mkdir -p " .. ctx.rootPath .. "/bin") + local bin = io.open(ctx.rootPath .. "/bin/pre-uninstall", "w") + if bin then + bin:write("#!/bin/sh\necho pre-uninstall\n") + bin:close() + os.execute("chmod +x " .. ctx.rootPath .. "/bin/pre-uninstall") + end +end +LUA + +cat >"$PLUGIN_DIR/hooks/env_keys.lua" <<'LUA' +function PLUGIN:EnvKeys(ctx) + return { + { key = "PATH", value = ctx.path .. "/bin" }, + } +end +LUA + +cat >"$PLUGIN_DIR/hooks/pre_uninstall.lua" <<'LUA' +function PLUGIN:PreUninstall(ctx) + local marker = os.getenv("MISE_DATA_DIR") .. "/pre-uninstall-marker" + local file = io.open(marker, "w") + if file then + file:write(ctx.main.name .. ":" .. ctx.main.version .. ":" .. ctx.sdkInfo[ctx.main.name].path) + file:close() + end +end +LUA + +git -C "$PLUGIN_DIR" init -q +git -C "$PLUGIN_DIR" add . +git -C "$PLUGIN_DIR" -c user.email=mise@example.com -c user.name=mise commit -m "init" + +mise install "vfox:file://$PLUGIN_DIR@1.0.0" + +mise uninstall "vfox:file://$PLUGIN_DIR@1.0.0" --dry-run +if [[ -f $MARKER ]]; then + fail "pre_uninstall hook ran during dry-run" +fi + +mise uninstall "vfox:file://$PLUGIN_DIR@1.0.0" +assert_contains "cat '$MARKER'" "pre-uninstall:1.0.0:" diff --git a/src/backend/vfox.rs b/src/backend/vfox.rs index d269a6008f..35e9fce0de 100644 --- a/src/backend/vfox.rs +++ b/src/backend/vfox.rs @@ -271,6 +271,30 @@ impl Backend for VfoxBackend { Some(&self.plugin_enum) } + async fn uninstall_version_impl( + &self, + config: &Arc, + _pr: &dyn crate::ui::progress_report::SingleReport, + tv: &ToolVersion, + ) -> eyre::Result<()> { + if self.is_backend_plugin() || !self.plugin.is_installed() { + return Ok(()); + } + + let (mut vfox, log_rx) = self.plugin.vfox(); + thread::spawn(|| { + for line in log_rx { + info!("{}", line); + } + }); + if let Ok(dep_env) = self.dependency_env(config).await { + vfox.cmd_env = Some(dep_env.into_iter().collect()); + } + vfox.pre_uninstall(&self.pathname, &tv.version, tv.install_path()) + .await?; + Ok(()) + } + async fn _idiomatic_filenames(&self) -> eyre::Result> { let (vfox, _log_rx) = self.plugin.vfox();