diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06e68cef5..c12ea0123 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -285,6 +285,7 @@ jobs: env: CARGO_HOME: ${{ env.DEV_DRIVE }}/.cargo RUSTUP_HOME: ${{ env.DEV_DRIVE }}/.rustup + UV_INTERNAL__TEST_DIR: ${{ env.DEV_DRIVE }}/tmp-uv run: | cargo nextest run --no-default-features --features python,pypi --workspace --status-level skip --failure-output immediate-final --no-fail-fast -j 20 --final-status-level slow diff --git a/Cargo.lock b/Cargo.lock index 12b4b0f77..b462f2c8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,21 +43,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -211,7 +196,6 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" dependencies = [ - "brotli", "bzip2", "flate2", "futures-core", @@ -461,27 +445,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "510a90332002c1af3317ef6b712f0dab697f30bbe809b86965eac2923c0bca8e" -[[package]] -name = "brotli" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bstr" version = "1.10.0" diff --git a/Cargo.toml b/Cargo.toml index bb9090b50..8baa67eed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,7 @@ quote = { version = "1.0.36" } rayon = { version = "1.8.0" } reflink-copy = { version = "0.1.15" } regex = { version = "1.10.2" } -reqwest = { version = "0.12.3", default-features = false, features = ["json", "gzip", "brotli", "stream", "rustls-tls", "rustls-tls-native-roots"] } +reqwest = { version = "0.12.3", default-features = false, features = ["json", "gzip", "stream", "rustls-tls", "rustls-tls-native-roots"] } reqwest-middleware = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "5e3eaf254b5bd481c75d2710eed055f95b756913" } reqwest-retry = { git = "https://github.com/astral-sh/reqwest-middleware", rev = "5e3eaf254b5bd481c75d2710eed055f95b756913" } rkyv = { version = "0.7.43", features = ["strict", "validation"] } diff --git a/crates/uv-cache/src/lib.rs b/crates/uv-cache/src/lib.rs index 5d76ad738..0c48d0e42 100644 --- a/crates/uv-cache/src/lib.rs +++ b/crates/uv-cache/src/lib.rs @@ -135,7 +135,14 @@ impl Cache { /// Create a temporary cache directory. pub fn temp() -> Result { - let temp_dir = tempfile::tempdir()?; + let temp_dir = + if let Ok(test_dir) = std::env::var("UV_INTERNAL__TEST_DIR").map(PathBuf::from) { + let uv_cache_dir = test_dir.join("uv-cache"); + let _ = fs_err::create_dir_all(&uv_cache_dir); + tempfile::tempdir_in(uv_cache_dir)? + } else { + tempfile::tempdir()? + }; Ok(Self { root: temp_dir.path().to_path_buf(), refresh: Refresh::None(Timestamp::now()), diff --git a/crates/uv/src/commands/tool/uninstall.rs b/crates/uv/src/commands/tool/uninstall.rs index b43a9e606..a171743a1 100644 --- a/crates/uv/src/commands/tool/uninstall.rs +++ b/crates/uv/src/commands/tool/uninstall.rs @@ -32,10 +32,14 @@ pub(crate) async fn uninstall(name: Option, printer: Printer) -> Re // Clean up any empty directories. if uv_fs::directories(installed_tools.root()).all(|path| uv_fs::is_temporary(&path)) { - fs_err::tokio::remove_dir_all(&installed_tools.root()).await?; + fs_err::tokio::remove_dir_all(&installed_tools.root()) + .await + .ignore_currently_being_deleted()?; if let Some(top_level) = installed_tools.root().parent() { if uv_fs::directories(top_level).all(|path| uv_fs::is_temporary(&path)) { - fs_err::tokio::remove_dir_all(top_level).await?; + fs_err::tokio::remove_dir_all(top_level) + .await + .ignore_currently_being_deleted()?; } } } @@ -43,6 +47,44 @@ pub(crate) async fn uninstall(name: Option, printer: Printer) -> Re Ok(ExitStatus::Success) } +trait IoErrorExt: std::error::Error + 'static { + #[inline] + fn is_in_process_of_being_deleted(&self) -> bool { + if cfg!(target_os = "windows") { + use std::error::Error; + let mut e: &dyn Error = &self; + loop { + if e.to_string().contains("The file cannot be opened because it is in the process of being deleted. (os error 303)") { + return true; + } + e = match e.source() { + Some(e) => e, + None => break, + } + } + } + + false + } +} + +impl IoErrorExt for std::io::Error {} + +/// An extension trait to suppress "cannot open file because it's currently being deleted" +trait IgnoreCurrentlyBeingDeleted { + fn ignore_currently_being_deleted(self) -> Self; +} + +impl IgnoreCurrentlyBeingDeleted for Result<(), std::io::Error> { + fn ignore_currently_being_deleted(self) -> Self { + match self { + Ok(()) => Ok(()), + Err(err) if err.is_in_process_of_being_deleted() => Ok(()), + Err(err) => Err(err), + } + } +} + /// Perform the uninstallation. async fn do_uninstall( installed_tools: &InstalledTools,