mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 05:03:46 +00:00
Cache WHEEL and METADATA reads in installed distributions (#15489)
## Summary Uses interior mutability to cache the reads. This follows the pattern we use for reading the platform tags in `Interpreter::tags`.
This commit is contained in:
parent
be4d5b72aa
commit
ef9a332364
10 changed files with 240 additions and 161 deletions
|
|
@ -2,6 +2,7 @@ use std::borrow::Cow;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -68,9 +69,42 @@ pub enum InstalledDistError {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InstalledDist {
|
||||||
|
pub kind: InstalledDistKind,
|
||||||
|
// Cache data that must be read from the `.dist-info` directory. These are safe to cache as
|
||||||
|
// the `InstalledDist` is immutable after creation.
|
||||||
|
metadata_cache: OnceLock<uv_pypi_types::ResolutionMetadata>,
|
||||||
|
tags_cache: OnceLock<Option<ExpandedTags>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InstalledDistKind> for InstalledDist {
|
||||||
|
fn from(kind: InstalledDistKind) -> Self {
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
metadata_cache: OnceLock::new(),
|
||||||
|
tags_cache: OnceLock::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for InstalledDist {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.kind.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for InstalledDist {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.kind == other.kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for InstalledDist {}
|
||||||
|
|
||||||
/// A built distribution (wheel) that is installed in a virtual environment.
|
/// A built distribution (wheel) that is installed in a virtual environment.
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||||
pub enum InstalledDist {
|
pub enum InstalledDistKind {
|
||||||
/// The distribution was derived from a registry, like `PyPI`.
|
/// The distribution was derived from a registry, like `PyPI`.
|
||||||
Registry(InstalledRegistryDist),
|
Registry(InstalledRegistryDist),
|
||||||
/// The distribution was derived from an arbitrary URL.
|
/// The distribution was derived from an arbitrary URL.
|
||||||
|
|
@ -152,35 +186,41 @@ impl InstalledDist {
|
||||||
|
|
||||||
return if let Some(direct_url) = Self::read_direct_url(path)? {
|
return if let Some(direct_url) = Self::read_direct_url(path)? {
|
||||||
match Url::try_from(&direct_url) {
|
match Url::try_from(&direct_url) {
|
||||||
Ok(url) => Ok(Some(Self::Url(InstalledDirectUrlDist {
|
Ok(url) => Ok(Some(Self::from(InstalledDistKind::Url(
|
||||||
name,
|
InstalledDirectUrlDist {
|
||||||
version,
|
|
||||||
editable: matches!(&direct_url, DirectUrl::LocalDirectory { dir_info, .. } if dir_info.editable == Some(true)),
|
|
||||||
direct_url: Box::new(direct_url),
|
|
||||||
url: DisplaySafeUrl::from(url),
|
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
|
||||||
cache_info,
|
|
||||||
build_info,
|
|
||||||
}))),
|
|
||||||
Err(err) => {
|
|
||||||
warn!("Failed to parse direct URL: {err}");
|
|
||||||
Ok(Some(Self::Registry(InstalledRegistryDist {
|
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
|
editable: matches!(&direct_url, DirectUrl::LocalDirectory { dir_info, .. } if dir_info.editable == Some(true)),
|
||||||
|
direct_url: Box::new(direct_url),
|
||||||
|
url: DisplaySafeUrl::from(url),
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
cache_info,
|
cache_info,
|
||||||
build_info,
|
build_info,
|
||||||
})))
|
},
|
||||||
|
)))),
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Failed to parse direct URL: {err}");
|
||||||
|
Ok(Some(Self::from(InstalledDistKind::Registry(
|
||||||
|
InstalledRegistryDist {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
|
cache_info,
|
||||||
|
build_info,
|
||||||
|
},
|
||||||
|
))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(Some(Self::Registry(InstalledRegistryDist {
|
Ok(Some(Self::from(InstalledDistKind::Registry(
|
||||||
name,
|
InstalledRegistryDist {
|
||||||
version,
|
name,
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
version,
|
||||||
cache_info,
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
build_info,
|
cache_info,
|
||||||
})))
|
build_info,
|
||||||
|
},
|
||||||
|
))))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,19 +244,23 @@ impl InstalledDist {
|
||||||
|
|
||||||
if let Some(version) = file_name.version {
|
if let Some(version) = file_name.version {
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
return Ok(Some(Self::EggInfoDirectory(InstalledEggInfoDirectory {
|
return Ok(Some(Self::from(InstalledDistKind::EggInfoDirectory(
|
||||||
name: file_name.name,
|
InstalledEggInfoDirectory {
|
||||||
version,
|
name: file_name.name,
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
version,
|
||||||
})));
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
|
},
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.is_file() {
|
if metadata.is_file() {
|
||||||
return Ok(Some(Self::EggInfoFile(InstalledEggInfoFile {
|
return Ok(Some(Self::from(InstalledDistKind::EggInfoFile(
|
||||||
name: file_name.name,
|
InstalledEggInfoFile {
|
||||||
version,
|
name: file_name.name,
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
version,
|
||||||
})));
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
|
},
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,22 +268,26 @@ impl InstalledDist {
|
||||||
let Some(egg_metadata) = read_metadata(&path.join("PKG-INFO")) else {
|
let Some(egg_metadata) = read_metadata(&path.join("PKG-INFO")) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
return Ok(Some(Self::EggInfoDirectory(InstalledEggInfoDirectory {
|
return Ok(Some(Self::from(InstalledDistKind::EggInfoDirectory(
|
||||||
name: file_name.name,
|
InstalledEggInfoDirectory {
|
||||||
version: Version::from_str(&egg_metadata.version)?,
|
name: file_name.name,
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
version: Version::from_str(&egg_metadata.version)?,
|
||||||
})));
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
|
},
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.is_file() {
|
if metadata.is_file() {
|
||||||
let Some(egg_metadata) = read_metadata(path) else {
|
let Some(egg_metadata) = read_metadata(path) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
return Ok(Some(Self::EggInfoDirectory(InstalledEggInfoDirectory {
|
return Ok(Some(Self::from(InstalledDistKind::EggInfoDirectory(
|
||||||
name: file_name.name,
|
InstalledEggInfoDirectory {
|
||||||
version: Version::from_str(&egg_metadata.version)?,
|
name: file_name.name,
|
||||||
path: path.to_path_buf().into_boxed_path(),
|
version: Version::from_str(&egg_metadata.version)?,
|
||||||
})));
|
path: path.to_path_buf().into_boxed_path(),
|
||||||
|
},
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,14 +331,16 @@ impl InstalledDist {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(Some(Self::LegacyEditable(InstalledLegacyEditable {
|
return Ok(Some(Self::from(InstalledDistKind::LegacyEditable(
|
||||||
name: egg_metadata.name,
|
InstalledLegacyEditable {
|
||||||
version: Version::from_str(&egg_metadata.version)?,
|
name: egg_metadata.name,
|
||||||
egg_link: path.to_path_buf().into_boxed_path(),
|
version: Version::from_str(&egg_metadata.version)?,
|
||||||
target: target.into_boxed_path(),
|
egg_link: path.to_path_buf().into_boxed_path(),
|
||||||
target_url: DisplaySafeUrl::from(url),
|
target: target.into_boxed_path(),
|
||||||
egg_info: egg_info.into_boxed_path(),
|
target_url: DisplaySafeUrl::from(url),
|
||||||
})));
|
egg_info: egg_info.into_boxed_path(),
|
||||||
|
},
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
@ -298,45 +348,45 @@ impl InstalledDist {
|
||||||
|
|
||||||
/// Return the [`Path`] at which the distribution is stored on-disk.
|
/// Return the [`Path`] at which the distribution is stored on-disk.
|
||||||
pub fn install_path(&self) -> &Path {
|
pub fn install_path(&self) -> &Path {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(dist) => &dist.path,
|
InstalledDistKind::Registry(dist) => &dist.path,
|
||||||
Self::Url(dist) => &dist.path,
|
InstalledDistKind::Url(dist) => &dist.path,
|
||||||
Self::EggInfoDirectory(dist) => &dist.path,
|
InstalledDistKind::EggInfoDirectory(dist) => &dist.path,
|
||||||
Self::EggInfoFile(dist) => &dist.path,
|
InstalledDistKind::EggInfoFile(dist) => &dist.path,
|
||||||
Self::LegacyEditable(dist) => &dist.egg_info,
|
InstalledDistKind::LegacyEditable(dist) => &dist.egg_info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`Version`] of the distribution.
|
/// Return the [`Version`] of the distribution.
|
||||||
pub fn version(&self) -> &Version {
|
pub fn version(&self) -> &Version {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(dist) => &dist.version,
|
InstalledDistKind::Registry(dist) => &dist.version,
|
||||||
Self::Url(dist) => &dist.version,
|
InstalledDistKind::Url(dist) => &dist.version,
|
||||||
Self::EggInfoDirectory(dist) => &dist.version,
|
InstalledDistKind::EggInfoDirectory(dist) => &dist.version,
|
||||||
Self::EggInfoFile(dist) => &dist.version,
|
InstalledDistKind::EggInfoFile(dist) => &dist.version,
|
||||||
Self::LegacyEditable(dist) => &dist.version,
|
InstalledDistKind::LegacyEditable(dist) => &dist.version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`CacheInfo`] of the distribution, if any.
|
/// Return the [`CacheInfo`] of the distribution, if any.
|
||||||
pub fn cache_info(&self) -> Option<&CacheInfo> {
|
pub fn cache_info(&self) -> Option<&CacheInfo> {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(dist) => dist.cache_info.as_ref(),
|
InstalledDistKind::Registry(dist) => dist.cache_info.as_ref(),
|
||||||
Self::Url(dist) => dist.cache_info.as_ref(),
|
InstalledDistKind::Url(dist) => dist.cache_info.as_ref(),
|
||||||
Self::EggInfoDirectory(..) => None,
|
InstalledDistKind::EggInfoDirectory(..) => None,
|
||||||
Self::EggInfoFile(..) => None,
|
InstalledDistKind::EggInfoFile(..) => None,
|
||||||
Self::LegacyEditable(..) => None,
|
InstalledDistKind::LegacyEditable(..) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`BuildInfo`] of the distribution, if any.
|
/// Return the [`BuildInfo`] of the distribution, if any.
|
||||||
pub fn build_info(&self) -> Option<&BuildInfo> {
|
pub fn build_info(&self) -> Option<&BuildInfo> {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(dist) => dist.build_info.as_ref(),
|
InstalledDistKind::Registry(dist) => dist.build_info.as_ref(),
|
||||||
Self::Url(dist) => dist.build_info.as_ref(),
|
InstalledDistKind::Url(dist) => dist.build_info.as_ref(),
|
||||||
Self::EggInfoDirectory(..) => None,
|
InstalledDistKind::EggInfoDirectory(..) => None,
|
||||||
Self::EggInfoFile(..) => None,
|
InstalledDistKind::EggInfoFile(..) => None,
|
||||||
Self::LegacyEditable(..) => None,
|
InstalledDistKind::LegacyEditable(..) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -380,9 +430,13 @@ impl InstalledDist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the `METADATA` file from a `.dist-info` directory.
|
/// Read the `METADATA` file from a `.dist-info` directory.
|
||||||
pub fn read_metadata(&self) -> Result<uv_pypi_types::ResolutionMetadata, InstalledDistError> {
|
pub fn read_metadata(&self) -> Result<&uv_pypi_types::ResolutionMetadata, InstalledDistError> {
|
||||||
match self {
|
if let Some(metadata) = self.metadata_cache.get() {
|
||||||
Self::Registry(_) | Self::Url(_) => {
|
return Ok(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
let metadata = match &self.kind {
|
||||||
|
InstalledDistKind::Registry(_) | InstalledDistKind::Url(_) => {
|
||||||
let path = self.install_path().join("METADATA");
|
let path = self.install_path().join("METADATA");
|
||||||
let contents = fs::read(&path)?;
|
let contents = fs::read(&path)?;
|
||||||
// TODO(zanieb): Update this to use thiserror so we can unpack parse errors downstream
|
// TODO(zanieb): Update this to use thiserror so we can unpack parse errors downstream
|
||||||
|
|
@ -391,13 +445,19 @@ impl InstalledDist {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
err: Box::new(err),
|
err: Box::new(err),
|
||||||
}
|
}
|
||||||
})
|
})?
|
||||||
}
|
}
|
||||||
Self::EggInfoFile(_) | Self::EggInfoDirectory(_) | Self::LegacyEditable(_) => {
|
InstalledDistKind::EggInfoFile(_)
|
||||||
let path = match self {
|
| InstalledDistKind::EggInfoDirectory(_)
|
||||||
Self::EggInfoFile(dist) => Cow::Borrowed(&*dist.path),
|
| InstalledDistKind::LegacyEditable(_) => {
|
||||||
Self::EggInfoDirectory(dist) => Cow::Owned(dist.path.join("PKG-INFO")),
|
let path = match &self.kind {
|
||||||
Self::LegacyEditable(dist) => Cow::Owned(dist.egg_info.join("PKG-INFO")),
|
InstalledDistKind::EggInfoFile(dist) => Cow::Borrowed(&*dist.path),
|
||||||
|
InstalledDistKind::EggInfoDirectory(dist) => {
|
||||||
|
Cow::Owned(dist.path.join("PKG-INFO"))
|
||||||
|
}
|
||||||
|
InstalledDistKind::LegacyEditable(dist) => {
|
||||||
|
Cow::Owned(dist.egg_info.join("PKG-INFO"))
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let contents = fs::read(path.as_ref())?;
|
let contents = fs::read(path.as_ref())?;
|
||||||
|
|
@ -406,9 +466,12 @@ impl InstalledDist {
|
||||||
path: path.to_path_buf(),
|
path: path.to_path_buf(),
|
||||||
err: Box::new(err),
|
err: Box::new(err),
|
||||||
}
|
}
|
||||||
})
|
})?
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let _ = self.metadata_cache.set(metadata);
|
||||||
|
Ok(self.metadata_cache.get().expect("metadata should be set"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the `INSTALLER` of the distribution.
|
/// Return the `INSTALLER` of the distribution.
|
||||||
|
|
@ -422,56 +485,64 @@ impl InstalledDist {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the supported wheel tags for the distribution from the `WHEEL` file, if available.
|
/// Return the supported wheel tags for the distribution from the `WHEEL` file, if available.
|
||||||
pub fn read_tags(&self) -> Result<Option<ExpandedTags>, InstalledDistError> {
|
pub fn read_tags(&self) -> Result<Option<&ExpandedTags>, InstalledDistError> {
|
||||||
// TODO(charlie): Cache this result.
|
if let Some(tags) = self.tags_cache.get() {
|
||||||
let path = match self {
|
return Ok(tags.as_ref());
|
||||||
Self::Registry(InstalledRegistryDist { path, .. }) => path,
|
}
|
||||||
Self::Url(InstalledDirectUrlDist { path, .. }) => path,
|
|
||||||
Self::EggInfoFile(_) => return Ok(None),
|
let path = match &self.kind {
|
||||||
Self::EggInfoDirectory(_) => return Ok(None),
|
InstalledDistKind::Registry(dist) => &dist.path,
|
||||||
Self::LegacyEditable(_) => return Ok(None),
|
InstalledDistKind::Url(dist) => &dist.path,
|
||||||
|
InstalledDistKind::EggInfoFile(_) => return Ok(None),
|
||||||
|
InstalledDistKind::EggInfoDirectory(_) => return Ok(None),
|
||||||
|
InstalledDistKind::LegacyEditable(_) => return Ok(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read the `WHEEL` file.
|
// Read the `WHEEL` file.
|
||||||
let contents = fs_err::read_to_string(path.join("WHEEL"))?;
|
let contents = fs_err::read_to_string(path.join("WHEEL"))?;
|
||||||
let wheel_file = WheelFile::parse(&contents)?;
|
let wheel_file = WheelFile::parse(&contents)?;
|
||||||
let Some(tags) = wheel_file.tags() else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse the tags.
|
// Parse the tags.
|
||||||
let tags = ExpandedTags::parse(tags.iter().map(String::as_str))?;
|
let tags = if let Some(tags) = wheel_file.tags() {
|
||||||
|
Some(ExpandedTags::parse(tags.iter().map(String::as_str))?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Some(tags))
|
let _ = self.tags_cache.set(tags);
|
||||||
|
Ok(self.tags_cache.get().expect("tags should be set").as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the distribution is editable.
|
/// Return true if the distribution is editable.
|
||||||
pub fn is_editable(&self) -> bool {
|
pub fn is_editable(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
&self.kind,
|
||||||
Self::LegacyEditable(_) | Self::Url(InstalledDirectUrlDist { editable: true, .. })
|
InstalledDistKind::LegacyEditable(_)
|
||||||
|
| InstalledDistKind::Url(InstalledDirectUrlDist { editable: true, .. })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the [`Url`] of the distribution, if it is editable.
|
/// Return the [`Url`] of the distribution, if it is editable.
|
||||||
pub fn as_editable(&self) -> Option<&Url> {
|
pub fn as_editable(&self) -> Option<&Url> {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(_) => None,
|
InstalledDistKind::Registry(_) => None,
|
||||||
Self::Url(dist) => dist.editable.then_some(&dist.url),
|
InstalledDistKind::Url(dist) => dist.editable.then_some(&dist.url),
|
||||||
Self::EggInfoFile(_) => None,
|
InstalledDistKind::EggInfoFile(_) => None,
|
||||||
Self::EggInfoDirectory(_) => None,
|
InstalledDistKind::EggInfoDirectory(_) => None,
|
||||||
Self::LegacyEditable(dist) => Some(&dist.target_url),
|
InstalledDistKind::LegacyEditable(dist) => Some(&dist.target_url),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the distribution refers to a local file or directory.
|
/// Return true if the distribution refers to a local file or directory.
|
||||||
pub fn is_local(&self) -> bool {
|
pub fn is_local(&self) -> bool {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(_) => false,
|
InstalledDistKind::Registry(_) => false,
|
||||||
Self::Url(dist) => matches!(&*dist.direct_url, DirectUrl::LocalDirectory { .. }),
|
InstalledDistKind::Url(dist) => {
|
||||||
Self::EggInfoFile(_) => false,
|
matches!(&*dist.direct_url, DirectUrl::LocalDirectory { .. })
|
||||||
Self::EggInfoDirectory(_) => false,
|
}
|
||||||
Self::LegacyEditable(_) => true,
|
InstalledDistKind::EggInfoFile(_) => false,
|
||||||
|
InstalledDistKind::EggInfoDirectory(_) => false,
|
||||||
|
InstalledDistKind::LegacyEditable(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -514,12 +585,12 @@ impl Name for InstalledLegacyEditable {
|
||||||
|
|
||||||
impl Name for InstalledDist {
|
impl Name for InstalledDist {
|
||||||
fn name(&self) -> &PackageName {
|
fn name(&self) -> &PackageName {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(dist) => dist.name(),
|
InstalledDistKind::Registry(dist) => dist.name(),
|
||||||
Self::Url(dist) => dist.name(),
|
InstalledDistKind::Url(dist) => dist.name(),
|
||||||
Self::EggInfoDirectory(dist) => dist.name(),
|
InstalledDistKind::EggInfoDirectory(dist) => dist.name(),
|
||||||
Self::EggInfoFile(dist) => dist.name(),
|
InstalledDistKind::EggInfoFile(dist) => dist.name(),
|
||||||
Self::LegacyEditable(dist) => dist.name(),
|
InstalledDistKind::LegacyEditable(dist) => dist.name(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -556,12 +627,12 @@ impl InstalledMetadata for InstalledLegacyEditable {
|
||||||
|
|
||||||
impl InstalledMetadata for InstalledDist {
|
impl InstalledMetadata for InstalledDist {
|
||||||
fn installed_version(&self) -> InstalledVersion<'_> {
|
fn installed_version(&self) -> InstalledVersion<'_> {
|
||||||
match self {
|
match &self.kind {
|
||||||
Self::Registry(dist) => dist.installed_version(),
|
InstalledDistKind::Registry(dist) => dist.installed_version(),
|
||||||
Self::Url(dist) => dist.installed_version(),
|
InstalledDistKind::Url(dist) => dist.installed_version(),
|
||||||
Self::EggInfoFile(dist) => dist.installed_version(),
|
InstalledDistKind::EggInfoFile(dist) => dist.installed_version(),
|
||||||
Self::EggInfoDirectory(dist) => dist.installed_version(),
|
InstalledDistKind::EggInfoDirectory(dist) => dist.installed_version(),
|
||||||
Self::LegacyEditable(dist) => dist.installed_version(),
|
InstalledDistKind::LegacyEditable(dist) => dist.installed_version(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
|
||||||
.read_metadata()
|
.read_metadata()
|
||||||
.map_err(|err| Error::ReadInstalled(Box::new(dist.clone()), err))?;
|
.map_err(|err| Error::ReadInstalled(Box::new(dist.clone()), err))?;
|
||||||
|
|
||||||
Ok(ArchiveMetadata::from_metadata23(metadata))
|
Ok(ArchiveMetadata::from_metadata23(metadata.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Either fetch the only wheel metadata (directly from the index or with range requests) or
|
/// Either fetch the only wheel metadata (directly from the index or with range requests) or
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ use uv_cache_info::CacheInfo;
|
||||||
use uv_cache_key::{CanonicalUrl, RepositoryUrl};
|
use uv_cache_key::{CanonicalUrl, RepositoryUrl};
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
BuildInfo, BuildVariables, ConfigSettings, ExtraBuildRequirement, ExtraBuildRequires,
|
BuildInfo, BuildVariables, ConfigSettings, ExtraBuildRequirement, ExtraBuildRequires,
|
||||||
ExtraBuildVariables, InstalledDirectUrlDist, InstalledDist, PackageConfigSettings,
|
ExtraBuildVariables, InstalledDirectUrlDist, InstalledDist, InstalledDistKind,
|
||||||
RequirementSource,
|
PackageConfigSettings, RequirementSource,
|
||||||
};
|
};
|
||||||
use uv_git_types::GitOid;
|
use uv_git_types::GitOid;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
@ -78,12 +78,12 @@ impl RequirementSatisfaction {
|
||||||
ext: _,
|
ext: _,
|
||||||
url: _,
|
url: _,
|
||||||
} => {
|
} => {
|
||||||
let InstalledDist::Url(InstalledDirectUrlDist {
|
let InstalledDistKind::Url(InstalledDirectUrlDist {
|
||||||
direct_url,
|
direct_url,
|
||||||
editable,
|
editable,
|
||||||
cache_info,
|
cache_info,
|
||||||
..
|
..
|
||||||
}) = &distribution
|
}) = &distribution.kind
|
||||||
else {
|
else {
|
||||||
return Self::Mismatch;
|
return Self::Mismatch;
|
||||||
};
|
};
|
||||||
|
|
@ -137,7 +137,8 @@ impl RequirementSatisfaction {
|
||||||
git: requested_git,
|
git: requested_git,
|
||||||
subdirectory: requested_subdirectory,
|
subdirectory: requested_subdirectory,
|
||||||
} => {
|
} => {
|
||||||
let InstalledDist::Url(InstalledDirectUrlDist { direct_url, .. }) = &distribution
|
let InstalledDistKind::Url(InstalledDirectUrlDist { direct_url, .. }) =
|
||||||
|
&distribution.kind
|
||||||
else {
|
else {
|
||||||
return Self::Mismatch;
|
return Self::Mismatch;
|
||||||
};
|
};
|
||||||
|
|
@ -192,11 +193,11 @@ impl RequirementSatisfaction {
|
||||||
ext: _,
|
ext: _,
|
||||||
url: _,
|
url: _,
|
||||||
} => {
|
} => {
|
||||||
let InstalledDist::Url(InstalledDirectUrlDist {
|
let InstalledDistKind::Url(InstalledDirectUrlDist {
|
||||||
direct_url,
|
direct_url,
|
||||||
cache_info,
|
cache_info,
|
||||||
..
|
..
|
||||||
}) = &distribution
|
}) = &distribution.kind
|
||||||
else {
|
else {
|
||||||
return Self::Mismatch;
|
return Self::Mismatch;
|
||||||
};
|
};
|
||||||
|
|
@ -247,11 +248,11 @@ impl RequirementSatisfaction {
|
||||||
r#virtual: _,
|
r#virtual: _,
|
||||||
url: _,
|
url: _,
|
||||||
} => {
|
} => {
|
||||||
let InstalledDist::Url(InstalledDirectUrlDist {
|
let InstalledDistKind::Url(InstalledDirectUrlDist {
|
||||||
direct_url,
|
direct_url,
|
||||||
cache_info,
|
cache_info,
|
||||||
..
|
..
|
||||||
}) = &distribution
|
}) = &distribution.kind
|
||||||
else {
|
else {
|
||||||
return Self::Mismatch;
|
return Self::Mismatch;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ use fs_err as fs;
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use uv_distribution_types::{
|
use uv_distribution_types::{
|
||||||
ConfigSettings, Diagnostic, ExtraBuildRequires, ExtraBuildVariables, InstalledDist, Name,
|
ConfigSettings, Diagnostic, ExtraBuildRequires, ExtraBuildVariables, InstalledDist,
|
||||||
NameRequirementSpecification, PackageConfigSettings, Requirement, UnresolvedRequirement,
|
InstalledDistKind, Name, NameRequirementSpecification, PackageConfigSettings, Requirement,
|
||||||
UnresolvedRequirementSpecification,
|
UnresolvedRequirement, UnresolvedRequirementSpecification,
|
||||||
};
|
};
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
@ -126,7 +126,7 @@ impl SitePackages {
|
||||||
.push(idx);
|
.push(idx);
|
||||||
|
|
||||||
// Index the distribution by URL.
|
// Index the distribution by URL.
|
||||||
if let InstalledDist::Url(dist) = &dist_info {
|
if let InstalledDistKind::Url(dist) = &dist_info.kind {
|
||||||
by_url.entry(dist.url.clone()).or_default().push(idx);
|
by_url.entry(dist.url.clone()).or_default().push(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -528,8 +528,8 @@ impl SitePackages {
|
||||||
.with_context(|| format!("Failed to read metadata for: {distribution}"))?;
|
.with_context(|| format!("Failed to read metadata for: {distribution}"))?;
|
||||||
|
|
||||||
// Add the dependencies to the queue.
|
// Add the dependencies to the queue.
|
||||||
for dependency in metadata.requires_dist {
|
for dependency in &metadata.requires_dist {
|
||||||
let dependency = Requirement::from(dependency);
|
let dependency = Requirement::from(dependency.clone());
|
||||||
if let Some(r#overrides) = overrides.get(&dependency.name) {
|
if let Some(r#overrides) = overrides.get(&dependency.name) {
|
||||||
for dependency in r#overrides {
|
for dependency in r#overrides {
|
||||||
if dependency.evaluate_markers(Some(markers), &requirement.extras) {
|
if dependency.evaluate_markers(Some(markers), &requirement.extras) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use uv_distribution_types::{InstalledDist, InstalledEggInfoFile};
|
use uv_distribution_types::{InstalledDist, InstalledDistKind, InstalledEggInfoFile};
|
||||||
|
|
||||||
/// Uninstall a package from the specified Python environment.
|
/// Uninstall a package from the specified Python environment.
|
||||||
pub async fn uninstall(
|
pub async fn uninstall(
|
||||||
|
|
@ -6,17 +6,17 @@ pub async fn uninstall(
|
||||||
) -> Result<uv_install_wheel::Uninstall, UninstallError> {
|
) -> Result<uv_install_wheel::Uninstall, UninstallError> {
|
||||||
let uninstall = tokio::task::spawn_blocking({
|
let uninstall = tokio::task::spawn_blocking({
|
||||||
let dist = dist.clone();
|
let dist = dist.clone();
|
||||||
move || match dist {
|
move || match dist.kind {
|
||||||
InstalledDist::Registry(_) | InstalledDist::Url(_) => {
|
InstalledDistKind::Registry(_) | InstalledDistKind::Url(_) => {
|
||||||
Ok(uv_install_wheel::uninstall_wheel(dist.install_path())?)
|
Ok(uv_install_wheel::uninstall_wheel(dist.install_path())?)
|
||||||
}
|
}
|
||||||
InstalledDist::EggInfoDirectory(_) => {
|
InstalledDistKind::EggInfoDirectory(_) => {
|
||||||
Ok(uv_install_wheel::uninstall_egg(dist.install_path())?)
|
Ok(uv_install_wheel::uninstall_egg(dist.install_path())?)
|
||||||
}
|
}
|
||||||
InstalledDist::LegacyEditable(dist) => {
|
InstalledDistKind::LegacyEditable(dist) => {
|
||||||
Ok(uv_install_wheel::uninstall_legacy_editable(&dist.egg_link)?)
|
Ok(uv_install_wheel::uninstall_legacy_editable(&dist.egg_link)?)
|
||||||
}
|
}
|
||||||
InstalledDist::EggInfoFile(dist) => Err(UninstallError::Distutils(dist)),
|
InstalledDistKind::EggInfoFile(dist) => Err(UninstallError::Distutils(dist)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::str::FromStr;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use uv_distribution_types::{IndexUrl, InstalledDist};
|
use uv_distribution_types::{IndexUrl, InstalledDist, InstalledDistKind};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pep440::{Operator, Version};
|
use uv_pep440::{Operator, Version};
|
||||||
use uv_pep508::{MarkerTree, VerbatimUrl, VersionOrUrl};
|
use uv_pep508::{MarkerTree, VerbatimUrl, VersionOrUrl};
|
||||||
|
|
@ -122,7 +122,7 @@ impl Preference {
|
||||||
|
|
||||||
/// Create a [`Preference`] from an installed distribution.
|
/// Create a [`Preference`] from an installed distribution.
|
||||||
pub fn from_installed(dist: &InstalledDist) -> Option<Self> {
|
pub fn from_installed(dist: &InstalledDist) -> Option<Self> {
|
||||||
let InstalledDist::Registry(dist) = dist else {
|
let InstalledDistKind::Registry(dist) = &dist.kind else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some(Self {
|
Some(Self {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use itertools::Itertools;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_distribution_types::{Diagnostic, InstalledDist, Name};
|
use uv_distribution_types::{Diagnostic, InstalledDistKind, Name};
|
||||||
use uv_installer::SitePackages;
|
use uv_installer::SitePackages;
|
||||||
use uv_preview::Preview;
|
use uv_preview::Preview;
|
||||||
use uv_python::PythonPreference;
|
use uv_python::PythonPreference;
|
||||||
|
|
@ -61,24 +61,24 @@ pub(crate) fn pip_freeze(
|
||||||
.flat_map(uv_installer::SitePackages::iter)
|
.flat_map(uv_installer::SitePackages::iter)
|
||||||
.filter(|dist| !(exclude_editable && dist.is_editable()))
|
.filter(|dist| !(exclude_editable && dist.is_editable()))
|
||||||
.sorted_unstable_by(|a, b| a.name().cmp(b.name()).then(a.version().cmp(b.version())))
|
.sorted_unstable_by(|a, b| a.name().cmp(b.name()).then(a.version().cmp(b.version())))
|
||||||
.map(|dist| match dist {
|
.map(|dist| match &dist.kind {
|
||||||
InstalledDist::Registry(dist) => {
|
InstalledDistKind::Registry(dist) => {
|
||||||
format!("{}=={}", dist.name().bold(), dist.version)
|
format!("{}=={}", dist.name().bold(), dist.version)
|
||||||
}
|
}
|
||||||
InstalledDist::Url(dist) => {
|
InstalledDistKind::Url(dist) => {
|
||||||
if dist.editable {
|
if dist.editable {
|
||||||
format!("-e {}", dist.url)
|
format!("-e {}", dist.url)
|
||||||
} else {
|
} else {
|
||||||
format!("{} @ {}", dist.name().bold(), dist.url)
|
format!("{} @ {}", dist.name().bold(), dist.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InstalledDist::EggInfoFile(dist) => {
|
InstalledDistKind::EggInfoFile(dist) => {
|
||||||
format!("{}=={}", dist.name().bold(), dist.version)
|
format!("{}=={}", dist.name().bold(), dist.version)
|
||||||
}
|
}
|
||||||
InstalledDist::EggInfoDirectory(dist) => {
|
InstalledDistKind::EggInfoDirectory(dist) => {
|
||||||
format!("{}=={}", dist.name().bold(), dist.version)
|
format!("{}=={}", dist.name().bold(), dist.version)
|
||||||
}
|
}
|
||||||
InstalledDist::LegacyEditable(dist) => {
|
InstalledDistKind::LegacyEditable(dist) => {
|
||||||
format!("-e {}", dist.target.display())
|
format!("-e {}", dist.target.display())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -389,6 +389,9 @@ pub(crate) struct Changelog {
|
||||||
impl Changelog {
|
impl Changelog {
|
||||||
/// Create a [`Changelog`] from a list of installed and uninstalled distributions.
|
/// Create a [`Changelog`] from a list of installed and uninstalled distributions.
|
||||||
pub(crate) fn new(installed: Vec<CachedDist>, uninstalled: Vec<InstalledDist>) -> Self {
|
pub(crate) fn new(installed: Vec<CachedDist>, uninstalled: Vec<InstalledDist>) -> Self {
|
||||||
|
// SAFETY: This is allowed because `LocalDist` implements `Hash` and `Eq` based solely on
|
||||||
|
// the inner `kind`, and omits the types that rely on internal mutability.
|
||||||
|
#[allow(clippy::mutable_key_type)]
|
||||||
let mut uninstalled: HashSet<_> = uninstalled.into_iter().map(LocalDist::from).collect();
|
let mut uninstalled: HashSet<_> = uninstalled.into_iter().map(LocalDist::from).collect();
|
||||||
|
|
||||||
let (reinstalled, installed): (HashSet<_>, HashSet<_>) = installed
|
let (reinstalled, installed): (HashSet<_>, HashSet<_>) = installed
|
||||||
|
|
|
||||||
|
|
@ -102,9 +102,11 @@ pub(crate) fn pip_show(
|
||||||
if let Ok(metadata) = dist.read_metadata() {
|
if let Ok(metadata) = dist.read_metadata() {
|
||||||
requires_map.insert(
|
requires_map.insert(
|
||||||
dist.name(),
|
dist.name(),
|
||||||
Box::into_iter(metadata.requires_dist)
|
metadata
|
||||||
|
.requires_dist
|
||||||
|
.iter()
|
||||||
.filter(|req| req.evaluate_markers(&markers, &[]))
|
.filter(|req| req.evaluate_markers(&markers, &[]))
|
||||||
.map(|req| req.name)
|
.map(|req| &req.name)
|
||||||
.sorted_unstable()
|
.sorted_unstable()
|
||||||
.dedup()
|
.dedup()
|
||||||
.collect_vec(),
|
.collect_vec(),
|
||||||
|
|
@ -118,9 +120,11 @@ pub(crate) fn pip_show(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Ok(metadata) = installed.read_metadata() {
|
if let Ok(metadata) = installed.read_metadata() {
|
||||||
let requires = Box::into_iter(metadata.requires_dist)
|
let requires = metadata
|
||||||
|
.requires_dist
|
||||||
|
.iter()
|
||||||
.filter(|req| req.evaluate_markers(&markers, &[]))
|
.filter(|req| req.evaluate_markers(&markers, &[]))
|
||||||
.map(|req| req.name)
|
.map(|req| &req.name)
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
if !requires.is_empty() {
|
if !requires.is_empty() {
|
||||||
requires_map.insert(installed.name(), requires);
|
requires_map.insert(installed.name(), requires);
|
||||||
|
|
@ -172,7 +176,7 @@ pub(crate) fn pip_show(
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(name, pkgs)| {
|
.filter(|(name, pkgs)| {
|
||||||
**name != distribution.name()
|
**name != distribution.name()
|
||||||
&& pkgs.iter().any(|pkg| pkg == distribution.name())
|
&& pkgs.iter().any(|pkg| *pkg == distribution.name())
|
||||||
})
|
})
|
||||||
.map(|(name, _)| name)
|
.map(|(name, _)| name)
|
||||||
.sorted_unstable()
|
.sorted_unstable()
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ impl<'env> DisplayDependencyGraph<'env> {
|
||||||
invert: bool,
|
invert: bool,
|
||||||
show_version_specifiers: bool,
|
show_version_specifiers: bool,
|
||||||
markers: &ResolverMarkerEnvironment,
|
markers: &ResolverMarkerEnvironment,
|
||||||
packages: &'env FxHashMap<&PackageName, Vec<ResolutionMetadata>>,
|
packages: &'env FxHashMap<&PackageName, Vec<&ResolutionMetadata>>,
|
||||||
latest: &'env FxHashMap<&PackageName, Version>,
|
latest: &'env FxHashMap<&PackageName, Version>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Create a graph.
|
// Create a graph.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue