Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1931,8 +1931,17 @@ pub trait Backend: Debug + Send + Sync {
tv: &ToolVersion,
script: &str,
) -> eyre::Result<()> {
// Resolve the hook against the exact version just installed (locked) rather
// than the request's runtime symlink: for a fuzzy request (e.g. `version = "3"`)
// the runtime symlink (installs/python/3) still points at the previous version
// until all installs finish and symlinks are rebuilt. Without this, both the
// hook's env (exec_env, e.g. a backend deriving PYTHONHOME/JAVA_HOME from
// runtime_path()) and its PATH (list_bin_paths, e.g. `pip`) would resolve to a
// stale install (#10347).
let tv_exact = tv.clone().with_locked();

// Get pre-tools environment variables from config
let mut env_vars = self.exec_env(&ctx.config, &ctx.ts, tv).await?;
let mut env_vars = self.exec_env(&ctx.config, &ctx.ts, &tv_exact).await?;

// Add pre-tools environment variables from config if available
if let Some(config_env) = ctx.config.env_maybe() {
Expand All @@ -1944,8 +1953,8 @@ pub trait Backend: Debug + Send + Sync {

// Use the backend's list_bin_paths to get the correct binary directories
// instead of hardcoding install_path/bin, which may not match the actual
// binary location for backends like aqua
let bin_paths = self.list_bin_paths(&ctx.config, tv).await?;
// binary location for backends like aqua.
let bin_paths = self.list_bin_paths(&ctx.config, &tv_exact).await?;
let mut path_env = PathEnv::from_iter(env::PATH.clone());
for p in bin_paths {
path_env.add(p);
Expand Down
68 changes: 68 additions & 0 deletions src/toolset/tool_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ impl ToolVersion {
self
}

/// Returns a copy locked to the exact resolved version, so `runtime_path()`
/// resolves to `install_path()` rather than a fuzzy runtime symlink (e.g.
/// `installs/python/3`). Used during postinstall so the hook sees the version
/// just installed, not a stale symlink target (#10347).
pub(crate) fn with_locked(mut self) -> Self {
self.locked = true;
self
}

fn from_lockfile(request: ToolRequest, lt: LockfileTool) -> Self {
let mut tv = Self::new(request, lt.version);
tv.locked = true;
Expand Down Expand Up @@ -802,6 +811,65 @@ mod tests {
Ok(())
}

#[test]
fn with_locked_runtime_path_uses_install_path_for_fuzzy_request() -> Result<()> {
reset_install_path_cache();

let temp_dir = tempfile::tempdir()?;
let short = format!(
"dummy-locked-{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
);
let mut backend = BackendArg::new_raw(
short.clone(),
None,
short,
None,
BackendResolution::new(false),
);
backend.installs_path = temp_dir.path().join("installs").join("dummy");

let install_path = backend.installs_path.join("3.14.6");
fs::create_dir_all(install_path.join("bin"))?;

// Reproduce the stale state during `mise up` (#10347): the fuzzy runtime
// symlink installs/dummy/3 still points at a previous version (3.13.9) while
// 3.14.6 is the version just installed -- runtime symlinks are only rebuilt
// after all installs finish. is_runtime_symlink() requires a "./" target; on
// Windows runtime symlinks are stored as a file containing the target.
let old_path = backend.installs_path.join("3.13.9");
fs::create_dir_all(old_path.join("bin"))?;
let runtime_link = backend.installs_path.join("3");
#[cfg(unix)]
std::os::unix::fs::symlink("./3.13.9", &runtime_link)?;
#[cfg(windows)]
fs::write(&runtime_link, "./3.13.9")?;

// Fuzzy request ("3") resolved to a concrete version ("3.14.6").
let request = ToolRequest::Version {
backend: Arc::new(backend),
version: "3".into(),
options: ToolVersionOptions::default(),
source: ToolSource::Argument,
};
let tv = ToolVersion::new(request, "3.14.6".into());

// Without locking, runtime_path() follows the stale fuzzy runtime symlink, so
// it does NOT resolve to the version just installed -- the bug behavior. (This
// also self-checks the stale-symlink setup above.)
assert_ne!(tv.runtime_path(), install_path);

// with_locked() pins runtime_path() to the exact install just made, not the
// fuzzy runtime symlink, so the postinstall hook sees 3.14.6 (#10347).
assert_eq!(tv.clone().with_locked().runtime_path(), install_path);
assert_eq!(tv.clone().with_locked().runtime_path(), tv.install_path());

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Ok(())
}

/// Regression test for https://github.com/jdx/mise/discussions/9526
///
/// `install_path()` must not cache a path that does not yet exist. If it
Expand Down
Loading