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:
Charlie Marsh 2025-08-25 09:40:20 -04:00 committed by GitHub
parent be4d5b72aa
commit ef9a332364
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 240 additions and 161 deletions

View file

@ -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(),
} }
} }
} }

View file

@ -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

View file

@ -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;
}; };

View file

@ -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) {

View file

@ -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??;

View file

@ -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 {

View file

@ -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())
} }
}) })

View file

@ -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

View file

@ -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()

View file

@ -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.