diff --git a/crates/uv-installer/src/installer.rs b/crates/uv-installer/src/installer.rs index fb67dbe20113..546bc67278e5 100644 --- a/crates/uv-installer/src/installer.rs +++ b/crates/uv-installer/src/installer.rs @@ -1,8 +1,7 @@ -use std::convert; - use anyhow::{Context, Error, Result}; use install_wheel_rs::{linker::LinkMode, Layout}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use std::convert; use tokio::sync::oneshot; use tracing::instrument; diff --git a/crates/uv/src/commands/project/environment.rs b/crates/uv/src/commands/project/environment.rs index 484bea8d04d8..eb8a8b35212a 100644 --- a/crates/uv/src/commands/project/environment.rs +++ b/crates/uv/src/commands/project/environment.rs @@ -5,7 +5,6 @@ use distribution_types::Resolution; use uv_cache::{Cache, CacheBucket}; use uv_client::Connectivity; use uv_configuration::{Concurrency, PreviewMode}; -use uv_fs::{LockedFile, Simplified}; use uv_python::{Interpreter, PythonEnvironment}; use uv_requirements::RequirementsSpecification; @@ -86,56 +85,25 @@ impl CachedEnvironment { // Search in the content-addressed cache. let cache_entry = cache.entry(CacheBucket::Environments, interpreter_hash, resolution_hash); - // Lock at the interpreter level, to avoid concurrent modification across processes. - fs_err::tokio::create_dir_all(cache_entry.dir()).await?; - let _lock = LockedFile::acquire( - cache_entry.dir().join(".lock"), - cache_entry.dir().user_display(), - )?; - - let ok = cache_entry.path().join(".ok"); - if settings.reinstall.is_none() { - // If the receipt exists, return the environment. - if ok.is_file() { - debug!( - "Reusing cached environment at: `{}`", - cache_entry.path().display() - ); - return Ok(Self(PythonEnvironment::from_root( - cache_entry.path(), - cache, - )?)); - } - } else { - // If the receipt exists, remove it. - match fs_err::tokio::remove_file(&ok).await { - Ok(()) => { - debug!( - "Removed receipt for environment at: `{}`", - cache_entry.path().display() - ); + if let Ok(root) = fs_err::read_link(cache_entry.path()) { + if let Ok(environment) = PythonEnvironment::from_root(root, cache) { + return Ok(Self(environment)); } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} - Err(err) => return Err(err.into()), } } - debug!( - "Creating cached environment at: `{}`", - cache_entry.path().display() - ); - + // Create the environment in the cache, then relocate it to its content-addressed location. + let temp_dir = cache.environment()?; let venv = uv_virtualenv::create_venv( - cache_entry.path(), + temp_dir.path(), interpreter, uv_virtualenv::Prompt::None, false, false, - false, + true, )?; - - let venv = sync_environment( + sync_environment( venv, &resolution, settings.as_ref().into(), @@ -149,10 +117,13 @@ impl CachedEnvironment { ) .await?; - // Create the receipt, to indicate to future readers that the environment is complete. - fs_err::tokio::File::create(ok).await?; + // Now that the environment is complete, sync it to its content-addressed location. + let id = cache + .persist(temp_dir.into_path(), cache_entry.path()) + .await?; + let root = cache.archive(&id); - Ok(Self(venv)) + Ok(Self(PythonEnvironment::from_root(root, cache)?)) } /// Convert the [`CachedEnvironment`] into an [`Interpreter`]. diff --git a/crates/uv/tests/cache_prune.rs b/crates/uv/tests/cache_prune.rs index fc1c5aefc29b..8ae2600a6523 100644 --- a/crates/uv/tests/cache_prune.rs +++ b/crates/uv/tests/cache_prune.rs @@ -122,6 +122,7 @@ fn prune_cached_env() { DEBUG uv [VERSION] ([COMMIT] DATE) Pruning cache at: [CACHE_DIR]/ DEBUG Removing dangling cache entry: [CACHE_DIR]/environments-v1/[ENTRY] + DEBUG Removing dangling cache entry: [CACHE_DIR]/archive-v0/[ENTRY] Removed [N] files ([SIZE]) "###); }