diff --git a/docs/hooks.md b/docs/hooks.md index 5b4c8d4eb1..036abefc82 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -33,26 +33,12 @@ leave = "echo 'I left the project'" ## Preinstall/postinstall hook -These hooks are run before and after each tool is installed (respectively). Unlike other hooks, these hooks do not require `mise activate`. - -The hooks run once per tool being installed, with `MISE_TOOL_NAME` and `MISE_TOOL_VERSION` environment variables set to the tool being processed. - -```toml -[hooks] -preinstall = "echo 'About to install $MISE_TOOL_NAME@$MISE_TOOL_VERSION'" -postinstall = "echo 'Finished installing $MISE_TOOL_NAME@$MISE_TOOL_VERSION'" -``` - -You can use these variables to run conditional logic based on the tool: +These hooks are run before and after tools are installed (respectively). Unlike other hooks, these hooks do not require `mise activate`. ```toml [hooks] -postinstall = ''' -if [ "$MISE_TOOL_NAME" = "node" ]; then - echo "Node.js $MISE_TOOL_VERSION installed, running npm setup..." - npm config set prefix ~/.npm-global -fi -''' +preinstall = "echo 'I am about to install tools'" +postinstall = "echo 'I just installed tools'" ``` ## Watch files hook @@ -77,15 +63,6 @@ Hooks are executed with the following environment variables set: - `MISE_PROJECT_ROOT`: The root directory of the project. - `MISE_PREVIOUS_DIR`: The directory that the user was in before the directory change (only if a directory change occurred). -For `preinstall` and `postinstall` hooks, the following additional environment variables are set: - -- `MISE_TOOL_NAME`: The short name of the tool being installed (e.g., `node`, `python`, `go`). -- `MISE_TOOL_VERSION`: The version of the tool being installed (e.g., `20.10.0`, `3.12.0`). - -For tool-level postinstall hooks (defined on the tool itself), an additional variable is available: - -- `MISE_TOOL_INSTALL_PATH`: The installation path of the tool. - ## Shell hooks Hooks can be executed in the current shell, for example if you'd like to add bash completions when entering a directory: diff --git a/e2e/config/test_hooks_postinstall_env b/e2e/config/test_hooks_postinstall_env index 4f4e4fcfe8..550b90d59a 100755 --- a/e2e/config/test_hooks_postinstall_env +++ b/e2e/config/test_hooks_postinstall_env @@ -15,7 +15,7 @@ EXPLICIT_PRE_TOOLS = {value = "explicitly_pre_tools", tools = false} POST_TOOLS_VAR = {value = "available_after_tools", tools = true} [tools] -dummy = { version = "latest", postinstall = "echo PRE_TOOLS_VAR=\$PRE_TOOLS_VAR; echo EXPLICIT_PRE_TOOLS=\$EXPLICIT_PRE_TOOLS; echo POST_TOOLS_VAR=\$POST_TOOLS_VAR; echo MISE_TOOL_NAME=\$MISE_TOOL_NAME; echo MISE_TOOL_VERSION=\$MISE_TOOL_VERSION" } +dummy = { version = "latest", postinstall = "echo PRE_TOOLS_VAR=\$PRE_TOOLS_VAR; echo EXPLICIT_PRE_TOOLS=\$EXPLICIT_PRE_TOOLS; echo POST_TOOLS_VAR=\$POST_TOOLS_VAR" } EOF # Remove any existing dummy installation to force reinstall @@ -58,21 +58,3 @@ else echo "Output: $output" exit 1 fi - -# Verify MISE_TOOL_NAME is set -if [[ $output == *"MISE_TOOL_NAME=dummy"* ]]; then - echo "✓ MISE_TOOL_NAME is set correctly" -else - echo "✗ MISE_TOOL_NAME is not set correctly" - echo "Output: $output" - exit 1 -fi - -# Verify MISE_TOOL_VERSION is set (should be "latest" resolved version) -if [[ $output == *"MISE_TOOL_VERSION="* ]] && [[ $output != *"MISE_TOOL_VERSION=$"* ]]; then - echo "✓ MISE_TOOL_VERSION is set" -else - echo "✗ MISE_TOOL_VERSION is not set" - echo "Output: $output" - exit 1 -fi diff --git a/e2e/config/test_hooks_tool_env b/e2e/config/test_hooks_tool_env deleted file mode 100644 index dbb8537c7a..0000000000 --- a/e2e/config/test_hooks_tool_env +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -# Test that MISE_TOOL_NAME and MISE_TOOL_VERSION are set in preinstall/postinstall hooks - -cat <mise.toml -[tools] -dummy = 'latest' - -[hooks] -preinstall = 'echo "PREINSTALL: name=\$MISE_TOOL_NAME version=\$MISE_TOOL_VERSION"' -postinstall = 'echo "POSTINSTALL: name=\$MISE_TOOL_NAME version=\$MISE_TOOL_VERSION"' -EOF - -# Install dummy tool and check that env vars are set -assert_contains "mise i dummy@1.0.0 2>&1" "PREINSTALL: name=dummy version=1.0.0" -assert_contains "mise i dummy@1.0.0 2>&1" "POSTINSTALL: name=dummy version=1.0.0" - -# Test with a different version -assert_contains "mise i dummy@2.0.0 2>&1" "PREINSTALL: name=dummy version=2.0.0" -assert_contains "mise i dummy@2.0.0 2>&1" "POSTINSTALL: name=dummy version=2.0.0" diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 829f5d731b..b63af91131 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -851,8 +851,6 @@ pub trait Backend: Debug + Send + Sync { CmdLineRunner::new(&*env::SHELL) .env(&*env::PATH_KEY, plugins::core::path_env_with_tv_path(tv)?) .env("MISE_TOOL_INSTALL_PATH", tv.install_path()) - .env("MISE_TOOL_NAME", &tv.ba().short) - .env("MISE_TOOL_VERSION", &tv.version) .with_pr(ctx.pr.as_ref()) .arg(env::SHELL_COMMAND_FLAG) .arg(script) diff --git a/src/hooks.rs b/src/hooks.rs index 2194f76bf2..bbbe47e767 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -12,13 +12,6 @@ use std::sync::Mutex; use std::{iter::once, sync::Arc}; use tokio::sync::OnceCell; -/// Context for tool-specific hooks (preinstall/postinstall) -#[derive(Debug, Clone)] -pub struct HookToolContext { - pub name: String, - pub version: String, -} - #[derive( Debug, Clone, @@ -91,38 +84,6 @@ pub async fn run_one_hook( ts: &Toolset, hook: Hooks, shell: Option<&dyn Shell>, -) { - run_one_hook_with_shell(config, ts, hook, shell).await; -} - -/// Run a hook with optional tool context for preinstall/postinstall hooks (used during installation) -/// This version doesn't take a shell parameter and can be used in spawned async tasks. -#[async_backtrace::framed] -pub async fn run_one_hook_with_tool( - config: &Arc, - ts: &Toolset, - hook: Hooks, - tool_ctx: &HookToolContext, -) { - for (root, h) in all_hooks(config).await { - if hook != h.hook || h.shell.is_some() { - // Skip shell-specific hooks during installation - continue; - } - trace!("running hook {hook} in {root:?}"); - if let Err(e) = execute(config, ts, root, h, Some(tool_ctx)).await { - warn!("error executing hook: {e}"); - } - } -} - -/// Run a hook with optional shell context (used during activate) -#[async_backtrace::framed] -async fn run_one_hook_with_shell( - config: &Arc, - ts: &Toolset, - hook: Hooks, - shell: Option<&dyn Shell>, ) { for (root, h) in all_hooks(config).await { if hook != h.hook || (h.shell.is_some() && h.shell != shell.map(|s| s.to_string())) { @@ -155,7 +116,7 @@ async fn run_one_hook_with_shell( } if h.shell.is_some() { println!("{}", h.script); - } else if let Err(e) = execute(config, ts, root, h, None).await { + } else if let Err(e) = execute(config, ts, root, h).await { warn!("error executing hook: {e}"); } } @@ -198,13 +159,7 @@ impl Hook { } } -async fn execute( - config: &Arc, - ts: &Toolset, - root: &Path, - hook: &Hook, - tool_ctx: Option<&HookToolContext>, -) -> Result<()> { +async fn execute(config: &Arc, ts: &Toolset, root: &Path, hook: &Hook) -> Result<()> { Settings::get().ensure_experimental("hooks")?; let shell = Settings::get().default_inline_shell()?; @@ -231,11 +186,6 @@ async fn execute( old.to_string_lossy().to_string(), ); } - // Add tool context for preinstall/postinstall hooks - if let Some(ctx) = tool_ctx { - env.insert("MISE_TOOL_NAME".to_string(), ctx.name.clone()); - env.insert("MISE_TOOL_VERSION".to_string(), ctx.version.clone()); - } // TODO: this should be different but I don't have easy access to it // env.insert("MISE_CONFIG_ROOT".to_string(), root.to_string_lossy().to_string()); cmd(&shell[0], args) diff --git a/src/toolset/toolset_install.rs b/src/toolset/toolset_install.rs index 3ced61fa08..876b87ad53 100644 --- a/src/toolset/toolset_install.rs +++ b/src/toolset/toolset_install.rs @@ -9,7 +9,7 @@ use tokio::{sync::Semaphore, task::JoinSet}; use crate::config::Config; use crate::config::settings::Settings; use crate::errors::Error; -use crate::hooks::{HookToolContext, Hooks}; +use crate::hooks::Hooks; use crate::install_context::InstallContext; use crate::toolset::Toolset; use crate::toolset::helpers::{get_leaf_dependencies, show_python_install_hint}; @@ -100,6 +100,12 @@ impl Toolset { }; mpr.init_footer(opts.dry_run, &footer_reason, versions.len()); + // Skip hooks in dry-run mode + if !opts.dry_run { + // Run pre-install hook + hooks::run_one_hook(config, self, Hooks::Preinstall, None).await; + } + self.init_request_options(&mut versions); show_python_install_hint(&versions); @@ -170,6 +176,12 @@ impl Toolset { } } + // Skip hooks in dry-run mode + if !opts.dry_run { + // Run post-install hook (ignoring errors) + let _ = hooks::run_one_hook(config, self, Hooks::Postinstall, None).await; + } + // Finish the global footer if !opts.dry_run { mpr.footer_finish(); @@ -308,21 +320,6 @@ impl Toolset { let result = async { let tv = tr.resolve(&config, &opts.resolve_options).await?; - // Run per-tool preinstall hook - if !opts.dry_run { - let tool_ctx = HookToolContext { - name: tv.ba().short.clone(), - version: tv.version.clone(), - }; - hooks::run_one_hook_with_tool( - &config, - &ts, - Hooks::Preinstall, - &tool_ctx, - ) - .await; - } - let ctx = InstallContext { config: config.clone(), ts: ts.clone(), @@ -333,26 +330,7 @@ impl Toolset { }; // Avoid wrapping the backend error here so the error location // points to the backend implementation (more helpful for debugging). - let result = ba.install_version(ctx, tv).await; - - // Run per-tool postinstall hook (only on success) - if !opts.dry_run - && let Ok(ref installed_tv) = result - { - let tool_ctx = HookToolContext { - name: installed_tv.ba().short.clone(), - version: installed_tv.version.clone(), - }; - hooks::run_one_hook_with_tool( - &config, - &ts, - Hooks::Postinstall, - &tool_ctx, - ) - .await; - } - - result + ba.install_version(ctx, tv).await } .await;