diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index e8ecc6bc6..73c0cfd91 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -10,7 +10,7 @@ use crate::commands::project::{ use crate::printer::Printer; use crate::settings::ResolverInstallerSettings; -use uv_cache::{Cache, CacheBucket}; +use uv_cache::{Cache, CacheBucket, Freshness}; use uv_cache_key::{cache_digest, hash_digest}; use uv_client::BaseClientBuilder; use uv_configuration::{Concurrency, Constraints, TargetTriple}; @@ -174,10 +174,13 @@ impl CachedEnvironment { // Search in the content-addressed cache. let cache_entry = cache.entry(CacheBucket::Environments, interpreter_hash, resolution_hash); - if let Ok(root) = cache.resolve_link(cache_entry.path()) { - if let Ok(environment) = PythonEnvironment::from_root(root, cache) { - return Ok(Self(environment)); - } + if cache + .freshness(&cache_entry, None, None) + .is_ok_and(Freshness::is_fresh) + && let Ok(root) = cache.resolve_link(cache_entry.path()) + && let Ok(environment) = PythonEnvironment::from_root(root, cache) + { + return Ok(Self(environment)); } // Create the environment in the cache, then relocate it to its content-addressed location. diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index 26bd5d636..8c9bd6b4b 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -1,4 +1,4 @@ -use crate::common::{TestContext, uv_snapshot}; +use crate::common::{TestContext, make_project, uv_snapshot}; use anyhow::Result; use assert_cmd::prelude::*; use assert_fs::prelude::*; @@ -734,7 +734,7 @@ fn tool_run_cache() { Resolved [N] packages in [TIME] "###); - // Verify that `--refresh` allows cache reuse. + // Verify that `--refresh` invalidates the cached environment. uv_snapshot!(context.filters(), context.tool_run() .arg("-p") .arg("3.12") @@ -742,7 +742,7 @@ fn tool_run_cache() { .arg("black") .arg("--version") .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) - .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -751,9 +751,17 @@ fn tool_run_cache() { ----- stderr ----- Resolved [N] packages in [TIME] - "); + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + black==24.3.0 + + click==8.1.7 + + mypy-extensions==1.0.0 + + packaging==24.0 + + pathspec==0.12.1 + + platformdirs==4.2.0 + "###); - // Verify that `--refresh-package` allows cache reuse. + // Verify that `--refresh-package` invalidates the cached environment. uv_snapshot!(context.filters(), context.tool_run() .arg("-p") .arg("3.12") @@ -762,7 +770,7 @@ fn tool_run_cache() { .arg("black") .arg("--version") .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) - .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" success: true exit_code: 0 ----- stdout ----- @@ -771,7 +779,15 @@ fn tool_run_cache() { ----- stderr ----- Resolved [N] packages in [TIME] - "); + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + black==24.3.0 + + click==8.1.7 + + mypy-extensions==1.0.0 + + packaging==24.0 + + pathspec==0.12.1 + + platformdirs==4.2.0 + "###); // Verify that varying the interpreter leads to a fresh environment. uv_snapshot!(context.filters(), context.tool_run() @@ -847,6 +863,79 @@ fn tool_run_cache() { "###); } +#[test] +fn tool_run_refresh_local_project() -> Result<()> { + let context = TestContext::new("3.12").with_filtered_counts(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + let project_dir = context.temp_dir.child("local-tool"); + + make_project( + project_dir.path(), + "local_tool", + indoc! {r#" + [project.scripts] + local-tool = "local_tool:main" + "#}, + )?; + project_dir + .child("src") + .child("local_tool") + .child("__init__.py") + .write_str(indoc! {r#" + def main(): + print("v1") + "#})?; + + uv_snapshot!(context.filters(), context.tool_run() + .arg("--from") + .arg(project_dir.as_os_str()) + .arg("local-tool") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + v1 + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + local-tool==0.1.0 (from file://[TEMP_DIR]/local-tool) + "###); + + project_dir + .child("src") + .child("local_tool") + .child("__init__.py") + .write_str(indoc! {r#" + def main(): + print("v2") + "#})?; + + uv_snapshot!(context.filters(), context.tool_run() + .arg("--refresh") + .arg("--from") + .arg(project_dir.as_os_str()) + .arg("local-tool") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + v2 + + ----- stderr ----- + Resolved [N] packages in [TIME] + Prepared [N] packages in [TIME] + Installed [N] packages in [TIME] + + local-tool==0.1.0 (from file://[TEMP_DIR]/local-tool) + "###); + + Ok(()) +} + #[test] fn tool_run_url() { let context = TestContext::new("3.12").with_filtered_counts();