Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions crates/uv-distribution-types/src/dist_error.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use crate::{BuiltDist, Dist, DistRef, Edge, Name, Node, Resolution, ResolvedDist, SourceDist};
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Formatter};

use petgraph::prelude::EdgeRef;
use petgraph::Direction;
use rustc_hash::FxHashSet;
use std::collections::VecDeque;
use std::fmt::{Debug, Display, Formatter};
use version_ranges::Ranges;

use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::Version;
use version_ranges::Ranges;

use crate::{
BuiltDist, Dist, DistRef, Edge, Name, Node, RequestedDist, Resolution, ResolvedDist, SourceDist,
};

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

impl DistErrorKind {
pub fn from_dist_and_err(dist: &Dist, err: &impl IsBuildBackendError) -> Self {
pub fn from_requested_dist(dist: &RequestedDist, err: &impl IsBuildBackendError) -> Self {
match dist {
RequestedDist::Installed(_) => DistErrorKind::Read,
RequestedDist::Installable(dist) => Self::from_dist(dist, err),
}
}

pub fn from_dist(dist: &Dist, err: &impl IsBuildBackendError) -> Self {
if err.is_build_backend_error() {
DistErrorKind::BuildBackend
} else {
Expand Down
2 changes: 2 additions & 0 deletions crates/uv-distribution-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub use crate::installed::*;
pub use crate::origin::*;
pub use crate::pip_index::*;
pub use crate::prioritized_distribution::*;
pub use crate::requested::*;
pub use crate::resolution::*;
pub use crate::resolved::*;
pub use crate::specified_requirement::*;
Expand All @@ -90,6 +91,7 @@ mod installed;
mod origin;
mod pip_index;
mod prioritized_distribution;
mod requested;
mod resolution;
mod resolved;
mod specified_requirement;
Expand Down
71 changes: 71 additions & 0 deletions crates/uv-distribution-types/src/requested.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use std::fmt::{Display, Formatter};

use crate::{
Dist, DistributionId, DistributionMetadata, Identifier, InstalledDist, Name, ResourceId,
VersionOrUrlRef,
};
use uv_normalize::PackageName;
use uv_pep440::Version;

/// A distribution that can be requested during resolution.
///
/// Either an already-installed distribution or a distribution that can be installed.
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum RequestedDist {
Installed(InstalledDist),
Installable(Dist),
}

impl RequestedDist {
/// Returns the version of the distribution, if it is known.
pub fn version(&self) -> Option<&Version> {
match self {
Self::Installed(dist) => Some(dist.version()),
Self::Installable(dist) => dist.version(),
}
}
}

impl Name for RequestedDist {
fn name(&self) -> &PackageName {
match self {
Self::Installable(dist) => dist.name(),
Self::Installed(dist) => dist.name(),
}
}
}

impl DistributionMetadata for RequestedDist {
fn version_or_url(&self) -> VersionOrUrlRef {
match self {
Self::Installed(dist) => dist.version_or_url(),
Self::Installable(dist) => dist.version_or_url(),
}
}
}

impl Identifier for RequestedDist {
fn distribution_id(&self) -> DistributionId {
match self {
Self::Installed(dist) => dist.distribution_id(),
Self::Installable(dist) => dist.distribution_id(),
}
}

fn resource_id(&self) -> ResourceId {
match self {
Self::Installed(dist) => dist.resource_id(),
Self::Installable(dist) => dist.resource_id(),
}
}
}

impl Display for RequestedDist {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Installed(dist) => dist.fmt(f),
Self::Installable(dist) => dist.fmt(f),
}
}
}
29 changes: 28 additions & 1 deletion crates/uv-distribution/src/distribution_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use uv_client::{
};
use uv_distribution_filename::WheelFilename;
use uv_distribution_types::{
BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, Name, SourceDist,
BuildableSource, BuiltDist, Dist, FileLocation, HashPolicy, Hashed, InstalledDist, Name,
SourceDist,
};
use uv_extract::hash::Hasher;
use uv_fs::write_atomic;
Expand Down Expand Up @@ -115,6 +116,32 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
}
}

/// Either fetch the only wheel metadata (directly from the index or with range requests) or
/// fetch and build the source distribution.
///
/// While hashes will be generated in some cases, hash-checking is only enforced for source
/// distributions, and should be enforced by the caller for wheels.
#[instrument(skip_all, fields(%dist))]
pub async fn get_installed_metadata(
&self,
dist: &InstalledDist,
) -> Result<ArchiveMetadata, Error> {
// If the metadata was provided by the user directly, prefer it.
if let Some(metadata) = self
.build_context
.dependency_metadata()
.get(dist.name(), Some(dist.version()))
{
return Ok(ArchiveMetadata::from_metadata23(metadata.clone()));
}

let metadata = dist
.metadata()
.map_err(|err| Error::ReadInstalled(Box::new(dist.clone()), err))?;

Ok(ArchiveMetadata::from_metadata23(metadata))
}

/// Either fetch the only wheel metadata (directly from the index or with range requests) or
/// fetch and build the source distribution.
///
Expand Down
4 changes: 3 additions & 1 deletion crates/uv-distribution/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use zip::result::ZipError;
use crate::metadata::MetadataError;
use uv_client::WrappedReqwestError;
use uv_distribution_filename::WheelFilenameError;
use uv_distribution_types::IsBuildBackendError;
use uv_distribution_types::{InstalledDist, InstalledDistError, IsBuildBackendError};
use uv_fs::Simplified;
use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers};
Expand Down Expand Up @@ -82,6 +82,8 @@ pub enum Error {
Metadata(#[from] uv_pypi_types::MetadataError),
#[error("Failed to read metadata: `{}`", _0.user_display())]
WheelMetadata(PathBuf, #[source] Box<uv_metadata::Error>),
#[error("Failed to read metadata from installed package `{0}`")]
ReadInstalled(Box<InstalledDist>, #[source] InstalledDistError),
#[error("Failed to read zip archive from built wheel")]
Zip(#[from] ZipError),
#[error("Source distribution directory contains neither readable `pyproject.toml` nor `setup.py`: `{}`", _0.user_display())]
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-installer/src/preparer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ impl Error {
let chain =
DerivationChain::from_resolution(resolution, (&dist).into()).unwrap_or_default();
Self::Dist(
DistErrorKind::from_dist_and_err(&dist, &err),
DistErrorKind::from_dist(&dist, &err),
Box::new(dist),
chain,
err,
Expand Down
6 changes: 1 addition & 5 deletions crates/uv-requirements/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ pub enum Error {
impl Error {
/// Create an [`Error`] from a distribution error.
pub(crate) fn from_dist(dist: Dist, err: uv_distribution::Error) -> Self {
Self::Dist(
DistErrorKind::from_dist_and_err(&dist, &err),
Box::new(dist),
err,
)
Self::Dist(DistErrorKind::from_dist(&dist, &err), Box::new(dist), err)
}
}

Expand Down
8 changes: 2 additions & 6 deletions crates/uv-resolver/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ use rustc_hash::FxHashMap;
use tracing::trace;

use uv_distribution_types::{
DerivationChain, Dist, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl,
InstalledDist, InstalledDistError,
DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
};
use uv_normalize::{ExtraName, PackageName};
use uv_pep440::{LocalVersionSlice, Version};
Expand Down Expand Up @@ -98,14 +97,11 @@ pub enum ResolveError {
#[error("{0} `{1}`")]
Dist(
DistErrorKind,
Box<Dist>,
Box<RequestedDist>,
DerivationChain,
#[source] Arc<uv_distribution::Error>,
),

#[error("Failed to read metadata from installed package `{0}`")]
ReadInstalled(Box<InstalledDist>, #[source] InstalledDistError),

#[error(transparent)]
NoSolution(#[from] NoSolutionError),

Expand Down
37 changes: 15 additions & 22 deletions crates/uv-resolver/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use tokio_stream::wrappers::ReceiverStream;
use tracing::{debug, info, instrument, trace, warn, Level};

use uv_configuration::{Constraints, Overrides};
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
use uv_distribution::DistributionDatabase;
use uv_distribution_types::{
BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata,
IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations,
Expand All @@ -33,9 +33,7 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifiers, MIN_VERSION};
use uv_pep508::MarkerTree;
use uv_platform_tags::Tags;
use uv_pypi_types::{
ConflictItem, ConflictItemRef, Conflicts, Requirement, ResolutionMetadata, VerbatimParsedUrl,
};
use uv_pypi_types::{ConflictItem, ConflictItemRef, Conflicts, Requirement, VerbatimParsedUrl};
use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
use uv_warnings::warn_user_once;

Expand Down Expand Up @@ -1097,11 +1095,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
.insert(name.clone(), reason.into());
return Ok(None);
}
// TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
// critical since we fetch URL dependencies _prior_ to invoking the resolver.
MetadataResponse::Error(dist, err) => {
// TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
// critical since we fetch URL dependencies _prior_ to invoking the resolver.
return Err(ResolveError::Dist(
DistErrorKind::from_dist_and_err(dist, &**err),
DistErrorKind::from_requested_dist(dist, &**err),
dist.clone(),
DerivationChain::default(),
err.clone(),
Expand Down Expand Up @@ -1642,7 +1640,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
.unwrap_or_default();
return Err(ResolveError::Dist(
DistErrorKind::from_dist_and_err(dist, &**err),
DistErrorKind::from_requested_dist(dist, &**err),
dist.clone(),
chain,
err.clone(),
Expand Down Expand Up @@ -2068,12 +2066,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
Some(Response::Installed { dist, metadata }) => {
trace!("Received installed distribution metadata for: {dist}");
self.index.distributions().done(
dist.version_id(),
Arc::new(MetadataResponse::Found(ArchiveMetadata::from_metadata23(
metadata,
))),
);
self.index
.distributions()
.done(dist.version_id(), Arc::new(metadata));
}
Some(Response::Dist { dist, metadata }) => {
let dist_kind = match dist {
Expand Down Expand Up @@ -2134,10 +2129,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}

Request::Installed(dist) => {
// TODO(charlie): This should be return a `MetadataResponse`.
let metadata = dist
.metadata()
.map_err(|err| ResolveError::ReadInstalled(Box::new(dist.clone()), err))?;
let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;

Ok(Some(Response::Installed { dist, metadata }))
}

Expand Down Expand Up @@ -2251,9 +2244,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
Response::Dist { dist, metadata }
}
ResolvedDist::Installed { dist } => {
let metadata = dist.metadata().map_err(|err| {
ResolveError::ReadInstalled(Box::new(dist.clone()), err)
})?;
let metadata =
provider.get_installed_metadata(&dist).boxed_local().await?;

Response::Installed { dist, metadata }
}
};
Expand Down Expand Up @@ -3079,7 +3072,7 @@ enum Response {
/// The returned metadata for an already-installed distribution.
Installed {
dist: InstalledDist,
metadata: ResolutionMetadata,
metadata: MetadataResponse,
},
}

Expand Down
28 changes: 24 additions & 4 deletions crates/uv-resolver/src/resolver/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::sync::Arc;

use uv_configuration::BuildOptions;
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl};
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl, InstalledDist, RequestedDist};
use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers};
use uv_platform_tags::Tags;
Expand Down Expand Up @@ -37,7 +37,7 @@ pub enum MetadataResponse {
/// A non-fatal error.
Unavailable(MetadataUnavailable),
/// The distribution could not be built or downloaded, a fatal error.
Error(Box<Dist>, Arc<uv_distribution::Error>),
Error(Box<RequestedDist>, Arc<uv_distribution::Error>),
}

/// Non-fatal metadata fetching error.
Expand Down Expand Up @@ -83,14 +83,20 @@ pub trait ResolverProvider {

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

/// Get the metadata for an installed distribution.
fn get_installed_metadata<'io>(
&'io self,
dist: &'io InstalledDist,
) -> impl Future<Output = WheelMetadataResult> + 'io;

/// Set the [`uv_distribution::Reporter`] to use for this installer.
#[must_use]
fn with_reporter(self, reporter: impl uv_distribution::Reporter + 'static) -> Self;
Expand Down Expand Up @@ -246,13 +252,27 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
))
}
err => Ok(MetadataResponse::Error(
Box::new(dist.clone()),
Box::new(RequestedDist::Installable(dist.clone())),
Arc::new(err),
)),
},
}
}

/// Return the metadata for an installed distribution.
async fn get_installed_metadata<'io>(
&'io self,
dist: &'io InstalledDist,
) -> WheelMetadataResult {
match self.fetcher.get_installed_metadata(dist).await {
Ok(metadata) => Ok(MetadataResponse::Found(metadata)),
Err(err) => Ok(MetadataResponse::Error(
Box::new(RequestedDist::Installed(dist.clone())),
Arc::new(err),
)),
}
}

/// Set the [`uv_distribution::Reporter`] to use for this installer.
#[must_use]
fn with_reporter(self, reporter: impl uv_distribution::Reporter + 'static) -> Self {
Expand Down
Loading