mirror of https://github.com/astral-sh/uv
Create a dedicated crate
This commit is contained in:
parent
38ee6ec800
commit
93d444cca4
|
|
@ -4676,6 +4676,7 @@ dependencies = [
|
||||||
"uv-git-types",
|
"uv-git-types",
|
||||||
"uv-install-wheel",
|
"uv-install-wheel",
|
||||||
"uv-installer",
|
"uv-installer",
|
||||||
|
"uv-lock",
|
||||||
"uv-normalize",
|
"uv-normalize",
|
||||||
"uv-pep440",
|
"uv-pep440",
|
||||||
"uv-pep508",
|
"uv-pep508",
|
||||||
|
|
@ -4835,6 +4836,7 @@ dependencies = [
|
||||||
"uv-distribution",
|
"uv-distribution",
|
||||||
"uv-distribution-types",
|
"uv-distribution-types",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
|
"uv-lock",
|
||||||
"uv-pep440",
|
"uv-pep440",
|
||||||
"uv-pep508",
|
"uv-pep508",
|
||||||
"uv-pypi-types",
|
"uv-pypi-types",
|
||||||
|
|
@ -4864,6 +4866,7 @@ dependencies = [
|
||||||
"uv-dirs",
|
"uv-dirs",
|
||||||
"uv-distribution-types",
|
"uv-distribution-types",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
|
"uv-lock",
|
||||||
"uv-normalize",
|
"uv-normalize",
|
||||||
"uv-pypi-types",
|
"uv-pypi-types",
|
||||||
"uv-redacted",
|
"uv-redacted",
|
||||||
|
|
@ -5282,6 +5285,7 @@ dependencies = [
|
||||||
"uv-cache-key",
|
"uv-cache-key",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
"uv-git-types",
|
"uv-git-types",
|
||||||
|
"uv-lock",
|
||||||
"uv-redacted",
|
"uv-redacted",
|
||||||
"uv-static",
|
"uv-static",
|
||||||
"uv-version",
|
"uv-version",
|
||||||
|
|
@ -5392,6 +5396,20 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv-lock"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"fs-err 3.1.1",
|
||||||
|
"fs2",
|
||||||
|
"tempfile",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"uv-fs",
|
||||||
|
"uv-state",
|
||||||
|
"uv-static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv-macros"
|
name = "uv-macros"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
@ -5625,6 +5643,7 @@ dependencies = [
|
||||||
"uv-extract",
|
"uv-extract",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
"uv-install-wheel",
|
"uv-install-wheel",
|
||||||
|
"uv-lock",
|
||||||
"uv-pep440",
|
"uv-pep440",
|
||||||
"uv-pep508",
|
"uv-pep508",
|
||||||
"uv-platform-tags",
|
"uv-platform-tags",
|
||||||
|
|
@ -5889,6 +5908,7 @@ dependencies = [
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
"uv-install-wheel",
|
"uv-install-wheel",
|
||||||
"uv-installer",
|
"uv-installer",
|
||||||
|
"uv-lock",
|
||||||
"uv-pep440",
|
"uv-pep440",
|
||||||
"uv-pep508",
|
"uv-pep508",
|
||||||
"uv-pypi-types",
|
"uv-pypi-types",
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ uv-git-types = { path = "crates/uv-git-types" }
|
||||||
uv-globfilter = { path = "crates/uv-globfilter" }
|
uv-globfilter = { path = "crates/uv-globfilter" }
|
||||||
uv-install-wheel = { path = "crates/uv-install-wheel", default-features = false }
|
uv-install-wheel = { path = "crates/uv-install-wheel", default-features = false }
|
||||||
uv-installer = { path = "crates/uv-installer" }
|
uv-installer = { path = "crates/uv-installer" }
|
||||||
|
uv-lock = { path = "crates/uv-lock" }
|
||||||
uv-macros = { path = "crates/uv-macros" }
|
uv-macros = { path = "crates/uv-macros" }
|
||||||
uv-metadata = { path = "crates/uv-metadata" }
|
uv-metadata = { path = "crates/uv-metadata" }
|
||||||
uv-normalize = { path = "crates/uv-normalize" }
|
uv-normalize = { path = "crates/uv-normalize" }
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ uv-configuration = { workspace = true }
|
||||||
uv-distribution = { workspace = true }
|
uv-distribution = { workspace = true }
|
||||||
uv-distribution-types = { workspace = true }
|
uv-distribution-types = { workspace = true }
|
||||||
uv-fs = { workspace = true }
|
uv-fs = { workspace = true }
|
||||||
|
uv-lock = { workspace = true, features = ["tokio"] }
|
||||||
uv-pep440 = { workspace = true }
|
uv-pep440 = { workspace = true }
|
||||||
uv-pep508 = { workspace = true }
|
uv-pep508 = { workspace = true }
|
||||||
uv-pypi-types = { workspace = true }
|
uv-pypi-types = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ use uv_configuration::PreviewMode;
|
||||||
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy};
|
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy};
|
||||||
use uv_distribution::BuildRequires;
|
use uv_distribution::BuildRequires;
|
||||||
use uv_distribution_types::{IndexLocations, Requirement, Resolution};
|
use uv_distribution_types::{IndexLocations, Requirement, Resolution};
|
||||||
use uv_fs::LockedFile;
|
|
||||||
use uv_fs::{PythonExt, Simplified};
|
use uv_fs::{PythonExt, Simplified};
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_pep440::Version;
|
use uv_pep440::Version;
|
||||||
use uv_pep508::PackageName;
|
use uv_pep508::PackageName;
|
||||||
use uv_pypi_types::VerbatimParsedUrl;
|
use uv_pypi_types::VerbatimParsedUrl;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ uv-cache-key = { workspace = true }
|
||||||
uv-dirs = { workspace = true }
|
uv-dirs = { workspace = true }
|
||||||
uv-distribution-types = { workspace = true }
|
uv-distribution-types = { workspace = true }
|
||||||
uv-fs = { workspace = true, features = ["tokio"] }
|
uv-fs = { workspace = true, features = ["tokio"] }
|
||||||
|
uv-lock = { workspace = true, features = ["tokio"] }
|
||||||
uv-normalize = { workspace = true }
|
uv-normalize = { workspace = true }
|
||||||
uv-pypi-types = { workspace = true }
|
uv-pypi-types = { workspace = true }
|
||||||
uv-redacted = { workspace = true }
|
uv-redacted = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ use tracing::debug;
|
||||||
|
|
||||||
pub use archive::ArchiveId;
|
pub use archive::ArchiveId;
|
||||||
use uv_cache_info::Timestamp;
|
use uv_cache_info::Timestamp;
|
||||||
use uv_fs::{LockedFile, cachedir, directories};
|
use uv_fs::{cachedir, directories};
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pypi_types::ResolutionMetadata;
|
use uv_pypi_types::ResolutionMetadata;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
use std::fmt::Display;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use fs2::FileExt;
|
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::warn;
|
||||||
|
|
||||||
pub use crate::path::*;
|
pub use crate::path::*;
|
||||||
|
|
||||||
|
|
@ -599,137 +597,6 @@ pub fn is_virtualenv_base(path: impl AsRef<Path>) -> bool {
|
||||||
path.as_ref().join("pyvenv.cfg").is_file()
|
path.as_ref().join("pyvenv.cfg").is_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A file lock that is automatically released when dropped.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[must_use]
|
|
||||||
pub struct LockedFile(fs_err::File);
|
|
||||||
|
|
||||||
impl LockedFile {
|
|
||||||
/// Inner implementation for [`LockedFile::acquire_blocking`] and [`LockedFile::acquire`].
|
|
||||||
fn lock_file_blocking(file: fs_err::File, resource: &str) -> Result<Self, std::io::Error> {
|
|
||||||
trace!(
|
|
||||||
"Checking lock for `{resource}` at `{}`",
|
|
||||||
file.path().user_display()
|
|
||||||
);
|
|
||||||
match file.file().try_lock_exclusive() {
|
|
||||||
Ok(()) => {
|
|
||||||
debug!("Acquired lock for `{resource}`");
|
|
||||||
Ok(Self(file))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
// Log error code and enum kind to help debugging more exotic failures.
|
|
||||||
if err.kind() != std::io::ErrorKind::WouldBlock {
|
|
||||||
debug!("Try lock error: {err:?}");
|
|
||||||
}
|
|
||||||
info!(
|
|
||||||
"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
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
debug!("Acquired lock for `{resource}`");
|
|
||||||
Ok(Self(file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The same as [`LockedFile::acquire`], but for synchronous contexts. Do not use from an async
|
|
||||||
/// context, as this can block the runtime while waiting for another process to release the
|
|
||||||
/// lock.
|
|
||||||
pub fn acquire_blocking(
|
|
||||||
path: impl AsRef<Path>,
|
|
||||||
resource: impl Display,
|
|
||||||
) -> Result<Self, std::io::Error> {
|
|
||||||
let file = Self::create(path)?;
|
|
||||||
let resource = resource.to_string();
|
|
||||||
Self::lock_file_blocking(file, &resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Acquire a cross-process lock for a resource using a file at the provided path.
|
|
||||||
#[cfg(feature = "tokio")]
|
|
||||||
pub async fn acquire(
|
|
||||||
path: impl AsRef<Path>,
|
|
||||||
resource: impl Display,
|
|
||||||
) -> Result<Self, std::io::Error> {
|
|
||||||
let file = Self::create(path)?;
|
|
||||||
let resource = resource.to_string();
|
|
||||||
tokio::task::spawn_blocking(move || Self::lock_file_blocking(file, &resource)).await?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
fn create(path: impl AsRef<Path>) -> Result<fs_err::File, std::io::Error> {
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
|
|
||||||
// If path already exists, return it.
|
|
||||||
if let Ok(file) = fs_err::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.open(path.as_ref())
|
|
||||||
{
|
|
||||||
return Ok(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, create a temporary file with 777 permissions. We must set
|
|
||||||
// permissions _after_ creating the file, to override the `umask`.
|
|
||||||
let file = if let Some(parent) = path.as_ref().parent() {
|
|
||||||
NamedTempFile::new_in(parent)?
|
|
||||||
} else {
|
|
||||||
NamedTempFile::new()?
|
|
||||||
};
|
|
||||||
if let Err(err) = file
|
|
||||||
.as_file()
|
|
||||||
.set_permissions(std::fs::Permissions::from_mode(0o777))
|
|
||||||
{
|
|
||||||
warn!("Failed to set permissions on temporary file: {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to move the file to path, but if path exists now, just open path
|
|
||||||
match file.persist_noclobber(path.as_ref()) {
|
|
||||||
Ok(file) => Ok(fs_err::File::from_parts(file, path.as_ref())),
|
|
||||||
Err(err) => {
|
|
||||||
if err.error.kind() == std::io::ErrorKind::AlreadyExists {
|
|
||||||
fs_err::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.open(path.as_ref())
|
|
||||||
} else {
|
|
||||||
Err(err.error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
fn create(path: impl AsRef<Path>) -> std::io::Result<fs_err::File> {
|
|
||||||
fs_err::OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(path.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for LockedFile {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Err(err) = fs2::FileExt::unlock(self.0.file()) {
|
|
||||||
error!(
|
|
||||||
"Failed to unlock {}; program may be stuck: {}",
|
|
||||||
self.0.path().display(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
debug!("Released lock at `{}`", self.0.path().display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An asynchronous reader that reports progress as bytes are read.
|
/// An asynchronous reader that reports progress as bytes are read.
|
||||||
#[cfg(feature = "tokio")]
|
#[cfg(feature = "tokio")]
|
||||||
pub struct ProgressReader<Reader: tokio::io::AsyncRead + Unpin, Callback: Fn(usize) + Unpin> {
|
pub struct ProgressReader<Reader: tokio::io::AsyncRead + Unpin, Callback: Fn(usize) + Unpin> {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ uv-auth = { workspace = true }
|
||||||
uv-cache-key = { workspace = true }
|
uv-cache-key = { workspace = true }
|
||||||
uv-fs = { workspace = true, features = ["tokio"] }
|
uv-fs = { workspace = true, features = ["tokio"] }
|
||||||
uv-git-types = { workspace = true }
|
uv-git-types = { workspace = true }
|
||||||
|
uv-lock = { workspace = true, features = ["tokio"] }
|
||||||
uv-redacted = { workspace = true }
|
uv-redacted = { workspace = true }
|
||||||
uv-static = { workspace = true }
|
uv-static = { workspace = true }
|
||||||
uv-version = { workspace = true }
|
uv-version = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ use reqwest_middleware::ClientWithMiddleware;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use uv_cache_key::{RepositoryUrl, cache_digest};
|
use uv_cache_key::{RepositoryUrl, cache_digest};
|
||||||
use uv_fs::LockedFile;
|
|
||||||
use uv_git_types::{GitHubRepository, GitOid, GitReference, GitUrl};
|
use uv_git_types::{GitHubRepository, GitOid, GitReference, GitUrl};
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
use uv_version::version;
|
use uv_version::version;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
[package]
|
||||||
|
name = "uv-lock"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = { workspace = true }
|
||||||
|
rust-version = { workspace = true }
|
||||||
|
homepage = { workspace = true }
|
||||||
|
documentation = { workspace = true }
|
||||||
|
repository = { workspace = true }
|
||||||
|
authors = { workspace = true }
|
||||||
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
uv-fs = { workspace = true }
|
||||||
|
uv-state = { workspace = true }
|
||||||
|
uv-static = { workspace = true }
|
||||||
|
|
||||||
|
fs-err = { workspace = true }
|
||||||
|
fs2 = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
tokio = { workspace = true, optional = true}
|
||||||
|
tracing = { workspace = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
tokio = ["dep:tokio", "fs-err/tokio"]
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
use fs_err as fs;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use fs2::FileExt;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
use uv_fs::Simplified;
|
||||||
|
use uv_state::{StateBucket, StateStore};
|
||||||
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
|
/// Filesystem locks used to synchronize access to shared resources across processes.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FilesystemLocks {
|
||||||
|
/// The path to the top-level directory of the filesystem locks.
|
||||||
|
root: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilesystemLocks {
|
||||||
|
/// A directory for filesystem locks at `root`.
|
||||||
|
fn from_path(root: impl Into<PathBuf>) -> Self {
|
||||||
|
Self { root: root.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`FilesystemLocks`] from settings.
|
||||||
|
///
|
||||||
|
/// Prefer, in order:
|
||||||
|
///
|
||||||
|
/// 1. The specific tool directory specified by the user, i.e., `UV_LOCK_DIR`
|
||||||
|
/// 2. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/tools`
|
||||||
|
/// 3. A directory in the local data directory, e.g., `./.uv/tools`
|
||||||
|
pub fn from_settings() -> Result<Self, std::io::Error> {
|
||||||
|
if let Some(lock_dir) = std::env::var_os(EnvVars::UV_LOCK_DIR).filter(|s| !s.is_empty()) {
|
||||||
|
Ok(Self::from_path(std::path::absolute(lock_dir)?))
|
||||||
|
} else {
|
||||||
|
Ok(Self::from_path(
|
||||||
|
StateStore::from_settings(None)?.bucket(StateBucket::Locks),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a temporary directory.
|
||||||
|
pub fn temp() -> Result<Self, std::io::Error> {
|
||||||
|
Ok(Self::from_path(
|
||||||
|
StateStore::temp()?.bucket(StateBucket::Locks),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the directory.
|
||||||
|
pub fn init(self) -> Result<Self, std::io::Error> {
|
||||||
|
let root = &self.root;
|
||||||
|
|
||||||
|
// Create the tools directory, if it doesn't exist.
|
||||||
|
fs::create_dir_all(root)?;
|
||||||
|
|
||||||
|
// Add a .gitignore.
|
||||||
|
match fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(root.join(".gitignore"))
|
||||||
|
{
|
||||||
|
Ok(mut file) => file.write_all(b"*")?,
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => (),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the path of the directory.
|
||||||
|
pub fn root(&self) -> &Path {
|
||||||
|
&self.root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A file lock that is automatically released when dropped.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[must_use]
|
||||||
|
pub struct LockedFile(fs_err::File);
|
||||||
|
|
||||||
|
impl LockedFile {
|
||||||
|
/// Inner implementation for [`LockedFile::acquire_blocking`] and [`LockedFile::acquire`].
|
||||||
|
fn lock_file_blocking(file: fs_err::File, resource: &str) -> Result<Self, std::io::Error> {
|
||||||
|
trace!(
|
||||||
|
"Checking lock for `{resource}` at `{}`",
|
||||||
|
file.path().user_display()
|
||||||
|
);
|
||||||
|
match file.file().try_lock_exclusive() {
|
||||||
|
Ok(()) => {
|
||||||
|
debug!("Acquired lock for `{resource}`");
|
||||||
|
Ok(Self(file))
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Log error code and enum kind to help debugging more exotic failures.
|
||||||
|
if err.kind() != std::io::ErrorKind::WouldBlock {
|
||||||
|
debug!("Try lock error: {err:?}");
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
"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
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
debug!("Acquired lock for `{resource}`");
|
||||||
|
Ok(Self(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The same as [`LockedFile::acquire`], but for synchronous contexts. Do not use from an async
|
||||||
|
/// context, as this can block the runtime while waiting for another process to release the
|
||||||
|
/// lock.
|
||||||
|
pub fn acquire_blocking(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
resource: impl Display,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
let file = Self::create(path)?;
|
||||||
|
let resource = resource.to_string();
|
||||||
|
Self::lock_file_blocking(file, &resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acquire a cross-process lock for a resource using a file at the provided path.
|
||||||
|
#[cfg(feature = "tokio")]
|
||||||
|
pub async fn acquire(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
resource: impl Display,
|
||||||
|
) -> Result<Self, std::io::Error> {
|
||||||
|
let file = Self::create(path)?;
|
||||||
|
let resource = resource.to_string();
|
||||||
|
tokio::task::spawn_blocking(move || Self::lock_file_blocking(file, &resource)).await?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn create(path: impl AsRef<Path>) -> Result<fs_err::File, std::io::Error> {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
// If path already exists, return it.
|
||||||
|
if let Ok(file) = fs_err::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(path.as_ref())
|
||||||
|
{
|
||||||
|
return Ok(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, create a temporary file with 777 permissions. We must set
|
||||||
|
// permissions _after_ creating the file, to override the `umask`.
|
||||||
|
let file = if let Some(parent) = path.as_ref().parent() {
|
||||||
|
NamedTempFile::new_in(parent)?
|
||||||
|
} else {
|
||||||
|
NamedTempFile::new()?
|
||||||
|
};
|
||||||
|
if let Err(err) = file
|
||||||
|
.as_file()
|
||||||
|
.set_permissions(std::fs::Permissions::from_mode(0o777))
|
||||||
|
{
|
||||||
|
warn!("Failed to set permissions on temporary file: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to move the file to path, but if path exists now, just open path
|
||||||
|
match file.persist_noclobber(path.as_ref()) {
|
||||||
|
Ok(file) => Ok(fs_err::File::from_parts(file, path.as_ref())),
|
||||||
|
Err(err) => {
|
||||||
|
if err.error.kind() == std::io::ErrorKind::AlreadyExists {
|
||||||
|
fs_err::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(path.as_ref())
|
||||||
|
} else {
|
||||||
|
Err(err.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn create(path: impl AsRef<Path>) -> std::io::Result<fs_err::File> {
|
||||||
|
fs_err::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(path.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for LockedFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Err(err) = fs2::FileExt::unlock(self.0.file()) {
|
||||||
|
error!(
|
||||||
|
"Failed to unlock {}; program may be stuck: {}",
|
||||||
|
self.0.path().display(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debug!("Released lock at `{}`", self.0.path().display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ uv-distribution-filename = { workspace = true }
|
||||||
uv-extract = { workspace = true }
|
uv-extract = { workspace = true }
|
||||||
uv-fs = { workspace = true }
|
uv-fs = { workspace = true }
|
||||||
uv-install-wheel = { workspace = true }
|
uv-install-wheel = { workspace = true }
|
||||||
|
uv-lock = { workspace = true, features = ["tokio"] }
|
||||||
uv-pep440 = { workspace = true }
|
uv-pep440 = { workspace = true }
|
||||||
uv-pep508 = { workspace = true }
|
uv-pep508 = { workspace = true }
|
||||||
uv-platform-tags = { workspace = true }
|
uv-platform-tags = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ use tracing::debug;
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_configuration::PreviewMode;
|
use uv_configuration::PreviewMode;
|
||||||
use uv_fs::{LockedFile, Simplified};
|
use uv_fs::Simplified;
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_pep440::Version;
|
use uv_pep440::Version;
|
||||||
|
|
||||||
use crate::discovery::find_python_installation;
|
use crate::discovery::find_python_installation;
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ use tracing::{debug, trace, warn};
|
||||||
use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness};
|
use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness};
|
||||||
use uv_cache_info::Timestamp;
|
use uv_cache_info::Timestamp;
|
||||||
use uv_cache_key::cache_digest;
|
use uv_cache_key::cache_digest;
|
||||||
use uv_fs::{LockedFile, PythonExt, Simplified, write_atomic_sync};
|
use uv_fs::{PythonExt, Simplified, write_atomic_sync};
|
||||||
use uv_install_wheel::Layout;
|
use uv_install_wheel::Layout;
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_pep440::Version;
|
use uv_pep440::Version;
|
||||||
use uv_pep508::{MarkerEnvironment, StringVersion};
|
use uv_pep508::{MarkerEnvironment, StringVersion};
|
||||||
use uv_platform_tags::Platform;
|
use uv_platform_tags::Platform;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ use uv_configuration::PreviewMode;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
|
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
|
||||||
|
|
||||||
use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file};
|
use uv_fs::{Simplified, replace_symlink, symlink_or_copy_file};
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_state::{StateBucket, StateStore};
|
use uv_state::{StateBucket, StateStore};
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
use uv_trampoline_builder::{Launcher, windows_python_launcher};
|
use uv_trampoline_builder::{Launcher, windows_python_launcher};
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ pub enum StateBucket {
|
||||||
ManagedPython,
|
ManagedPython,
|
||||||
/// Installed tools.
|
/// Installed tools.
|
||||||
Tools,
|
Tools,
|
||||||
|
/// File-system locks.
|
||||||
|
Locks,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateBucket {
|
impl StateBucket {
|
||||||
|
|
@ -112,6 +114,7 @@ impl StateBucket {
|
||||||
match self {
|
match self {
|
||||||
Self::ManagedPython => "python",
|
Self::ManagedPython => "python",
|
||||||
Self::Tools => "tools",
|
Self::Tools => "tools",
|
||||||
|
Self::Locks => "locks",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,9 @@ impl EnvVars {
|
||||||
/// Specifies the directory where uv stores managed tools.
|
/// Specifies the directory where uv stores managed tools.
|
||||||
pub const UV_TOOL_DIR: &'static str = "UV_TOOL_DIR";
|
pub const UV_TOOL_DIR: &'static str = "UV_TOOL_DIR";
|
||||||
|
|
||||||
|
/// Specifies the directory where uv stores filesystem locks.
|
||||||
|
pub const UV_LOCK_DIR: &'static str = "UV_LOCK_DIR";
|
||||||
|
|
||||||
/// Specifies the "bin" directory for installing tool executables.
|
/// Specifies the "bin" directory for installing tool executables.
|
||||||
pub const UV_TOOL_BIN_DIR: &'static str = "UV_TOOL_BIN_DIR";
|
pub const UV_TOOL_BIN_DIR: &'static str = "UV_TOOL_BIN_DIR";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ uv-distribution-types = { workspace = true }
|
||||||
uv-fs = { workspace = true }
|
uv-fs = { workspace = true }
|
||||||
uv-install-wheel = { workspace = true }
|
uv-install-wheel = { workspace = true }
|
||||||
uv-installer = { workspace = true }
|
uv-installer = { workspace = true }
|
||||||
|
uv-lock = { workspace = true, features = ["tokio"] }
|
||||||
uv-pep440 = { workspace = true }
|
uv-pep440 = { workspace = true }
|
||||||
uv-pep508 = { workspace = true }
|
uv-pep508 = { workspace = true }
|
||||||
uv-pypi-types = { workspace = true }
|
uv-pypi-types = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,9 @@ use uv_install_wheel::read_record_file;
|
||||||
pub use receipt::ToolReceipt;
|
pub use receipt::ToolReceipt;
|
||||||
pub use tool::{Tool, ToolEntrypoint};
|
pub use tool::{Tool, ToolEntrypoint};
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_fs::{LockedFile, Simplified};
|
use uv_fs::Simplified;
|
||||||
use uv_installer::SitePackages;
|
use uv_installer::SitePackages;
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_python::{Interpreter, PythonEnvironment};
|
use uv_python::{Interpreter, PythonEnvironment};
|
||||||
use uv_state::{StateBucket, StateStore};
|
use uv_state::{StateBucket, StateStore};
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ uv-git = { workspace = true }
|
||||||
uv-git-types = { workspace = true }
|
uv-git-types = { workspace = true }
|
||||||
uv-install-wheel = { workspace = true, default-features = false }
|
uv-install-wheel = { workspace = true, default-features = false }
|
||||||
uv-installer = { workspace = true }
|
uv-installer = { workspace = true }
|
||||||
|
uv-lock = { workspace = true, features = ["tokio"] }
|
||||||
uv-normalize = { workspace = true }
|
uv-normalize = { workspace = true }
|
||||||
uv-pep440 = { workspace = true }
|
uv-pep440 = { workspace = true }
|
||||||
uv-pep508 = { workspace = true }
|
uv-pep508 = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,10 @@ use uv_distribution_types::{
|
||||||
Index, IndexName, IndexUrl, IndexUrls, NameRequirementSpecification, Requirement,
|
Index, IndexName, IndexUrl, IndexUrls, NameRequirementSpecification, Requirement,
|
||||||
RequirementSource, UnresolvedRequirement, VersionId,
|
RequirementSource, UnresolvedRequirement, VersionId,
|
||||||
};
|
};
|
||||||
use uv_fs::{LockedFile, Simplified};
|
use uv_fs::Simplified;
|
||||||
use uv_git::GIT_STORE;
|
use uv_git::GIT_STORE;
|
||||||
use uv_git_types::GitReference;
|
use uv_git_types::GitReference;
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups, PackageName};
|
use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups, PackageName};
|
||||||
use uv_pep508::{ExtraName, MarkerTree, UnnamedRequirement, VersionOrUrl};
|
use uv_pep508::{ExtraName, MarkerTree, UnnamedRequirement, VersionOrUrl};
|
||||||
use uv_pypi_types::{ParsedUrl, VerbatimParsedUrl};
|
use uv_pypi_types::{ParsedUrl, VerbatimParsedUrl};
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@ use uv_distribution_types::{
|
||||||
Index, Requirement, RequiresPython, Resolution, UnresolvedRequirement,
|
Index, Requirement, RequiresPython, Resolution, UnresolvedRequirement,
|
||||||
UnresolvedRequirementSpecification,
|
UnresolvedRequirementSpecification,
|
||||||
};
|
};
|
||||||
use uv_fs::{CWD, LockedFile, Simplified};
|
use uv_fs::{CWD, Simplified};
|
||||||
use uv_git::ResolvedRepositoryReference;
|
use uv_git::ResolvedRepositoryReference;
|
||||||
use uv_installer::{SatisfiesResult, SitePackages};
|
use uv_installer::{SatisfiesResult, SitePackages};
|
||||||
|
use uv_lock::LockedFile;
|
||||||
use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName};
|
use uv_normalize::{DEV_DEPENDENCIES, DefaultGroups, ExtraName, GroupName, PackageName};
|
||||||
use uv_pep440::{TildeVersionSpecifier, Version, VersionSpecifiers};
|
use uv_pep440::{TildeVersionSpecifier, Version, VersionSpecifiers};
|
||||||
use uv_pep508::MarkerTreeContents;
|
use uv_pep508::MarkerTreeContents;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue