Remove fs2 dependency and update Rust to 1.89 (#15764)

<!--
Thank you for contributing to uv! To help us out with reviewing, please
consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? --> 

This PR removes the crate fs2 and updates Rust version to 1.89.

*Why?*

Crate fs2 is unmaintained for a long time now and has unfixed issues.
Especially it doesn't build on AIX, which is the reason I started fixing
it.

*How?*

I removed fs2 and replaced it by std:fs:File methods.

## Test Plan

<!-- How was it tested? -->
- I built it on Windows and AIX only.
- I did not test the artifacts.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
Michael Richter 2025-11-02 22:33:53 +01:00 committed by GitHub
parent 6da135a66a
commit 485503ee65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 27 additions and 58 deletions

11
Cargo.lock generated
View File

@ -1443,16 +1443,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "futures"
version = "0.3.31"
@ -5892,7 +5882,6 @@ dependencies = [
"either",
"encoding_rs_io",
"fs-err",
"fs2",
"junction",
"path-slash",
"percent-encoding",

View File

@ -112,7 +112,6 @@ encoding_rs_io = { version = "0.1.7" }
etcetera = { version = "0.11.0" }
flate2 = { version = "1.0.33", default-features = false, features = ["zlib-rs"] }
fs-err = { version = "3.0.0", features = ["tokio"] }
fs2 = { version = "0.4.3" }
futures = { version = "0.3.30" }
glob = { version = "0.3.1" }
globset = { version = "0.4.15" }

View File

@ -20,14 +20,13 @@ dunce = { workspace = true }
either = { workspace = true }
encoding_rs_io = { workspace = true }
fs-err = { workspace = true }
fs2 = { workspace = true }
path-slash = { workspace = true }
percent-encoding = { workspace = true }
same-file = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
tempfile = { workspace = true }
tokio = { workspace = true, optional = true}
tokio = { workspace = true, optional = true }
tracing = { workspace = true }
[target.'cfg(any(unix, target_os = "wasi", target_os = "redox"))'.dependencies]

View File

@ -1,9 +1,7 @@
use std::borrow::Cow;
use std::fmt::Display;
use std::io;
use std::path::{Path, PathBuf};
use fs2::FileExt;
use tempfile::NamedTempFile;
use tracing::{debug, error, info, trace, warn};
@ -555,10 +553,12 @@ pub fn persist_with_retry_sync(
/// Iterate over the subdirectories of a directory.
///
/// If the directory does not exist, returns an empty iterator.
pub fn directories(path: impl AsRef<Path>) -> Result<impl Iterator<Item = PathBuf>, io::Error> {
pub fn directories(
path: impl AsRef<Path>,
) -> Result<impl Iterator<Item = PathBuf>, std::io::Error> {
let entries = match path.as_ref().read_dir() {
Ok(entries) => Some(entries),
Err(err) if err.kind() == io::ErrorKind::NotFound => None,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => return Err(err),
};
Ok(entries
@ -578,10 +578,10 @@ pub fn directories(path: impl AsRef<Path>) -> Result<impl Iterator<Item = PathBu
/// Iterate over the entries in a directory.
///
/// If the directory does not exist, returns an empty iterator.
pub fn entries(path: impl AsRef<Path>) -> Result<impl Iterator<Item = PathBuf>, io::Error> {
pub fn entries(path: impl AsRef<Path>) -> Result<impl Iterator<Item = PathBuf>, std::io::Error> {
let entries = match path.as_ref().read_dir() {
Ok(entries) => Some(entries),
Err(err) if err.kind() == io::ErrorKind::NotFound => None,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => return Err(err),
};
Ok(entries
@ -600,10 +600,10 @@ pub fn entries(path: impl AsRef<Path>) -> Result<impl Iterator<Item = PathBuf>,
/// Iterate over the files in a directory.
///
/// If the directory does not exist, returns an empty iterator.
pub fn files(path: impl AsRef<Path>) -> Result<impl Iterator<Item = PathBuf>, io::Error> {
pub fn files(path: impl AsRef<Path>) -> Result<impl Iterator<Item = PathBuf>, std::io::Error> {
let entries = match path.as_ref().read_dir() {
Ok(entries) => Some(entries),
Err(err) if err.kind() == io::ErrorKind::NotFound => None,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => return Err(err),
};
Ok(entries
@ -653,17 +653,17 @@ pub fn is_virtualenv_base(path: impl AsRef<Path>) -> bool {
}
/// Whether the error is due to a lock being held.
fn is_known_already_locked_error(err: &std::io::Error) -> bool {
if matches!(err.kind(), std::io::ErrorKind::WouldBlock) {
return true;
fn is_known_already_locked_error(err: &std::fs::TryLockError) -> bool {
match err {
std::fs::TryLockError::WouldBlock => true,
std::fs::TryLockError::Error(err) => {
// On Windows, we've seen: Os { code: 33, kind: Uncategorized, message: "The process cannot access the file because another process has locked a portion of the file." }
if cfg!(windows) && err.raw_os_error() == Some(33) {
return true;
}
false
}
}
// On Windows, we've seen: Os { code: 33, kind: Uncategorized, message: "The process cannot access the file because another process has locked a portion of the file." }
if cfg!(windows) && err.raw_os_error() == Some(33) {
return true;
}
false
}
/// A file lock that is automatically released when dropped.
@ -678,7 +678,7 @@ impl LockedFile {
"Checking lock for `{resource}` at `{}`",
file.path().user_display()
);
match file.file().try_lock_exclusive() {
match file.file().try_lock() {
Ok(()) => {
debug!("Acquired lock for `{resource}`");
Ok(Self(file))
@ -692,15 +692,7 @@ impl LockedFile {
"Waiting to acquire lock for `{resource}` at `{}`",
file.path().user_display(),
);
file.file().lock_exclusive().map_err(|err| {
// Not an fs_err method, we need to build our own path context
std::io::Error::other(format!(
"Could not acquire lock for `{resource}` at `{}`: {}",
file.path().user_display(),
err
))
})?;
file.lock()?;
debug!("Acquired lock for `{resource}`");
Ok(Self(file))
}
@ -713,7 +705,7 @@ impl LockedFile {
"Checking lock for `{resource}` at `{}`",
file.path().user_display()
);
match file.file().try_lock_exclusive() {
match file.try_lock() {
Ok(()) => {
debug!("Acquired lock for `{resource}`");
Some(Self(file))
@ -739,8 +731,7 @@ impl LockedFile {
"Checking shared lock for `{resource}` at `{}`",
file.path().user_display()
);
// TODO(konsti): Update fs_err to support this.
match FileExt::try_lock_shared(file.file()) {
match file.try_lock_shared() {
Ok(()) => {
debug!("Acquired shared lock for `{resource}`");
Ok(Self(file))
@ -754,15 +745,7 @@ impl LockedFile {
"Waiting to acquire shared lock for `{resource}` at `{}`",
file.path().user_display(),
);
FileExt::lock_shared(file.file()).map_err(|err| {
// Not an fs_err method, we need to build our own path context
std::io::Error::other(format!(
"Could not acquire shared lock for `{resource}` at `{}`: {}",
file.path().user_display(),
err
))
})?;
file.lock_shared()?;
debug!("Acquired shared lock for `{resource}`");
Ok(Self(file))
}
@ -884,11 +867,10 @@ impl LockedFile {
impl Drop for LockedFile {
fn drop(&mut self) {
if let Err(err) = fs2::FileExt::unlock(self.0.file()) {
if let Err(err) = self.0.unlock() {
error!(
"Failed to unlock {}; program may be stuck: {}",
self.0.path().display(),
err
"Failed to unlock resource at `{}`; program may be stuck: {err}",
self.0.path().display()
);
} else {
debug!("Released lock at `{}`", self.0.path().display());