Consolidate UnusableDependencies into a generic Unavailable incompatibility (#1088)

Requires https://github.com/zanieb/pubgrub/pull/20

In short, `UnusableDependencies` can be generalized into `Unavailable`
which encompasses incompatibilities where a package range which is
unusable for some inherent reason as well as when its dependencies are
unusable. We can eventually use this to track more incompatibilities in
the solver. I made the reason string required because I can't see a case
where we should leave it out.

Additionally, this improves the display of conflicts in the root
requirements.
This commit is contained in:
Zanie Blue 2024-01-24 22:10:44 -06:00 committed by GitHub
parent 091f8e09ff
commit ed1ac640b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 54 additions and 49 deletions

2
Cargo.lock generated
View file

@ -2246,7 +2246,7 @@ dependencies = [
[[package]]
name = "pubgrub"
version = "0.2.1"
source = "git+https://github.com/zanieb/pubgrub?rev=0e02ea9fc8d021fb6a6b9e77b09ade4332068f42#0e02ea9fc8d021fb6a6b9e77b09ade4332068f42"
source = "git+https://github.com/zanieb/pubgrub?rev=7a573e3902ff338abdcf3b87682e4c6d04845f2f#7a573e3902ff338abdcf3b87682e4c6d04845f2f"
dependencies = [
"indexmap 2.1.0",
"log",

View file

@ -53,7 +53,7 @@ owo-colors = { version = "3.5.0" }
petgraph = { version = "0.6.4" }
platform-info = { version = "2.0.2" }
plist = { version = "1.6.0" }
pubgrub = { git = "https://github.com/zanieb/pubgrub", rev = "0e02ea9fc8d021fb6a6b9e77b09ade4332068f42" }
pubgrub = { git = "https://github.com/zanieb/pubgrub", rev = "7a573e3902ff338abdcf3b87682e4c6d04845f2f" }
pyo3 = { version = "0.20.2" }
pyo3-log = { version = "0.9.0"}
pyproject-toml = { version = "0.8.1" }

View file

@ -49,10 +49,10 @@ pub enum ResolveError {
#[error("~= operator requires at least two release segments: {0}")]
InvalidTildeEquals(pep440_rs::VersionSpecifier),
#[error("Conflicting URLs for package `{0}`:\n- {1}\n- {2}")]
#[error("There are conflicting URLs for package `{0}`:\n- {1}\n- {2}")]
ConflictingUrls(PackageName, String, String),
#[error("Conflicting versions for `{0}`: {1}")]
#[error("There are conflicting versions for `{0}`: {1}")]
ConflictingVersions(String, String),
#[error("Package `{0}` attempted to resolve via URL: {1}. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{0} @ {1}` to your dependencies or constraints file.")]

View file

@ -96,32 +96,18 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
}
}
}
External::UnavailableDependencies(package, set) => {
let set = self.simplify_set(set, package);
format!(
"dependencies of {}are unavailable",
Padded::new("", &PackageRange::compatibility(package, &set), " ")
)
}
External::UnusableDependencies(package, set, reason) => {
if let Some(reason) = reason {
if matches!(package, PubGrubPackage::Root(_)) {
format!("{package} dependencies are unusable: {reason}")
} else {
let set = self.simplify_set(set, package);
format!(
"dependencies of {}are unusable: {reason}",
Padded::new("", &PackageRange::compatibility(package, &set), " ")
)
}
} else {
let set = self.simplify_set(set, package);
format!(
"dependencies of {}are unusable",
Padded::new("", &PackageRange::compatibility(package, &set), " ")
)
External::Unavailable(package, set, reason) => match package {
PubGrubPackage::Root(Some(name)) => {
format!("{name} cannot be used because {reason}")
}
}
PubGrubPackage::Root(None) => {
format!("your requirements cannot be used because {reason}")
}
_ => format!(
"{}is unusable because {reason}",
Padded::new("", &PackageRange::compatibility(package, set), " ")
),
},
External::FromDependencyOf(package, package_set, dependency, dependency_set) => {
let package_set = self.simplify_set(package_set, package);
let dependency_set = self.simplify_set(dependency_set, dependency);
@ -403,8 +389,7 @@ impl PubGrubReportFormatter<'_> {
}
}
External::NotRoot(..) => {}
External::UnavailableDependencies(..) => {}
External::UnusableDependencies(..) => {}
External::Unavailable(..) => {}
External::FromDependencyOf(..) => {}
},
DerivationTree::Derived(derived) => {

View file

@ -335,22 +335,30 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
.get_dependencies(package, &version, &mut priorities, request_sink)
.await?
{
Dependencies::Unusable(reason) => {
state.add_incompatibility(Incompatibility::unusable_dependencies(
Dependencies::Unavailable(reason) => {
let message = {
if matches!(package, PubGrubPackage::Root(_)) {
// Including front-matter for the root package is redundant
reason.clone()
} else {
format!("its dependencies are unusable because {reason}")
}
};
state.add_incompatibility(Incompatibility::unavailable(
package.clone(),
version.clone(),
reason.clone(),
message,
));
continue;
}
Dependencies::Known(constraints) if constraints.contains_key(package) => {
Dependencies::Available(constraints) if constraints.contains_key(package) => {
return Err(PubGrubError::SelfDependency {
package: package.clone(),
version: version.clone(),
}
.into());
}
Dependencies::Known(constraints) => constraints,
Dependencies::Available(constraints) => constraints,
};
// Add that package and version if the dependencies are not problematic.
@ -588,7 +596,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
| ResolveError::ConflictingUrls(..)),
) = constraints
{
return Ok(Dependencies::Unusable(Some(err.to_string())));
return Ok(Dependencies::Unavailable(uncapitalize(err.to_string())));
}
let mut constraints = constraints?;
@ -610,10 +618,12 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
);
}
Ok(Dependencies::Known(constraints.into()))
Ok(Dependencies::Available(constraints.into()))
}
PubGrubPackage::Python(_) => Ok(Dependencies::Known(DependencyConstraints::default())),
PubGrubPackage::Python(_) => {
Ok(Dependencies::Available(DependencyConstraints::default()))
}
PubGrubPackage::Package(package_name, extra, url) => {
// Wait for the metadata to be available.
@ -640,7 +650,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
version.clone(),
);
constraints.insert(PubGrubPackage::Python(PubGrubPython::Target), version);
return Ok(Dependencies::Known(constraints));
return Ok(Dependencies::Available(constraints));
}
let entry = self.index.distributions.wait(&package_id).await?;
@ -670,7 +680,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
);
}
Ok(Dependencies::Known(constraints.into()))
Ok(Dependencies::Available(constraints.into()))
}
}
}
@ -877,8 +887,16 @@ enum Response {
/// For each [Package] there is a set of versions allowed as a dependency.
#[derive(Clone)]
enum Dependencies {
/// Package dependencies are not usable
Unusable(Option<String>),
/// Package dependencies are not available.
Unavailable(String),
/// Container for all available package versions.
Known(DependencyConstraints<PubGrubPackage, Range<Version>>),
Available(DependencyConstraints<PubGrubPackage, Range<Version>>),
}
fn uncapitalize<T: AsRef<str>>(string: T) -> String {
let mut chars = string.as_ref().chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_lowercase().chain(chars).collect(),
}
}

View file

@ -1516,7 +1516,8 @@ fn conflicting_repeated_url_dependency_version_mismatch() -> Result<()> {
----- stderr -----
× No solution found when resolving dependencies:
root dependencies are unusable: Conflicting URLs for package `werkzeug`:
your requirements cannot be used because there are conflicting URLs for
package `werkzeug`:
- https://files.pythonhosted.org/packages/bd/24/11c3ea5a7e866bf2d97f0501d0b4b1c9bbeade102bb4b588f0d2919a5212/Werkzeug-2.0.1-py3-none-any.whl
- https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl
"###);
@ -1556,7 +1557,8 @@ fn conflicting_repeated_url_dependency_version_match() -> Result<()> {
----- stderr -----
× No solution found when resolving dependencies:
root dependencies are unusable: Conflicting URLs for package `werkzeug`:
your requirements cannot be used because there are conflicting URLs for
package `werkzeug`:
- git+https://github.com/pallets/werkzeug.git@2.0.0
- https://files.pythonhosted.org/packages/ff/1d/960bb4017c68674a1cb099534840f18d3def3ce44aed12b5ed8b78e0153e/Werkzeug-2.0.0-py3-none-any.whl
"###);
@ -1900,8 +1902,8 @@ dependencies = ["django==5.0b1", "django==5.0a1"]
----- stderr -----
× No solution found when resolving dependencies:
my-project dependencies are unusable: Conflicting versions for `django`:
`django==5.0b1` does not intersect with `django==5.0a1`
my-project cannot be used because there are conflicting versions for
`django`: `django==5.0b1` does not intersect with `django==5.0a1`
"###);
});

View file

@ -1245,7 +1245,7 @@ fn direct_incompatible_versions() -> Result<()> {
----- stderr -----
× No solution found when resolving dependencies:
root dependencies are unusable: Conflicting versions for `albatross`: `albatross==1.0.0` does not intersect with `albatross==2.0.0`
your requirements cannot be used because there are conflicting versions for `albatross`: `albatross==1.0.0` does not intersect with `albatross==2.0.0`
"###);
});