mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Respect static metadata for already-installed distributions (#10242)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on opensuse (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86_64 (push) Blocked by required conditions
CI / check system | python3.10 on windows (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary Closes https://github.com/astral-sh/uv/issues/10239#issuecomment-2565663046
This commit is contained in:
parent
dec6f5aa02
commit
dcd96a83aa
12 changed files with 347 additions and 47 deletions
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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::*;
|
||||
|
@ -90,6 +91,7 @@ mod installed;
|
|||
mod origin;
|
||||
mod pip_index;
|
||||
mod prioritized_distribution;
|
||||
mod requested;
|
||||
mod resolution;
|
||||
mod resolved;
|
||||
mod specified_requirement;
|
||||
|
|
71
crates/uv-distribution-types/src/requested.rs
Normal file
71
crates/uv-distribution-types/src/requested.rs
Normal file
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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};
|
||||
|
@ -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())]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
@ -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),
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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(),
|
||||
|
@ -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 {
|
||||
|
@ -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 }))
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
};
|
||||
|
@ -3079,7 +3072,7 @@ enum Response {
|
|||
/// The returned metadata for an already-installed distribution.
|
||||
Installed {
|
||||
dist: InstalledDist,
|
||||
metadata: ResolutionMetadata,
|
||||
metadata: MetadataResponse,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -83,7 +83,7 @@ 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>(
|
||||
|
@ -91,6 +91,12 @@ pub trait ResolverProvider {
|
|||
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;
|
||||
|
@ -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 {
|
||||
|
|
|
@ -5,7 +5,9 @@ use owo_colors::OwoColorize;
|
|||
use rustc_hash::FxHashMap;
|
||||
use version_ranges::Ranges;
|
||||
|
||||
use uv_distribution_types::{DerivationChain, DerivationStep, Dist, DistErrorKind, Name};
|
||||
use uv_distribution_types::{
|
||||
DerivationChain, DerivationStep, Dist, DistErrorKind, Name, RequestedDist,
|
||||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
use uv_resolver::SentinelRange;
|
||||
|
@ -78,7 +80,7 @@ impl OperationDiagnostic {
|
|||
chain,
|
||||
err,
|
||||
)) => {
|
||||
dist_error(kind, dist, &chain, err);
|
||||
requested_dist_error(kind, dist, &chain, err);
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Requirements(uv_requirements::Error::Dist(kind, dist, err)) => {
|
||||
|
@ -154,6 +156,51 @@ pub(crate) fn dist_error(
|
|||
anstream::eprint!("{report:?}");
|
||||
}
|
||||
|
||||
/// Render a requested distribution failure (read, download or build) with a help message.
|
||||
pub(crate) fn requested_dist_error(
|
||||
kind: DistErrorKind,
|
||||
dist: Box<RequestedDist>,
|
||||
chain: &DerivationChain,
|
||||
cause: Arc<uv_distribution::Error>,
|
||||
) {
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("{kind} `{dist}`")]
|
||||
#[diagnostic()]
|
||||
struct Diagnostic {
|
||||
kind: DistErrorKind,
|
||||
dist: Box<RequestedDist>,
|
||||
#[source]
|
||||
cause: Arc<uv_distribution::Error>,
|
||||
#[help]
|
||||
help: Option<String>,
|
||||
}
|
||||
|
||||
let help = SUGGESTIONS
|
||||
.get(dist.name())
|
||||
.map(|suggestion| {
|
||||
format!(
|
||||
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
|
||||
dist.name().cyan(),
|
||||
suggestion.cyan(),
|
||||
suggestion.cyan(),
|
||||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
if chain.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(format_chain(dist.name(), dist.version(), chain))
|
||||
}
|
||||
});
|
||||
let report = miette::Report::new(Diagnostic {
|
||||
kind,
|
||||
dist,
|
||||
cause,
|
||||
help,
|
||||
});
|
||||
anstream::eprint!("{report:?}");
|
||||
}
|
||||
|
||||
/// Render a [`uv_resolver::NoSolutionError`].
|
||||
pub(crate) fn no_solution(err: &uv_resolver::NoSolutionError) {
|
||||
let report = miette::Report::msg(format!("{err}")).context(err.header());
|
||||
|
|
|
@ -7724,3 +7724,137 @@ fn missing_subdirectory_url() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_metadata_pyproject_toml() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
context.temp_dir.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyio==3.7.0"
|
||||
]
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "anyio"
|
||||
version = "3.7.0"
|
||||
requires-dist = ["typing-extensions"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-r")
|
||||
.arg("pyproject.toml"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ typing-extensions==4.10.0
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_metadata_source_tree() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
context.temp_dir.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyio==3.7.0"
|
||||
]
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "anyio"
|
||||
version = "3.7.0"
|
||||
requires-dist = ["typing-extensions"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e")
|
||||
.arg("."), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ example==0.0.0 (from file://[TEMP_DIR]/)
|
||||
+ typing-extensions==4.10.0
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for: <https://github.com/astral-sh/uv/issues/10239#issuecomment-2565663046>
|
||||
#[test]
|
||||
fn static_metadata_already_installed() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
context.temp_dir.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "example"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyio==3.7.0"
|
||||
]
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "anyio"
|
||||
version = "3.7.0"
|
||||
requires-dist = ["typing-extensions"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-r")
|
||||
.arg("pyproject.toml"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ typing-extensions==4.10.0
|
||||
"###
|
||||
);
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e")
|
||||
.arg("."), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ example==0.0.0 (from file://[TEMP_DIR]/)
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue