Skip to content

Commit f054f3e

Browse files
committed
Respect static metadata for already-installed distributions
1 parent 0ea4f5f commit f054f3e

12 files changed

Lines changed: 332 additions & 47 deletions

File tree

crates/uv-distribution-types/src/dist_error.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
use crate::{BuiltDist, Dist, DistRef, Edge, Name, Node, Resolution, ResolvedDist, SourceDist};
1+
use std::collections::VecDeque;
2+
use std::fmt::{Debug, Display, Formatter};
3+
24
use petgraph::prelude::EdgeRef;
35
use petgraph::Direction;
46
use rustc_hash::FxHashSet;
5-
use std::collections::VecDeque;
6-
use std::fmt::{Debug, Display, Formatter};
7+
use version_ranges::Ranges;
8+
79
use uv_normalize::{ExtraName, GroupName, PackageName};
810
use uv_pep440::Version;
9-
use version_ranges::Ranges;
11+
12+
use crate::{
13+
BuiltDist, Dist, DistRef, Edge, Name, Node, RequestedDist, Resolution, ResolvedDist, SourceDist,
14+
};
1015

1116
/// Inspect whether an error type is a build error.
1217
pub trait IsBuildBackendError: std::error::Error + Send + Sync + 'static {
@@ -25,7 +30,14 @@ pub enum DistErrorKind {
2530
}
2631

2732
impl DistErrorKind {
28-
pub fn from_dist_and_err(dist: &Dist, err: &impl IsBuildBackendError) -> Self {
33+
pub fn from_requested_dist(dist: &RequestedDist, err: &impl IsBuildBackendError) -> Self {
34+
match dist {
35+
RequestedDist::Installed(_) => DistErrorKind::Read,
36+
RequestedDist::Installable(dist) => Self::from_dist(dist, err),
37+
}
38+
}
39+
40+
pub fn from_dist(dist: &Dist, err: &impl IsBuildBackendError) -> Self {
2941
if err.is_build_backend_error() {
3042
DistErrorKind::BuildBackend
3143
} else {

crates/uv-distribution-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ pub use crate::installed::*;
6767
pub use crate::origin::*;
6868
pub use crate::pip_index::*;
6969
pub use crate::prioritized_distribution::*;
70+
pub use crate::requested::*;
7071
pub use crate::resolution::*;
7172
pub use crate::resolved::*;
7273
pub use crate::specified_requirement::*;
@@ -90,6 +91,7 @@ mod installed;
9091
mod origin;
9192
mod pip_index;
9293
mod prioritized_distribution;
94+
mod requested;
9395
mod resolution;
9496
mod resolved;
9597
mod specified_requirement;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
use uv_normalize::PackageName;
4+
5+
use crate::{
6+
Dist, DistributionId, DistributionMetadata, Identifier, InstalledDist, Name, ResourceId,
7+
VersionOrUrlRef,
8+
};
9+
10+
/// A distribution that can be requested during resolution.
11+
///
12+
/// Either an already-installed distribution or a distribution that can be installed.
13+
#[derive(Debug, Clone)]
14+
#[allow(clippy::large_enum_variant)]
15+
pub enum RequestedDist {
16+
Installed(InstalledDist),
17+
Installable(Dist),
18+
}
19+
20+
impl Name for RequestedDist {
21+
fn name(&self) -> &PackageName {
22+
match self {
23+
Self::Installable(dist) => dist.name(),
24+
Self::Installed(dist) => dist.name(),
25+
}
26+
}
27+
}
28+
29+
impl DistributionMetadata for RequestedDist {
30+
fn version_or_url(&self) -> VersionOrUrlRef {
31+
match self {
32+
Self::Installed(dist) => dist.version_or_url(),
33+
Self::Installable(dist) => dist.version_or_url(),
34+
}
35+
}
36+
}
37+
38+
impl Identifier for RequestedDist {
39+
fn distribution_id(&self) -> DistributionId {
40+
match self {
41+
Self::Installed(dist) => dist.distribution_id(),
42+
Self::Installable(dist) => dist.distribution_id(),
43+
}
44+
}
45+
46+
fn resource_id(&self) -> ResourceId {
47+
match self {
48+
Self::Installed(dist) => dist.resource_id(),
49+
Self::Installable(dist) => dist.resource_id(),
50+
}
51+
}
52+
}
53+
54+
impl Display for RequestedDist {
55+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
56+
match self {
57+
Self::Installed(dist) => dist.fmt(f),
58+
Self::Installable(dist) => dist.fmt(f),
59+
}
60+
}
61+
}

crates/uv-distribution/src/distribution_database.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ use uv_client::{
2121
};
2222
use uv_distribution_filename::WheelFilename;
2323
use uv_distribution_types::{
24-
BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, Name, SourceDist,
24+
BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, InstalledDist, Name,
25+
SourceDist,
2526
};
2627
use uv_extract::hash::Hasher;
2728
use uv_fs::write_atomic;
@@ -115,6 +116,32 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
115116
}
116117
}
117118

119+
/// Either fetch the only wheel metadata (directly from the index or with range requests) or
120+
/// fetch and build the source distribution.
121+
///
122+
/// While hashes will be generated in some cases, hash-checking is only enforced for source
123+
/// distributions, and should be enforced by the caller for wheels.
124+
#[instrument(skip_all, fields(%dist))]
125+
pub async fn get_installed_metadata(
126+
&self,
127+
dist: &InstalledDist,
128+
) -> Result<ArchiveMetadata, Error> {
129+
// If the metadata was provided by the user directly, prefer it.
130+
if let Some(metadata) = self
131+
.build_context
132+
.dependency_metadata()
133+
.get(dist.name(), Some(dist.version()))
134+
{
135+
return Ok(ArchiveMetadata::from_metadata23(metadata.clone()));
136+
}
137+
138+
let metadata = dist
139+
.metadata()
140+
.map_err(|err| Error::ReadInstalled(Box::new(dist.clone()), err))?;
141+
142+
Ok(ArchiveMetadata::from_metadata23(metadata))
143+
}
144+
118145
/// Either fetch the only wheel metadata (directly from the index or with range requests) or
119146
/// fetch and build the source distribution.
120147
///

crates/uv-distribution/src/error.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use zip::result::ZipError;
88
use crate::metadata::MetadataError;
99
use uv_client::WrappedReqwestError;
1010
use uv_distribution_filename::WheelFilenameError;
11-
use uv_distribution_types::IsBuildBackendError;
11+
use uv_distribution_types::{InstalledDist, InstalledDistError, IsBuildBackendError};
1212
use uv_fs::Simplified;
1313
use uv_normalize::PackageName;
1414
use uv_pep440::{Version, VersionSpecifiers};
@@ -82,6 +82,8 @@ pub enum Error {
8282
Metadata(#[from] uv_pypi_types::MetadataError),
8383
#[error("Failed to read metadata: `{}`", _0.user_display())]
8484
WheelMetadata(PathBuf, #[source] Box<uv_metadata::Error>),
85+
#[error("Failed to read metadata from installed package `{0}`")]
86+
ReadInstalled(Box<InstalledDist>, #[source] InstalledDistError),
8587
#[error("Failed to read zip archive from built wheel")]
8688
Zip(#[from] ZipError),
8789
#[error("Source distribution directory contains neither readable `pyproject.toml` nor `setup.py`: `{}`", _0.user_display())]

crates/uv-installer/src/preparer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl Error {
230230
let chain =
231231
DerivationChain::from_resolution(resolution, (&dist).into()).unwrap_or_default();
232232
Self::Dist(
233-
DistErrorKind::from_dist_and_err(&dist, &err),
233+
DistErrorKind::from_dist(&dist, &err),
234234
Box::new(dist),
235235
chain,
236236
err,

crates/uv-requirements/src/lib.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ pub enum Error {
3535
impl Error {
3636
/// Create an [`Error`] from a distribution error.
3737
pub(crate) fn from_dist(dist: Dist, err: uv_distribution::Error) -> Self {
38-
Self::Dist(
39-
DistErrorKind::from_dist_and_err(&dist, &err),
40-
Box::new(dist),
41-
err,
42-
)
38+
Self::Dist(DistErrorKind::from_dist(&dist, &err), Box::new(dist), err)
4339
}
4440
}
4541

crates/uv-resolver/src/error.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ use rustc_hash::FxHashMap;
1010
use tracing::trace;
1111

1212
use uv_distribution_types::{
13-
DerivationChain, Dist, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl,
14-
InstalledDist, InstalledDistError,
13+
DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
1514
};
1615
use uv_normalize::{ExtraName, PackageName};
1716
use uv_pep440::{LocalVersionSlice, Version};
@@ -98,14 +97,11 @@ pub enum ResolveError {
9897
#[error("{0} `{1}`")]
9998
Dist(
10099
DistErrorKind,
101-
Box<Dist>,
100+
Box<RequestedDist>,
102101
DerivationChain,
103102
#[source] Arc<uv_distribution::Error>,
104103
),
105104

106-
#[error("Failed to read metadata from installed package `{0}`")]
107-
ReadInstalled(Box<InstalledDist>, #[source] InstalledDistError),
108-
109105
#[error(transparent)]
110106
NoSolution(#[from] NoSolutionError),
111107

crates/uv-resolver/src/resolver/mod.rs

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use tokio_stream::wrappers::ReceiverStream;
2121
use tracing::{debug, info, instrument, trace, warn, Level};
2222

2323
use uv_configuration::{Constraints, Overrides};
24-
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
24+
use uv_distribution::DistributionDatabase;
2525
use uv_distribution_types::{
2626
BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata,
2727
IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations,
@@ -33,9 +33,7 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
3333
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifiers, MIN_VERSION};
3434
use uv_pep508::MarkerTree;
3535
use uv_platform_tags::Tags;
36-
use uv_pypi_types::{
37-
ConflictItem, ConflictItemRef, Conflicts, Requirement, ResolutionMetadata, VerbatimParsedUrl,
38-
};
36+
use uv_pypi_types::{ConflictItem, ConflictItemRef, Conflicts, Requirement, VerbatimParsedUrl};
3937
use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
4038
use uv_warnings::warn_user_once;
4139

@@ -1097,11 +1095,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
10971095
.insert(name.clone(), reason.into());
10981096
return Ok(None);
10991097
}
1098+
// TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
1099+
// critical since we fetch URL dependencies _prior_ to invoking the resolver.
11001100
MetadataResponse::Error(dist, err) => {
1101-
// TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
1102-
// critical since we fetch URL dependencies _prior_ to invoking the resolver.
11031101
return Err(ResolveError::Dist(
1104-
DistErrorKind::from_dist_and_err(dist, &**err),
1102+
DistErrorKind::from_requested_dist(dist, &**err),
11051103
dist.clone(),
11061104
DerivationChain::default(),
11071105
err.clone(),
@@ -1642,7 +1640,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
16421640
let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
16431641
.unwrap_or_default();
16441642
return Err(ResolveError::Dist(
1645-
DistErrorKind::from_dist_and_err(dist, &**err),
1643+
DistErrorKind::from_requested_dist(dist, &**err),
16461644
dist.clone(),
16471645
chain,
16481646
err.clone(),
@@ -2068,12 +2066,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
20682066
}
20692067
Some(Response::Installed { dist, metadata }) => {
20702068
trace!("Received installed distribution metadata for: {dist}");
2071-
self.index.distributions().done(
2072-
dist.version_id(),
2073-
Arc::new(MetadataResponse::Found(ArchiveMetadata::from_metadata23(
2074-
metadata,
2075-
))),
2076-
);
2069+
self.index
2070+
.distributions()
2071+
.done(dist.version_id(), Arc::new(metadata));
20772072
}
20782073
Some(Response::Dist { dist, metadata }) => {
20792074
let dist_kind = match dist {
@@ -2134,10 +2129,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
21342129
}
21352130

21362131
Request::Installed(dist) => {
2137-
// TODO(charlie): This should be return a `MetadataResponse`.
2138-
let metadata = dist
2139-
.metadata()
2140-
.map_err(|err| ResolveError::ReadInstalled(Box::new(dist.clone()), err))?;
2132+
let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2133+
21412134
Ok(Some(Response::Installed { dist, metadata }))
21422135
}
21432136

@@ -2251,9 +2244,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
22512244
Response::Dist { dist, metadata }
22522245
}
22532246
ResolvedDist::Installed { dist } => {
2254-
let metadata = dist.metadata().map_err(|err| {
2255-
ResolveError::ReadInstalled(Box::new(dist.clone()), err)
2256-
})?;
2247+
let metadata =
2248+
provider.get_installed_metadata(&dist).boxed_local().await?;
2249+
22572250
Response::Installed { dist, metadata }
22582251
}
22592252
};
@@ -3079,7 +3072,7 @@ enum Response {
30793072
/// The returned metadata for an already-installed distribution.
30803073
Installed {
30813074
dist: InstalledDist,
3082-
metadata: ResolutionMetadata,
3075+
metadata: MetadataResponse,
30833076
},
30843077
}
30853078

crates/uv-resolver/src/resolver/provider.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::sync::Arc;
33

44
use uv_configuration::BuildOptions;
55
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
6-
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl};
6+
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl, InstalledDist, RequestedDist};
77
use uv_normalize::PackageName;
88
use uv_pep440::{Version, VersionSpecifiers};
99
use uv_platform_tags::Tags;
@@ -37,7 +37,7 @@ pub enum MetadataResponse {
3737
/// A non-fatal error.
3838
Unavailable(MetadataUnavailable),
3939
/// The distribution could not be built or downloaded, a fatal error.
40-
Error(Box<Dist>, Arc<uv_distribution::Error>),
40+
Error(Box<RequestedDist>, Arc<uv_distribution::Error>),
4141
}
4242

4343
/// Non-fatal metadata fetching error.
@@ -83,14 +83,20 @@ pub trait ResolverProvider {
8383

8484
/// Get the metadata for a distribution.
8585
///
86-
/// For a wheel, this is done by querying it's (remote) metadata, for a source dist we
86+
/// For a wheel, this is done by querying it (remote) metadata. For a source distribution, we
8787
/// (fetch and) build the source distribution and return the metadata from the built
8888
/// distribution.
8989
fn get_or_build_wheel_metadata<'io>(
9090
&'io self,
9191
dist: &'io Dist,
9292
) -> impl Future<Output = WheelMetadataResult> + 'io;
9393

94+
/// Get the metadata for an installed distribution.
95+
fn get_installed_metadata<'io>(
96+
&'io self,
97+
dist: &'io InstalledDist,
98+
) -> impl Future<Output = WheelMetadataResult> + 'io;
99+
94100
/// Set the [`uv_distribution::Reporter`] to use for this installer.
95101
#[must_use]
96102
fn with_reporter(self, reporter: impl uv_distribution::Reporter + 'static) -> Self;
@@ -246,13 +252,27 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
246252
))
247253
}
248254
err => Ok(MetadataResponse::Error(
249-
Box::new(dist.clone()),
255+
Box::new(RequestedDist::Installable(dist.clone())),
250256
Arc::new(err),
251257
)),
252258
},
253259
}
254260
}
255261

262+
/// Return the metadata for an installed distribution.
263+
async fn get_installed_metadata<'io>(
264+
&'io self,
265+
dist: &'io InstalledDist,
266+
) -> WheelMetadataResult {
267+
match self.fetcher.get_installed_metadata(dist).await {
268+
Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
269+
Err(err) => Ok(MetadataResponse::Error(
270+
Box::new(RequestedDist::Installed(dist.clone())),
271+
Arc::new(err),
272+
)),
273+
}
274+
}
275+
256276
/// Set the [`uv_distribution::Reporter`] to use for this installer.
257277
#[must_use]
258278
fn with_reporter(self, reporter: impl uv_distribution::Reporter + 'static) -> Self {

0 commit comments

Comments
 (0)