mirror of https://github.com/astral-sh/uv
Show full derivation chain when encountering build failures (#9108)
## Summary This PR adds context to our error messages to explain _why_ a given package was included, if we fail to download or build it. It's quite a large change, but it motivated some good refactors and improvements along the way. Closes https://github.com/astral-sh/uv/issues/8962.
This commit is contained in:
parent
a552f74308
commit
fe477c3417
|
|
@ -29,8 +29,8 @@ use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
|
|||
use uv_pypi_types::{Conflicts, Requirement};
|
||||
use uv_python::{Interpreter, PythonEnvironment};
|
||||
use uv_resolver::{
|
||||
ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder,
|
||||
PythonRequirement, Resolver, ResolverEnvironment,
|
||||
DerivationChainBuilder, ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest,
|
||||
OptionsBuilder, PythonRequirement, Resolver, ResolverEnvironment,
|
||||
};
|
||||
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
|
||||
|
|
@ -278,7 +278,33 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
|||
remote.iter().map(ToString::to_string).join(", ")
|
||||
);
|
||||
|
||||
preparer.prepare(remote, self.in_flight).await?
|
||||
preparer
|
||||
.prepare(remote, self.in_flight)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
uv_installer::PrepareError::DownloadAndBuild(dist, chain, err) => {
|
||||
debug_assert!(chain.is_empty());
|
||||
let chain =
|
||||
DerivationChainBuilder::from_resolution(resolution, (&*dist).into())
|
||||
.unwrap_or_default();
|
||||
uv_installer::PrepareError::DownloadAndBuild(dist, chain, err)
|
||||
}
|
||||
uv_installer::PrepareError::Download(dist, chain, err) => {
|
||||
debug_assert!(chain.is_empty());
|
||||
let chain =
|
||||
DerivationChainBuilder::from_resolution(resolution, (&*dist).into())
|
||||
.unwrap_or_default();
|
||||
uv_installer::PrepareError::Download(dist, chain, err)
|
||||
}
|
||||
uv_installer::PrepareError::Build(dist, chain, err) => {
|
||||
debug_assert!(chain.is_empty());
|
||||
let chain =
|
||||
DerivationChainBuilder::from_resolution(resolution, (&*dist).into())
|
||||
.unwrap_or_default();
|
||||
uv_installer::PrepareError::Build(dist, chain, err)
|
||||
}
|
||||
_ => err,
|
||||
})?
|
||||
};
|
||||
|
||||
// Remove any unnecessary packages.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
use uv_normalize::PackageName;
|
||||
use uv_pep440::Version;
|
||||
|
||||
/// A chain of derivation steps from the root package to the current package, to explain why a
|
||||
/// package is included in the resolution.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DerivationChain(Vec<DerivationStep>);
|
||||
|
||||
impl FromIterator<DerivationStep> for DerivationChain {
|
||||
fn from_iter<T: IntoIterator<Item = DerivationStep>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl DerivationChain {
|
||||
/// Returns the length of the derivation chain.
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the derivation chain is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the steps in the derivation chain.
|
||||
pub fn iter(&self) -> std::slice::Iter<DerivationStep> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DerivationChain {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for (idx, step) in self.0.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
write!(f, " -> ")?;
|
||||
}
|
||||
write!(f, "{}=={}", step.name, step.version)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'chain> IntoIterator for &'chain DerivationChain {
|
||||
type Item = &'chain DerivationStep;
|
||||
type IntoIter = std::slice::Iter<'chain, DerivationStep>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for DerivationChain {
|
||||
type Item = DerivationStep;
|
||||
type IntoIter = std::vec::IntoIter<DerivationStep>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// A step in a derivation chain.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DerivationStep {
|
||||
/// The name of the package.
|
||||
name: PackageName,
|
||||
/// The version of the package.
|
||||
version: Version,
|
||||
}
|
||||
|
||||
impl DerivationStep {
|
||||
/// Create a [`DerivationStep`] from a package name and version.
|
||||
pub fn new(name: PackageName, version: Version) -> Self {
|
||||
Self { name, version }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DerivationStep {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}=={}", self.name, self.version)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ pub enum FileConversionError {
|
|||
}
|
||||
|
||||
/// Internal analog to [`uv_pypi_types::File`].
|
||||
#[derive(Debug, Clone, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub struct File {
|
||||
pub dist_info_metadata: bool,
|
||||
|
|
@ -66,7 +66,7 @@ impl File {
|
|||
}
|
||||
|
||||
/// While a registry file is generally a remote URL, it can also be a file if it comes from a directory flat indexes.
|
||||
#[derive(Debug, Clone, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum FileLocation {
|
||||
/// URL relative to the base URL.
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ pub use crate::any::*;
|
|||
pub use crate::buildable::*;
|
||||
pub use crate::cached::*;
|
||||
pub use crate::dependency_metadata::*;
|
||||
pub use crate::derivation::*;
|
||||
pub use crate::diagnostic::*;
|
||||
pub use crate::error::*;
|
||||
pub use crate::file::*;
|
||||
|
|
@ -74,6 +75,7 @@ mod any;
|
|||
mod buildable;
|
||||
mod cached;
|
||||
mod dependency_metadata;
|
||||
mod derivation;
|
||||
mod diagnostic;
|
||||
mod error;
|
||||
mod file;
|
||||
|
|
@ -166,14 +168,21 @@ impl std::fmt::Display for InstalledVersion<'_> {
|
|||
/// Either a built distribution, a wheel, or a source distribution that exists at some location.
|
||||
///
|
||||
/// The location can be an index, URL or path (wheel), or index, URL, path or Git repository (source distribution).
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum Dist {
|
||||
Built(BuiltDist),
|
||||
Source(SourceDist),
|
||||
}
|
||||
|
||||
/// A reference to a built or source distribution.
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum DistRef<'a> {
|
||||
Built(&'a BuiltDist),
|
||||
Source(&'a SourceDist),
|
||||
}
|
||||
|
||||
/// A wheel, with its three possible origins (index, url, path)
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum BuiltDist {
|
||||
Registry(RegistryBuiltDist),
|
||||
|
|
@ -182,7 +191,7 @@ pub enum BuiltDist {
|
|||
}
|
||||
|
||||
/// A source distribution, with its possible origins (index, url, path, git)
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum SourceDist {
|
||||
Registry(RegistrySourceDist),
|
||||
|
|
@ -193,7 +202,7 @@ pub enum SourceDist {
|
|||
}
|
||||
|
||||
/// A built distribution (wheel) that exists in a registry, like `PyPI`.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct RegistryBuiltWheel {
|
||||
pub filename: WheelFilename,
|
||||
pub file: Box<File>,
|
||||
|
|
@ -201,7 +210,7 @@ pub struct RegistryBuiltWheel {
|
|||
}
|
||||
|
||||
/// A built distribution (wheel) that exists in a registry, like `PyPI`.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct RegistryBuiltDist {
|
||||
/// All wheels associated with this distribution. It is guaranteed
|
||||
/// that there is at least one wheel.
|
||||
|
|
@ -231,7 +240,7 @@ pub struct RegistryBuiltDist {
|
|||
}
|
||||
|
||||
/// A built distribution (wheel) that exists at an arbitrary URL.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct DirectUrlBuiltDist {
|
||||
/// We require that wheel urls end in the full wheel filename, e.g.
|
||||
/// `https://example.org/packages/flask-3.0.0-py3-none-any.whl`
|
||||
|
|
@ -243,7 +252,7 @@ pub struct DirectUrlBuiltDist {
|
|||
}
|
||||
|
||||
/// A built distribution (wheel) that exists in a local directory.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct PathBuiltDist {
|
||||
pub filename: WheelFilename,
|
||||
/// The absolute path to the wheel which we use for installing.
|
||||
|
|
@ -253,7 +262,7 @@ pub struct PathBuiltDist {
|
|||
}
|
||||
|
||||
/// A source distribution that exists in a registry, like `PyPI`.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct RegistrySourceDist {
|
||||
pub name: PackageName,
|
||||
pub version: Version,
|
||||
|
|
@ -272,7 +281,7 @@ pub struct RegistrySourceDist {
|
|||
}
|
||||
|
||||
/// A source distribution that exists at an arbitrary URL.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct DirectUrlSourceDist {
|
||||
/// Unlike [`DirectUrlBuiltDist`], we can't require a full filename with a version here, people
|
||||
/// like using e.g. `foo @ https://github.com/org/repo/archive/master.zip`
|
||||
|
|
@ -288,7 +297,7 @@ pub struct DirectUrlSourceDist {
|
|||
}
|
||||
|
||||
/// A source distribution that exists in a Git repository.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct GitSourceDist {
|
||||
pub name: PackageName,
|
||||
/// The URL without the revision and subdirectory fragment.
|
||||
|
|
@ -300,7 +309,7 @@ pub struct GitSourceDist {
|
|||
}
|
||||
|
||||
/// A source distribution that exists in a local archive (e.g., a `.tar.gz` file).
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct PathSourceDist {
|
||||
pub name: PackageName,
|
||||
/// The absolute path to the distribution which we use for installing.
|
||||
|
|
@ -312,7 +321,7 @@ pub struct PathSourceDist {
|
|||
}
|
||||
|
||||
/// A source distribution that exists in a local directory.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct DirectorySourceDist {
|
||||
pub name: PackageName,
|
||||
/// The absolute path to the distribution which we use for installing.
|
||||
|
|
@ -512,12 +521,33 @@ impl Dist {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the version of the distribution, if it is known.
|
||||
pub fn version(&self) -> Option<&Version> {
|
||||
match self {
|
||||
Self::Built(wheel) => Some(wheel.version()),
|
||||
Self::Source(source_dist) => source_dist.version(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this distribution into a reference.
|
||||
pub fn as_ref(&self) -> DistRef {
|
||||
match self {
|
||||
Self::Built(dist) => DistRef::Built(dist),
|
||||
Self::Source(dist) => DistRef::Source(dist),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a SourceDist> for DistRef<'a> {
|
||||
fn from(dist: &'a SourceDist) -> Self {
|
||||
DistRef::Source(dist)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a BuiltDist> for DistRef<'a> {
|
||||
fn from(dist: &'a BuiltDist) -> Self {
|
||||
DistRef::Built(dist)
|
||||
}
|
||||
}
|
||||
|
||||
impl BuiltDist {
|
||||
|
|
|
|||
|
|
@ -9,29 +9,13 @@ use uv_cache::Cache;
|
|||
use uv_configuration::BuildOptions;
|
||||
use uv_distribution::{DistributionDatabase, LocalWheel};
|
||||
use uv_distribution_types::{
|
||||
BuildableSource, BuiltDist, CachedDist, Dist, Hashed, Identifier, Name, RemoteSource,
|
||||
SourceDist,
|
||||
BuildableSource, BuiltDist, CachedDist, DerivationChain, Dist, Hashed, Identifier, Name,
|
||||
RemoteSource, SourceDist,
|
||||
};
|
||||
use uv_pep508::PackageName;
|
||||
use uv_platform_tags::Tags;
|
||||
use uv_types::{BuildContext, HashStrategy, InFlight};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Building source distributions is disabled, but attempted to build `{0}`")]
|
||||
NoBuild(PackageName),
|
||||
#[error("Using pre-built wheels is disabled, but attempted to use `{0}`")]
|
||||
NoBinary(PackageName),
|
||||
#[error("Failed to download `{0}`")]
|
||||
Download(Box<BuiltDist>, #[source] uv_distribution::Error),
|
||||
#[error("Failed to download and build `{0}`")]
|
||||
DownloadAndBuild(Box<SourceDist>, #[source] uv_distribution::Error),
|
||||
#[error("Failed to build `{0}`")]
|
||||
Build(Box<SourceDist>, #[source] uv_distribution::Error),
|
||||
#[error("Unzip failed in another thread: {0}")]
|
||||
Thread(String),
|
||||
}
|
||||
|
||||
/// Prepare distributions for installation.
|
||||
///
|
||||
/// Downloads, builds, and unzips a set of distributions.
|
||||
|
|
@ -145,16 +129,7 @@ impl<'a, Context: BuildContext> Preparer<'a, Context> {
|
|||
.database
|
||||
.get_or_build_wheel(&dist, self.tags, policy)
|
||||
.boxed_local()
|
||||
.map_err(|err| match dist.clone() {
|
||||
Dist::Built(dist) => Error::Download(Box::new(dist), err),
|
||||
Dist::Source(dist) => {
|
||||
if dist.is_local() {
|
||||
Error::Build(Box::new(dist), err)
|
||||
} else {
|
||||
Error::DownloadAndBuild(Box::new(dist), err)
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|err| Error::from_dist(dist.clone(), err))
|
||||
.await
|
||||
.and_then(|wheel: LocalWheel| {
|
||||
if wheel.satisfies(policy) {
|
||||
|
|
@ -165,16 +140,7 @@ impl<'a, Context: BuildContext> Preparer<'a, Context> {
|
|||
policy.digests(),
|
||||
wheel.hashes(),
|
||||
);
|
||||
Err(match dist {
|
||||
Dist::Built(dist) => Error::Download(Box::new(dist), err),
|
||||
Dist::Source(dist) => {
|
||||
if dist.is_local() {
|
||||
Error::Build(Box::new(dist), err)
|
||||
} else {
|
||||
Error::DownloadAndBuild(Box::new(dist), err)
|
||||
}
|
||||
}
|
||||
})
|
||||
Err(Error::from_dist(dist, err))
|
||||
}
|
||||
})
|
||||
.map(CachedDist::from);
|
||||
|
|
@ -203,6 +169,50 @@ impl<'a, Context: BuildContext> Preparer<'a, Context> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Building source distributions is disabled, but attempted to build `{0}`")]
|
||||
NoBuild(PackageName),
|
||||
#[error("Using pre-built wheels is disabled, but attempted to use `{0}`")]
|
||||
NoBinary(PackageName),
|
||||
#[error("Failed to download `{0}`")]
|
||||
Download(
|
||||
Box<BuiltDist>,
|
||||
DerivationChain,
|
||||
#[source] uv_distribution::Error,
|
||||
),
|
||||
#[error("Failed to download and build `{0}`")]
|
||||
DownloadAndBuild(
|
||||
Box<SourceDist>,
|
||||
DerivationChain,
|
||||
#[source] uv_distribution::Error,
|
||||
),
|
||||
#[error("Failed to build `{0}`")]
|
||||
Build(
|
||||
Box<SourceDist>,
|
||||
DerivationChain,
|
||||
#[source] uv_distribution::Error,
|
||||
),
|
||||
#[error("Unzip failed in another thread: {0}")]
|
||||
Thread(String),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Create an [`Error`] from a distribution error.
|
||||
fn from_dist(dist: Dist, cause: uv_distribution::Error) -> Self {
|
||||
match dist {
|
||||
Dist::Built(dist) => Self::Download(Box::new(dist), DerivationChain::default(), cause),
|
||||
Dist::Source(dist) => {
|
||||
if dist.is_local() {
|
||||
Self::Build(Box::new(dist), DerivationChain::default(), cause)
|
||||
} else {
|
||||
Self::DownloadAndBuild(Box::new(dist), DerivationChain::default(), cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Reporter: Send + Sync {
|
||||
/// Callback to invoke when a wheel is unzipped. This implies that the wheel was downloaded and,
|
||||
/// if necessary, built.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
use futures::{stream::FuturesOrdered, TryStreamExt};
|
||||
|
||||
use uv_distribution::{DistributionDatabase, Reporter};
|
||||
use uv_distribution_types::{Dist, DistributionMetadata};
|
||||
use uv_distribution_types::DistributionMetadata;
|
||||
use uv_pypi_types::Requirement;
|
||||
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||
use uv_types::{BuildContext, HashStrategy};
|
||||
|
|
@ -100,16 +100,7 @@ impl<'a, Context: BuildContext> ExtrasResolver<'a, Context> {
|
|||
let archive = database
|
||||
.get_or_build_wheel_metadata(&dist, hasher.get(&dist))
|
||||
.await
|
||||
.map_err(|err| match dist {
|
||||
Dist::Built(built) => Error::Download(Box::new(built), err),
|
||||
Dist::Source(source) => {
|
||||
if source.is_local() {
|
||||
Error::Build(Box::new(source), err)
|
||||
} else {
|
||||
Error::DownloadAndBuild(Box::new(source), err)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
.map_err(|err| Error::from_dist(dist, err))?;
|
||||
|
||||
let metadata = archive.metadata.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use uv_distribution_types::{BuiltDist, Dist, GitSourceDist, SourceDist};
|
||||
use uv_git::GitUrl;
|
||||
use uv_pypi_types::{Requirement, RequirementSource};
|
||||
|
||||
pub use crate::extras::*;
|
||||
pub use crate::lookahead::*;
|
||||
pub use crate::source_tree::*;
|
||||
pub use crate::sources::*;
|
||||
pub use crate::specification::*;
|
||||
pub use crate::unnamed::*;
|
||||
use uv_distribution_types::{BuiltDist, DerivationChain, Dist, GitSourceDist, SourceDist};
|
||||
use uv_git::GitUrl;
|
||||
use uv_pypi_types::{Requirement, RequirementSource};
|
||||
|
||||
mod extras;
|
||||
mod lookahead;
|
||||
|
|
@ -20,13 +19,25 @@ pub mod upgrade;
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Failed to download `{0}`")]
|
||||
Download(Box<BuiltDist>, #[source] uv_distribution::Error),
|
||||
Download(
|
||||
Box<BuiltDist>,
|
||||
DerivationChain,
|
||||
#[source] uv_distribution::Error,
|
||||
),
|
||||
|
||||
#[error("Failed to download and build `{0}`")]
|
||||
DownloadAndBuild(Box<SourceDist>, #[source] uv_distribution::Error),
|
||||
DownloadAndBuild(
|
||||
Box<SourceDist>,
|
||||
DerivationChain,
|
||||
#[source] uv_distribution::Error,
|
||||
),
|
||||
|
||||
#[error("Failed to build `{0}`")]
|
||||
Build(Box<SourceDist>, #[source] uv_distribution::Error),
|
||||
Build(
|
||||
Box<SourceDist>,
|
||||
DerivationChain,
|
||||
#[source] uv_distribution::Error,
|
||||
),
|
||||
|
||||
#[error(transparent)]
|
||||
Distribution(#[from] uv_distribution::Error),
|
||||
|
|
@ -38,6 +49,22 @@ pub enum Error {
|
|||
WheelFilename(#[from] uv_distribution_filename::WheelFilenameError),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Create an [`Error`] from a distribution error.
|
||||
pub(crate) fn from_dist(dist: Dist, cause: uv_distribution::Error) -> Self {
|
||||
match dist {
|
||||
Dist::Built(dist) => Self::Download(Box::new(dist), DerivationChain::default(), cause),
|
||||
Dist::Source(dist) => {
|
||||
if dist.is_local() {
|
||||
Self::Build(Box::new(dist), DerivationChain::default(), cause)
|
||||
} else {
|
||||
Self::DownloadAndBuild(Box::new(dist), DerivationChain::default(), cause)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [`Requirement`] into a [`Dist`], if it is a direct URL.
|
||||
pub(crate) fn required_dist(
|
||||
requirement: &Requirement,
|
||||
|
|
|
|||
|
|
@ -174,16 +174,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
|||
.database
|
||||
.get_or_build_wheel_metadata(&dist, self.hasher.get(&dist))
|
||||
.await
|
||||
.map_err(|err| match dist {
|
||||
Dist::Built(built) => Error::Download(Box::new(built), err),
|
||||
Dist::Source(source) => {
|
||||
if source.is_local() {
|
||||
Error::Build(Box::new(source), err)
|
||||
} else {
|
||||
Error::DownloadAndBuild(Box::new(source), err)
|
||||
}
|
||||
}
|
||||
})?;
|
||||
.map_err(|err| Error::from_dist(dist, err))?;
|
||||
|
||||
let metadata = archive.metadata.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ use rustc_hash::FxHashMap;
|
|||
use tracing::trace;
|
||||
|
||||
use uv_distribution_types::{
|
||||
BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist,
|
||||
BuiltDist, DerivationChain, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist,
|
||||
SourceDist,
|
||||
};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_pep440::{LocalVersionSlice, Version};
|
||||
|
|
@ -97,20 +98,36 @@ pub enum ResolveError {
|
|||
ParsedUrl(#[from] uv_pypi_types::ParsedUrlError),
|
||||
|
||||
#[error("Failed to download `{0}`")]
|
||||
Download(Box<BuiltDist>, #[source] Arc<uv_distribution::Error>),
|
||||
Download(
|
||||
Box<BuiltDist>,
|
||||
DerivationChain,
|
||||
#[source] Arc<uv_distribution::Error>,
|
||||
),
|
||||
|
||||
#[error("Failed to download and build `{0}`")]
|
||||
DownloadAndBuild(Box<SourceDist>, #[source] Arc<uv_distribution::Error>),
|
||||
DownloadAndBuild(
|
||||
Box<SourceDist>,
|
||||
DerivationChain,
|
||||
#[source] Arc<uv_distribution::Error>,
|
||||
),
|
||||
|
||||
#[error("Failed to read `{0}`")]
|
||||
Read(Box<BuiltDist>, #[source] Arc<uv_distribution::Error>),
|
||||
Read(
|
||||
Box<BuiltDist>,
|
||||
DerivationChain,
|
||||
#[source] Arc<uv_distribution::Error>,
|
||||
),
|
||||
|
||||
// TODO(zanieb): Use `thiserror` in `InstalledDist` so we can avoid chaining `anyhow`
|
||||
#[error("Failed to read metadata from installed package `{0}`")]
|
||||
ReadInstalled(Box<InstalledDist>, #[source] anyhow::Error),
|
||||
ReadInstalled(Box<InstalledDist>, DerivationChain, #[source] anyhow::Error),
|
||||
|
||||
#[error("Failed to build `{0}`")]
|
||||
Build(Box<SourceDist>, #[source] Arc<uv_distribution::Error>),
|
||||
Build(
|
||||
Box<SourceDist>,
|
||||
DerivationChain,
|
||||
#[source] Arc<uv_distribution::Error>,
|
||||
),
|
||||
|
||||
#[error(transparent)]
|
||||
NoSolution(#[from] NoSolutionError),
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ pub use resolution::{
|
|||
};
|
||||
pub use resolution_mode::ResolutionMode;
|
||||
pub use resolver::{
|
||||
BuildId, DefaultResolverProvider, InMemoryIndex, MetadataResponse, PackageVersionsResult,
|
||||
Reporter as ResolverReporter, Resolver, ResolverEnvironment, ResolverProvider,
|
||||
VersionsResponse, WheelMetadataResult,
|
||||
BuildId, DefaultResolverProvider, DerivationChainBuilder, InMemoryIndex, MetadataResponse,
|
||||
PackageVersionsResult, Reporter as ResolverReporter, Resolver, ResolverEnvironment,
|
||||
ResolverProvider, VersionsResponse, WheelMetadataResult,
|
||||
};
|
||||
pub use version_map::VersionMap;
|
||||
pub use yanks::AllowedYanks;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use petgraph::Direction;
|
||||
use pubgrub::{Kind, SelectedDependencies, State};
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use uv_distribution_types::{
|
||||
DerivationChain, DerivationStep, DistRef, Name, Node, Resolution, ResolvedDist,
|
||||
};
|
||||
use uv_pep440::Version;
|
||||
|
||||
use crate::dependency_provider::UvDependencyProvider;
|
||||
use crate::pubgrub::PubGrubPackage;
|
||||
|
||||
/// A chain of derivation steps from the root package to the current package, to explain why a
|
||||
/// package is included in the resolution.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct DerivationChainBuilder;
|
||||
|
||||
impl DerivationChainBuilder {
|
||||
/// Compute a [`DerivationChain`] from a resolution graph.
|
||||
///
|
||||
/// This is used to construct a derivation chain upon install failure in the `uv pip` context,
|
||||
/// where we don't have a lockfile describing the resolution.
|
||||
pub fn from_resolution(
|
||||
resolution: &Resolution,
|
||||
target: DistRef<'_>,
|
||||
) -> Option<DerivationChain> {
|
||||
// Find the target distribution in the resolution graph.
|
||||
let target = resolution.graph().node_indices().find(|node| {
|
||||
let Node::Dist {
|
||||
dist: ResolvedDist::Installable { dist, .. },
|
||||
..
|
||||
} = &resolution.graph()[*node]
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
target == dist.as_ref()
|
||||
})?;
|
||||
|
||||
// Perform a BFS to find the shortest path to the root.
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back((target, Vec::new()));
|
||||
|
||||
// TODO(charlie): Consider respecting markers here.
|
||||
let mut seen = FxHashSet::default();
|
||||
while let Some((node, mut path)) = queue.pop_front() {
|
||||
if !seen.insert(node) {
|
||||
continue;
|
||||
}
|
||||
match &resolution.graph()[node] {
|
||||
Node::Root => {
|
||||
path.reverse();
|
||||
path.pop();
|
||||
return Some(DerivationChain::from_iter(path));
|
||||
}
|
||||
Node::Dist { dist, .. } => {
|
||||
path.push(DerivationStep::new(
|
||||
dist.name().clone(),
|
||||
dist.version().clone(),
|
||||
));
|
||||
for neighbor in resolution
|
||||
.graph()
|
||||
.neighbors_directed(node, Direction::Incoming)
|
||||
{
|
||||
queue.push_back((neighbor, path.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Compute a [`DerivationChain`] from the current PubGrub state.
|
||||
///
|
||||
/// This is used to construct a derivation chain upon resolution failure.
|
||||
pub(crate) fn from_state(
|
||||
package: &PubGrubPackage,
|
||||
version: &Version,
|
||||
state: &State<UvDependencyProvider>,
|
||||
) -> Option<DerivationChain> {
|
||||
/// Find a path from the current package to the root package.
|
||||
fn find_path(
|
||||
package: &PubGrubPackage,
|
||||
version: &Version,
|
||||
state: &State<UvDependencyProvider>,
|
||||
solution: &SelectedDependencies<UvDependencyProvider>,
|
||||
path: &mut Vec<DerivationStep>,
|
||||
) -> bool {
|
||||
// Retrieve the incompatiblies for the current package.
|
||||
let Some(incompats) = state.incompatibilities.get(package) else {
|
||||
return false;
|
||||
};
|
||||
for index in incompats {
|
||||
let incompat = &state.incompatibility_store[*index];
|
||||
|
||||
// Find a dependency from a package to the current package.
|
||||
if let Kind::FromDependencyOf(p1, _v1, p2, v2) = &incompat.kind {
|
||||
if p2 == package && v2.contains(version) {
|
||||
if let Some(version) = solution.get(p1) {
|
||||
if let Some(name) = p1.name() {
|
||||
// Add to the current path.
|
||||
path.push(DerivationStep::new(name.clone(), version.clone()));
|
||||
|
||||
// Recursively search the next package.
|
||||
if find_path(p1, version, state, solution, path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Backtrack if the path didn't lead to the root.
|
||||
path.pop();
|
||||
} else {
|
||||
// If we've reached the root, return.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
let solution = state.partial_solution.extract_solution();
|
||||
let path = {
|
||||
let mut path = vec![];
|
||||
if !find_path(package, version, state, &solution, &mut path) {
|
||||
return None;
|
||||
}
|
||||
path.reverse();
|
||||
path.dedup();
|
||||
path
|
||||
};
|
||||
|
||||
Some(path.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,9 @@ pub(crate) use urls::Urls;
|
|||
use uv_configuration::{Constraints, Overrides};
|
||||
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
|
||||
use uv_distribution_types::{
|
||||
BuiltDist, CompatibleDist, Dist, DistributionMetadata, IncompatibleDist, IncompatibleSource,
|
||||
IncompatibleWheel, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist,
|
||||
PythonRequirementKind, RemoteSource, ResolvedDist, ResolvedDistRef, SourceDist,
|
||||
BuiltDist, CompatibleDist, DerivationChain, Dist, DistributionMetadata, IncompatibleDist,
|
||||
IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexUrl,
|
||||
InstalledDist, PythonRequirementKind, RemoteSource, ResolvedDist, ResolvedDistRef, SourceDist,
|
||||
VersionOrUrlRef,
|
||||
};
|
||||
use uv_git::GitResolver;
|
||||
|
|
@ -62,6 +62,8 @@ pub(crate) use crate::resolver::availability::{
|
|||
IncompletePackage, ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion,
|
||||
};
|
||||
use crate::resolver::batch_prefetch::BatchPrefetcher;
|
||||
pub use crate::resolver::derivation::DerivationChainBuilder;
|
||||
|
||||
use crate::resolver::groups::Groups;
|
||||
pub use crate::resolver::index::InMemoryIndex;
|
||||
use crate::resolver::indexes::Indexes;
|
||||
|
|
@ -76,6 +78,7 @@ use crate::{marker, DependencyMode, Exclusions, FlatIndex, Options, ResolutionMo
|
|||
|
||||
mod availability;
|
||||
mod batch_prefetch;
|
||||
mod derivation;
|
||||
mod environment;
|
||||
mod fork_map;
|
||||
mod groups;
|
||||
|
|
@ -387,6 +390,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
resolutions.push(resolution);
|
||||
continue 'FORK;
|
||||
};
|
||||
|
||||
state.next = highest_priority_pkg;
|
||||
|
||||
let url = state.next.name().and_then(|name| state.fork_urls.get(name));
|
||||
|
|
@ -520,9 +524,12 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
&state.fork_urls,
|
||||
&state.env,
|
||||
&state.python_requirement,
|
||||
&state.pubgrub,
|
||||
)?;
|
||||
match forked_deps {
|
||||
ForkedDependencies::Unavailable(reason) => {
|
||||
// Then here, if we get a reason that we consider unrecoverable, we should
|
||||
// show the derivation chain.
|
||||
state
|
||||
.pubgrub
|
||||
.add_incompatibility(Incompatibility::custom_version(
|
||||
|
|
@ -945,25 +952,33 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
unreachable!("`requires-python` is only known upfront for registry distributions")
|
||||
}
|
||||
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.
|
||||
let chain = DerivationChain::default();
|
||||
return Err(match &**dist {
|
||||
Dist::Built(built_dist @ BuiltDist::Path(_)) => {
|
||||
ResolveError::Read(Box::new(built_dist.clone()), (*err).clone())
|
||||
ResolveError::Read(Box::new(built_dist.clone()), chain, (*err).clone())
|
||||
}
|
||||
Dist::Source(source_dist @ SourceDist::Path(_)) => {
|
||||
ResolveError::Build(Box::new(source_dist.clone()), (*err).clone())
|
||||
ResolveError::Build(Box::new(source_dist.clone()), chain, (*err).clone())
|
||||
}
|
||||
Dist::Source(source_dist @ SourceDist::Directory(_)) => {
|
||||
ResolveError::Build(Box::new(source_dist.clone()), (*err).clone())
|
||||
ResolveError::Build(Box::new(source_dist.clone()), chain, (*err).clone())
|
||||
}
|
||||
Dist::Built(built_dist) => {
|
||||
ResolveError::Download(Box::new(built_dist.clone()), (*err).clone())
|
||||
ResolveError::Download(Box::new(built_dist.clone()), chain, (*err).clone())
|
||||
}
|
||||
Dist::Source(source_dist) => {
|
||||
if source_dist.is_local() {
|
||||
ResolveError::Build(Box::new(source_dist.clone()), (*err).clone())
|
||||
ResolveError::Build(
|
||||
Box::new(source_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
)
|
||||
} else {
|
||||
ResolveError::DownloadAndBuild(
|
||||
Box::new(source_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1197,8 +1212,16 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
fork_urls: &ForkUrls,
|
||||
env: &ResolverEnvironment,
|
||||
python_requirement: &PythonRequirement,
|
||||
pubgrub: &State<UvDependencyProvider>,
|
||||
) -> Result<ForkedDependencies, ResolveError> {
|
||||
let result = self.get_dependencies(package, version, fork_urls, env, python_requirement);
|
||||
let result = self.get_dependencies(
|
||||
package,
|
||||
version,
|
||||
fork_urls,
|
||||
env,
|
||||
python_requirement,
|
||||
pubgrub,
|
||||
);
|
||||
if env.marker_environment().is_some() {
|
||||
result.map(|deps| match deps {
|
||||
Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
|
||||
|
|
@ -1220,6 +1243,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
fork_urls: &ForkUrls,
|
||||
env: &ResolverEnvironment,
|
||||
python_requirement: &PythonRequirement,
|
||||
pubgrub: &State<UvDependencyProvider>,
|
||||
) -> Result<Dependencies, ResolveError> {
|
||||
let url = package.name().and_then(|name| fork_urls.get(name));
|
||||
let dependencies = match &**package {
|
||||
|
|
@ -1357,28 +1381,42 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
));
|
||||
}
|
||||
MetadataResponse::Error(dist, err) => {
|
||||
let chain = DerivationChainBuilder::from_state(package, version, pubgrub)
|
||||
.unwrap_or_default();
|
||||
return Err(match &**dist {
|
||||
Dist::Built(built_dist @ BuiltDist::Path(_)) => {
|
||||
ResolveError::Read(Box::new(built_dist.clone()), (*err).clone())
|
||||
}
|
||||
Dist::Source(source_dist @ SourceDist::Path(_)) => {
|
||||
ResolveError::Build(Box::new(source_dist.clone()), (*err).clone())
|
||||
}
|
||||
Dist::Built(built_dist @ BuiltDist::Path(_)) => ResolveError::Read(
|
||||
Box::new(built_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
),
|
||||
Dist::Source(source_dist @ SourceDist::Path(_)) => ResolveError::Build(
|
||||
Box::new(source_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
),
|
||||
Dist::Source(source_dist @ SourceDist::Directory(_)) => {
|
||||
ResolveError::Build(Box::new(source_dist.clone()), (*err).clone())
|
||||
}
|
||||
Dist::Built(built_dist) => {
|
||||
ResolveError::Download(Box::new(built_dist.clone()), (*err).clone())
|
||||
ResolveError::Build(
|
||||
Box::new(source_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
)
|
||||
}
|
||||
Dist::Built(built_dist) => ResolveError::Download(
|
||||
Box::new(built_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
),
|
||||
Dist::Source(source_dist) => {
|
||||
if source_dist.is_local() {
|
||||
ResolveError::Build(
|
||||
Box::new(source_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
)
|
||||
} else {
|
||||
ResolveError::DownloadAndBuild(
|
||||
Box::new(source_dist.clone()),
|
||||
chain,
|
||||
(*err).clone(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1848,9 +1886,14 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
}
|
||||
|
||||
Request::Installed(dist) => {
|
||||
let metadata = dist
|
||||
.metadata()
|
||||
.map_err(|err| ResolveError::ReadInstalled(Box::new(dist.clone()), err))?;
|
||||
// TODO(charlie): This should be return a `MetadataResponse`.
|
||||
let metadata = dist.metadata().map_err(|err| {
|
||||
ResolveError::ReadInstalled(
|
||||
Box::new(dist.clone()),
|
||||
DerivationChain::default(),
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
Ok(Some(Response::Installed { dist, metadata }))
|
||||
}
|
||||
|
||||
|
|
@ -1964,7 +2007,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
}
|
||||
ResolvedDist::Installed { dist } => {
|
||||
let metadata = dist.metadata().map_err(|err| {
|
||||
ResolveError::ReadInstalled(Box::new(dist.clone()), err)
|
||||
ResolveError::ReadInstalled(
|
||||
Box::new(dist.clone()),
|
||||
DerivationChain::default(),
|
||||
err,
|
||||
)
|
||||
})?;
|
||||
Response::Installed { dist, metadata }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::sync::LazyLock;
|
|||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use uv_distribution_types::{BuiltDist, Name, SourceDist};
|
||||
use uv_distribution_types::{BuiltDist, DerivationChain, Name, SourceDist};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
use crate::commands::pip;
|
||||
|
|
@ -42,7 +42,7 @@ impl OperationDiagnostic {
|
|||
pub(crate) fn with_hint(hint: String) -> Self {
|
||||
Self {
|
||||
hint: Some(hint),
|
||||
context: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,8 +50,8 @@ impl OperationDiagnostic {
|
|||
#[must_use]
|
||||
pub(crate) fn with_context(context: &'static str) -> Self {
|
||||
Self {
|
||||
hint: None,
|
||||
context: Some(context),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,47 +74,70 @@ impl OperationDiagnostic {
|
|||
}
|
||||
pip::operations::Error::Resolve(uv_resolver::ResolveError::DownloadAndBuild(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
download_and_build(dist, Box::new(err));
|
||||
download_and_build(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Resolve(uv_resolver::ResolveError::Download(dist, err)) => {
|
||||
download(dist, Box::new(err));
|
||||
pip::operations::Error::Resolve(uv_resolver::ResolveError::Download(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
download(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Resolve(uv_resolver::ResolveError::Build(dist, err)) => {
|
||||
build(dist, Box::new(err));
|
||||
pip::operations::Error::Resolve(uv_resolver::ResolveError::Build(dist, chain, err)) => {
|
||||
build(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Requirements(uv_requirements::Error::DownloadAndBuild(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
download_and_build(dist, Box::new(err));
|
||||
download_and_build(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Requirements(uv_requirements::Error::Download(dist, err)) => {
|
||||
download(dist, Box::new(err));
|
||||
pip::operations::Error::Requirements(uv_requirements::Error::Download(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
download(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Requirements(uv_requirements::Error::Build(dist, err)) => {
|
||||
build(dist, Box::new(err));
|
||||
pip::operations::Error::Requirements(uv_requirements::Error::Build(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
build(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Prepare(uv_installer::PrepareError::DownloadAndBuild(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
download_and_build(dist, Box::new(err));
|
||||
download_and_build(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Prepare(uv_installer::PrepareError::Download(dist, err)) => {
|
||||
download(dist, Box::new(err));
|
||||
pip::operations::Error::Prepare(uv_installer::PrepareError::Download(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
download(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Prepare(uv_installer::PrepareError::Build(dist, err)) => {
|
||||
build(dist, Box::new(err));
|
||||
pip::operations::Error::Prepare(uv_installer::PrepareError::Build(
|
||||
dist,
|
||||
chain,
|
||||
err,
|
||||
)) => {
|
||||
build(dist, &chain, Box::new(err));
|
||||
None
|
||||
}
|
||||
pip::operations::Error::Requirements(err) => {
|
||||
|
|
@ -133,7 +156,7 @@ impl OperationDiagnostic {
|
|||
}
|
||||
|
||||
/// Render a remote source distribution build failure with a help message.
|
||||
pub(crate) fn download_and_build(sdist: Box<SourceDist>, cause: Error) {
|
||||
pub(crate) fn download_and_build(sdist: Box<SourceDist>, chain: &DerivationChain, cause: Error) {
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("Failed to download and build `{sdist}`")]
|
||||
#[diagnostic()]
|
||||
|
|
@ -146,14 +169,32 @@ pub(crate) fn download_and_build(sdist: Box<SourceDist>, cause: Error) {
|
|||
}
|
||||
|
||||
let report = miette::Report::new(Diagnostic {
|
||||
help: SUGGESTIONS.get(sdist.name()).map(|suggestion| {
|
||||
format!(
|
||||
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
|
||||
sdist.name().cyan(),
|
||||
suggestion.cyan(),
|
||||
suggestion.cyan(),
|
||||
)
|
||||
}),
|
||||
help: SUGGESTIONS
|
||||
.get(sdist.name())
|
||||
.map(|suggestion| {
|
||||
format!(
|
||||
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
|
||||
sdist.name().cyan(),
|
||||
suggestion.cyan(),
|
||||
suggestion.cyan(),
|
||||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
if chain.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut message = format!("`{}` was included because", sdist.name().cyan());
|
||||
for (i, step) in chain.iter().enumerate() {
|
||||
if i == 0 {
|
||||
message = format!("{message} `{}` depends on", step.cyan());
|
||||
} else {
|
||||
message = format!("{message} `{}` which depends on", step.cyan());
|
||||
}
|
||||
}
|
||||
message = format!("{message} `{}`", sdist.name().cyan());
|
||||
Some(message)
|
||||
}
|
||||
}),
|
||||
sdist,
|
||||
cause,
|
||||
});
|
||||
|
|
@ -161,7 +202,7 @@ pub(crate) fn download_and_build(sdist: Box<SourceDist>, cause: Error) {
|
|||
}
|
||||
|
||||
/// Render a remote binary distribution download failure with a help message.
|
||||
pub(crate) fn download(sdist: Box<BuiltDist>, cause: Error) {
|
||||
pub(crate) fn download(sdist: Box<BuiltDist>, chain: &DerivationChain, cause: Error) {
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("Failed to download `{sdist}`")]
|
||||
#[diagnostic()]
|
||||
|
|
@ -174,14 +215,32 @@ pub(crate) fn download(sdist: Box<BuiltDist>, cause: Error) {
|
|||
}
|
||||
|
||||
let report = miette::Report::new(Diagnostic {
|
||||
help: SUGGESTIONS.get(sdist.name()).map(|suggestion| {
|
||||
format!(
|
||||
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
|
||||
sdist.name().cyan(),
|
||||
suggestion.cyan(),
|
||||
suggestion.cyan(),
|
||||
)
|
||||
}),
|
||||
help: SUGGESTIONS
|
||||
.get(sdist.name())
|
||||
.map(|suggestion| {
|
||||
format!(
|
||||
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
|
||||
sdist.name().cyan(),
|
||||
suggestion.cyan(),
|
||||
suggestion.cyan(),
|
||||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
if chain.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut message = format!("`{}` was included because", sdist.name().cyan());
|
||||
for (i, step) in chain.iter().enumerate() {
|
||||
if i == 0 {
|
||||
message = format!("{message} `{}` depends on", step.cyan());
|
||||
} else {
|
||||
message = format!("{message} `{}` which depends on", step.cyan());
|
||||
}
|
||||
}
|
||||
message = format!("{message} `{}`", sdist.name().cyan());
|
||||
Some(message)
|
||||
}
|
||||
}),
|
||||
sdist,
|
||||
cause,
|
||||
});
|
||||
|
|
@ -189,7 +248,7 @@ pub(crate) fn download(sdist: Box<BuiltDist>, cause: Error) {
|
|||
}
|
||||
|
||||
/// Render a local source distribution build failure with a help message.
|
||||
pub(crate) fn build(sdist: Box<SourceDist>, cause: Error) {
|
||||
pub(crate) fn build(sdist: Box<SourceDist>, chain: &DerivationChain, cause: Error) {
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("Failed to build `{sdist}`")]
|
||||
#[diagnostic()]
|
||||
|
|
@ -202,14 +261,32 @@ pub(crate) fn build(sdist: Box<SourceDist>, cause: Error) {
|
|||
}
|
||||
|
||||
let report = miette::Report::new(Diagnostic {
|
||||
help: SUGGESTIONS.get(sdist.name()).map(|suggestion| {
|
||||
format!(
|
||||
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
|
||||
sdist.name().cyan(),
|
||||
suggestion.cyan(),
|
||||
suggestion.cyan(),
|
||||
)
|
||||
}),
|
||||
help: SUGGESTIONS
|
||||
.get(sdist.name())
|
||||
.map(|suggestion| {
|
||||
format!(
|
||||
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
|
||||
sdist.name().cyan(),
|
||||
suggestion.cyan(),
|
||||
suggestion.cyan(),
|
||||
)
|
||||
})
|
||||
.or_else(|| {
|
||||
if chain.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut message = format!("`{}` was included because", sdist.name().cyan());
|
||||
for (i, step) in chain.iter().enumerate() {
|
||||
if i == 0 {
|
||||
message = format!("{message} `{}` depends on", step.cyan());
|
||||
} else {
|
||||
message = format!("{message} `{}` which depends on", step.cyan());
|
||||
}
|
||||
}
|
||||
message = format!("{message} `{}`", sdist.name().cyan());
|
||||
Some(message)
|
||||
}
|
||||
}),
|
||||
sdist,
|
||||
cause,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ pub(crate) async fn pip_install(
|
|||
)
|
||||
.await
|
||||
{
|
||||
Ok(resolution) => Resolution::from(resolution),
|
||||
Ok(graph) => Resolution::from(graph),
|
||||
Err(err) => {
|
||||
return diagnostics::OperationDiagnostic::default()
|
||||
.report(err)
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ use uv_requirements::{
|
|||
SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::{
|
||||
DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, Preference,
|
||||
Preferences, PythonRequirement, Resolver, ResolverEnvironment, ResolverOutput,
|
||||
DependencyMode, DerivationChainBuilder, Exclusions, FlatIndex, InMemoryIndex, Manifest,
|
||||
Options, Preference, Preferences, PythonRequirement, Resolver, ResolverEnvironment,
|
||||
ResolverOutput,
|
||||
};
|
||||
use uv_types::{HashStrategy, InFlight, InstalledPackagesProvider};
|
||||
use uv_warnings::warn_user;
|
||||
|
|
@ -459,7 +460,37 @@ pub(crate) async fn install(
|
|||
)
|
||||
.with_reporter(PrepareReporter::from(printer).with_length(remote.len() as u64));
|
||||
|
||||
let wheels = preparer.prepare(remote.clone(), in_flight).await?;
|
||||
let wheels = preparer
|
||||
.prepare(remote.clone(), in_flight)
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.map_err(|err| match err {
|
||||
// Attach resolution context to the error.
|
||||
Error::Prepare(uv_installer::PrepareError::Download(dist, chain, err)) => {
|
||||
debug_assert!(chain.is_empty());
|
||||
let chain =
|
||||
DerivationChainBuilder::from_resolution(resolution, (&*dist).into())
|
||||
.unwrap_or_default();
|
||||
Error::Prepare(uv_installer::PrepareError::Download(dist, chain, err))
|
||||
}
|
||||
Error::Prepare(uv_installer::PrepareError::Build(dist, chain, err)) => {
|
||||
debug_assert!(chain.is_empty());
|
||||
let chain =
|
||||
DerivationChainBuilder::from_resolution(resolution, (&*dist).into())
|
||||
.unwrap_or_default();
|
||||
Error::Prepare(uv_installer::PrepareError::Build(dist, chain, err))
|
||||
}
|
||||
Error::Prepare(uv_installer::PrepareError::DownloadAndBuild(dist, chain, err)) => {
|
||||
debug_assert!(chain.is_empty());
|
||||
let chain =
|
||||
DerivationChainBuilder::from_resolution(resolution, (&*dist).into())
|
||||
.unwrap_or_default();
|
||||
Error::Prepare(uv_installer::PrepareError::DownloadAndBuild(
|
||||
dist, chain, err,
|
||||
))
|
||||
}
|
||||
_ => err,
|
||||
})?;
|
||||
|
||||
logger.on_prepare(wheels.len(), start, printer)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ use uv_configuration::{
|
|||
ExtrasSpecification, HashCheckingMode, InstallOptions, LowerBound, TrustedHost,
|
||||
};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_distribution_types::{DirectorySourceDist, Dist, Index, ResolvedDist, SourceDist};
|
||||
use uv_distribution_types::{
|
||||
DirectorySourceDist, Dist, Index, Resolution, ResolvedDist, SourceDist,
|
||||
};
|
||||
use uv_installer::SitePackages;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_pep508::{MarkerTree, Requirement, VersionOrUrl};
|
||||
|
|
@ -467,9 +469,7 @@ pub(super) async fn do_sync(
|
|||
}
|
||||
|
||||
/// Filter out any virtual workspace members.
|
||||
fn apply_no_virtual_project(
|
||||
resolution: uv_distribution_types::Resolution,
|
||||
) -> uv_distribution_types::Resolution {
|
||||
fn apply_no_virtual_project(resolution: Resolution) -> Resolution {
|
||||
resolution.filter(|dist| {
|
||||
let ResolvedDist::Installable { dist, .. } = dist else {
|
||||
return true;
|
||||
|
|
@ -488,10 +488,7 @@ fn apply_no_virtual_project(
|
|||
}
|
||||
|
||||
/// If necessary, convert any editable requirements to non-editable.
|
||||
fn apply_editable_mode(
|
||||
resolution: uv_distribution_types::Resolution,
|
||||
editable: EditableMode,
|
||||
) -> uv_distribution_types::Resolution {
|
||||
fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolution {
|
||||
match editable {
|
||||
// No modifications are necessary for editable mode; retain any editable distributions.
|
||||
EditableMode::Editable => resolution,
|
||||
|
|
|
|||
|
|
@ -5573,6 +5573,8 @@ fn fail_to_add_revert_project() -> Result<()> {
|
|||
exec(code, locals())
|
||||
File "<string>", line 1, in <module>
|
||||
ZeroDivisionError: division by zero
|
||||
|
||||
help: `child` was included because `parent==0.1.0` depends on `child`
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||
|
|
@ -5680,6 +5682,8 @@ fn fail_to_edit_revert_project() -> Result<()> {
|
|||
exec(code, locals())
|
||||
File "<string>", line 1, in <module>
|
||||
ZeroDivisionError: division by zero
|
||||
|
||||
help: `child` was included because `parent==0.1.0` depends on `child`
|
||||
"###);
|
||||
|
||||
let pyproject_toml = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
|
||||
|
|
|
|||
|
|
@ -7357,6 +7357,7 @@ fn lock_invalid_hash() -> Result<()> {
|
|||
|
||||
Computed:
|
||||
sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
|
||||
help: `idna` was included because `project==0.1.0` depends on `anyio==3.7.0` which depends on `idna`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
|
|
@ -8195,6 +8196,7 @@ fn lock_redact_https() -> Result<()> {
|
|||
× Failed to download `iniconfig==2.0.0`
|
||||
├─▶ Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl`
|
||||
╰─▶ HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
||||
help: `iniconfig` was included because `foo==0.1.0` depends on `iniconfig`
|
||||
"###);
|
||||
|
||||
// Installing from the lockfile should fail without an index.
|
||||
|
|
@ -8207,6 +8209,7 @@ fn lock_redact_https() -> Result<()> {
|
|||
× Failed to download `iniconfig==2.0.0`
|
||||
├─▶ Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl`
|
||||
╰─▶ HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
||||
help: `iniconfig` was included because `foo==0.1.0` depends on `iniconfig`
|
||||
"###);
|
||||
|
||||
// Installing from the lockfile should succeed when credentials are included on the command-line.
|
||||
|
|
@ -8246,6 +8249,7 @@ fn lock_redact_https() -> Result<()> {
|
|||
× Failed to download `iniconfig==2.0.0`
|
||||
├─▶ Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl`
|
||||
╰─▶ HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
||||
help: `iniconfig` was included because `foo==0.1.0` depends on `iniconfig`
|
||||
"###);
|
||||
|
||||
// Installing with credentials from with `UV_INDEX_URL` should succeed.
|
||||
|
|
@ -19826,3 +19830,249 @@ fn lock_dynamic_version() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_derivation_chain() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["wsgiref"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.lock(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `wsgiref`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_derivation_chain_extra() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
optional-dependencies = { wsgi = ["wsgiref"] }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.lock(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `wsgiref`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_derivation_chain_group() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
wsgi = ["wsgiref"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.lock(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `wsgiref`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_derivation_chain_extended() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { path = "child" }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let child = context.temp_dir.child("child");
|
||||
child.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["wsgiref"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.lock(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `child==0.1.0` which depends on `wsgiref`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7319,3 +7319,62 @@ fn sklearn() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_derivation_chain() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["wsgiref"]
|
||||
"#
|
||||
})?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.pip_install()
|
||||
.arg("-e")
|
||||
.arg("."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `wsgiref`
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -700,6 +700,8 @@ fn sync_build_isolation_package() -> Result<()> {
|
|||
Traceback (most recent call last):
|
||||
File "<string>", line 8, in <module>
|
||||
ModuleNotFoundError: No module named 'hatchling'
|
||||
|
||||
help: `source-distribution` was included because `project==0.1.0` depends on `source-distribution`
|
||||
"###);
|
||||
|
||||
// Install `hatchling` for `source-distribution`.
|
||||
|
|
@ -789,6 +791,8 @@ fn sync_build_isolation_extra() -> Result<()> {
|
|||
Traceback (most recent call last):
|
||||
File "<string>", line 8, in <module>
|
||||
ModuleNotFoundError: No module named 'hatchling'
|
||||
|
||||
help: `source-distribution` was included because `project==0.1.0` depends on `source-distribution`
|
||||
"###);
|
||||
|
||||
// Running `uv sync` with `--all-extras` should also fail.
|
||||
|
|
@ -806,6 +810,8 @@ fn sync_build_isolation_extra() -> Result<()> {
|
|||
Traceback (most recent call last):
|
||||
File "<string>", line 8, in <module>
|
||||
ModuleNotFoundError: No module named 'hatchling'
|
||||
|
||||
help: `source-distribution` was included because `project==0.1.0` depends on `source-distribution`
|
||||
"###);
|
||||
|
||||
// Install the build dependencies.
|
||||
|
|
@ -4270,3 +4276,196 @@ fn sync_all_groups() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_derivation_chain() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["wsgiref"]
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "wsgiref"
|
||||
version = "0.1.2"
|
||||
dependencies = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.sync(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `wsgiref`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_derivation_chain_extra() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
optional-dependencies = { wsgi = ["wsgiref"] }
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "wsgiref"
|
||||
version = "0.1.2"
|
||||
dependencies = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `wsgiref`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_derivation_chain_group() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
wsgi = ["wsgiref"]
|
||||
|
||||
[[tool.uv.dependency-metadata]]
|
||||
name = "wsgiref"
|
||||
version = "0.1.2"
|
||||
dependencies = []
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let filters = context
|
||||
.filters()
|
||||
.into_iter()
|
||||
.chain([
|
||||
(r"exit code: 1", "exit status: 1"),
|
||||
(r"/.*/src", "/[TMP]/src"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
× Failed to download and build `wsgiref==0.1.2`
|
||||
╰─▶ Build backend failed to determine requirements with `build_wheel()` (exit status: 1)
|
||||
|
||||
[stderr]
|
||||
Traceback (most recent call last):
|
||||
File "<string>", line 14, in <module>
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 325, in get_requires_for_build_wheel
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 295, in _get_build_requires
|
||||
self.run_setup()
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 487, in run_setup
|
||||
super().run_setup(setup_script=setup_script)
|
||||
File "[CACHE_DIR]/builds-v0/[TMP]/build_meta.py", line 311, in run_setup
|
||||
exec(code, locals())
|
||||
File "<string>", line 5, in <module>
|
||||
File "[CACHE_DIR]/[TMP]/src/ez_setup/__init__.py", line 170
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
|
||||
|
||||
help: `wsgiref` was included because `project==0.1.0` depends on `wsgiref`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue