Skip to content

Commit 8e4f468

Browse files
committed
Add an UnusableDependencies incompatibility kind and use for conflicting versions
1 parent a20325f commit 8e4f468

File tree

9 files changed

+123
-16
lines changed

9 files changed

+123
-16
lines changed

crates/puffin-cli/tests/pip_compile.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -666,14 +666,16 @@ fn compile_numpy_py38() -> Result<()> {
666666
.arg("--no-build")
667667
.env("VIRTUAL_ENV", venv.as_os_str())
668668
.current_dir(&temp_dir), @r###"
669-
success: false
670-
exit_code: 2
671-
----- stdout -----
672-
673-
----- stderr -----
674-
error: Failed to build distribution: numpy-1.24.4.tar.gz
675-
Caused by: Building source distributions is disabled
676-
"###);
669+
success: true
670+
exit_code: 0
671+
----- stdout -----
672+
# This file was autogenerated by Puffin v0.0.1 via the following command:
673+
# [BIN_PATH] pip-compile requirements.in --python-version py38 --cache-dir [CACHE_DIR]
674+
numpy==1.24.4
675+
676+
----- stderr -----
677+
Resolved 1 package in [TIME]
678+
"###);
677679
});
678680

679681
Ok(())

crates/puffin-cli/tests/snapshots/pip_compile__compile_unsolvable_requirements.snap

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ info:
66
- pip-compile
77
- pyproject.toml
88
- "--cache-dir"
9-
- /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmpN531dN
9+
- /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmpvGljPp
1010
env:
11-
VIRTUAL_ENV: /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmp99w9dK/.venv
11+
VIRTUAL_ENV: /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmp2rBXR2/.venv
1212
---
1313
success: false
1414
exit_code: 1
1515
----- stdout -----
1616

1717
----- stderr -----
1818
× No solution found when resolving dependencies:
19-
╰─▶ my-project depends on django
19+
╰─▶ my-project dependencies are unusable: `django==5.0b1` does not intersect
20+
with `django==5.0a1`
2021

crates/puffin-resolver/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ pub enum ResolveError {
4444
#[error("Conflicting URLs for package `{0}`: {1} and {2}")]
4545
ConflictingUrls(PackageName, String, String),
4646

47+
#[error("`{0}{1}` does not intersect with `{0}{2}`")]
48+
ConflictingVersions(PubGrubPackage, Range<PubGrubVersion>, Range<PubGrubVersion>),
49+
4750
#[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.")]
4851
DisallowedUrl(PackageName, Url),
4952

crates/puffin-resolver/src/pubgrub/dependencies.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl PubGrubDependencies {
5959

6060
if let Some(entry) = dependencies.get_key_value(&package) {
6161
// Merge the versions.
62-
let version = merge_versions(entry.1, &version);
62+
let version = merge_versions(&package, entry.1, &version)?;
6363

6464
// Merge the package.
6565
if let Some(package) = merge_package(entry.0, &package)? {
@@ -107,7 +107,7 @@ impl PubGrubDependencies {
107107

108108
if let Some(entry) = dependencies.get_key_value(&package) {
109109
// Merge the versions.
110-
let version = merge_versions(entry.1, &version);
110+
let version = merge_versions(&package, entry.1, &version)?;
111111

112112
// Merge the package.
113113
if let Some(package) = merge_package(entry.0, &package)? {
@@ -178,10 +178,20 @@ fn to_pubgrub(
178178

179179
/// Merge two [`PubGrubVersion`] ranges.
180180
fn merge_versions(
181+
package: &PubGrubPackage,
181182
left: &Range<PubGrubVersion>,
182183
right: &Range<PubGrubVersion>,
183-
) -> Range<PubGrubVersion> {
184-
left.intersection(right)
184+
) -> Result<Range<PubGrubVersion>, ResolveError> {
185+
let result = left.intersection(right);
186+
if result.is_empty() {
187+
Err(ResolveError::ConflictingVersions(
188+
package.clone(),
189+
left.clone(),
190+
right.clone(),
191+
))
192+
} else {
193+
Ok(result)
194+
}
185195
}
186196

187197
/// Merge two [`PubGrubPackage`] instances.

crates/puffin-resolver/src/pubgrub/report.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt;
22

3+
use pubgrub::package::Package;
34
use pubgrub::range::Range;
45
use pubgrub::report::{DerivationTree, Derived, External, Reporter};
56
use pubgrub::term::Term;
@@ -345,6 +346,8 @@ enum PuffinExternal {
345346
NoVersions(PubGrubPackage, Range<PubGrubVersion>),
346347
/// Dependencies of the package are unavailable for versions in that set.
347348
UnavailableDependencies(PubGrubPackage, Range<PubGrubVersion>),
349+
/// Dependencies of the package are unusable for versions in that set.
350+
UnusableDependencies(PubGrubPackage, Range<PubGrubVersion>, Option<String>),
348351
/// Incompatibility coming from the dependencies of a given package.
349352
FromDependencyOf(
350353
PubGrubPackage,
@@ -362,6 +365,9 @@ impl PuffinExternal {
362365
External::UnavailableDependencies(p, vs) => {
363366
PuffinExternal::UnavailableDependencies(p, vs)
364367
}
368+
External::UnusableDependencies(p, vs, reason) => {
369+
PuffinExternal::UnusableDependencies(p, vs, reason)
370+
}
365371
External::FromDependencyOf(p, vs, p_dep, vs_dep) => {
366372
PuffinExternal::FromDependencyOf(p, vs, p_dep, vs_dep)
367373
}
@@ -395,6 +401,25 @@ impl fmt::Display for PuffinExternal {
395401
)
396402
}
397403
}
404+
Self::UnusableDependencies(package, set, reason) => {
405+
if let Some(reason) = reason {
406+
if matches!(package, PubGrubPackage::Root(_)) {
407+
write!(f, "{package} dependencies are unusable: {reason}")
408+
} else {
409+
if set == &Range::full() {
410+
write!(f, "dependencies of {package} are unusable: {reason}")
411+
} else {
412+
write!(f, "dependencies of {package}{set} are unusable: {reason}",)
413+
}
414+
}
415+
} else {
416+
if set == &Range::full() {
417+
write!(f, "dependencies of {package} are unusable")
418+
} else {
419+
write!(f, "dependencies of {package}{set} are unusable")
420+
}
421+
}
422+
}
398423
Self::FromDependencyOf(package, package_set, dependency, dependency_set) => {
399424
if package_set == &Range::full() && dependency_set == &Range::full() {
400425
write!(f, "{package} depends on {dependency}")

crates/puffin-resolver/src/resolver.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,14 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
239239
));
240240
continue;
241241
}
242+
Dependencies::Unusable(reason) => {
243+
state.add_incompatibility(Incompatibility::unusable_dependencies(
244+
package.clone(),
245+
version.clone(),
246+
reason.clone(),
247+
));
248+
continue;
249+
}
242250
Dependencies::Known(constraints) if constraints.contains_key(package) => {
243251
return Err(PubGrubError::SelfDependency {
244252
package: package.clone(),
@@ -455,7 +463,11 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
455463
None,
456464
None,
457465
self.markers,
458-
)?;
466+
);
467+
if let Err(err @ ResolveError::ConflictingVersions(..)) = constraints {
468+
return Ok(Dependencies::Unusable(Some(format!("{err}"))));
469+
}
470+
let constraints = constraints?;
459471

460472
for (package, version) in constraints.iter() {
461473
debug!("Adding direct dependency: {package}{version}");
@@ -910,6 +922,8 @@ impl<'a> FromIterator<&'a Url> for AllowedUrls {
910922
enum Dependencies {
911923
/// Package dependencies are unavailable.
912924
Unknown,
925+
/// Package dependencies are not usable
926+
Unusable(Option<String>),
913927
/// Container for all available package versions.
914928
Known(DependencyConstraints<PubGrubPackage, Range<PubGrubVersion>>),
915929
}

vendor/pubgrub/src/internal/incompatibility.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub enum Kind<P: Package, VS: VersionSet> {
4545
NoVersions(P, VS),
4646
/// Dependencies of the package are unavailable for versions in that range.
4747
UnavailableDependencies(P, VS),
48+
/// Dependencies of the package are unusable for versions in that range.
49+
UnusableDependencies(P, VS, Option<String>),
4850
/// Incompatibility coming from the dependencies of a given package.
4951
FromDependencyOf(P, VS, P, VS),
5052
/// Derived from two causes. Stores cause ids.
@@ -104,6 +106,17 @@ impl<P: Package, VS: VersionSet> Incompatibility<P, VS> {
104106
}
105107
}
106108

109+
/// Create an incompatibility to remember
110+
/// that a package version is not selectable
111+
/// because its dependencies are not usable.
112+
pub fn unusable_dependencies(package: P, version: VS::V, reason: Option<String>) -> Self {
113+
let set = VS::singleton(version);
114+
Self {
115+
package_terms: SmallMap::One([(package.clone(), Term::Positive(set.clone()))]),
116+
kind: Kind::UnusableDependencies(package, set, reason),
117+
}
118+
}
119+
107120
/// Build an incompatibility from a given dependency.
108121
pub fn from_dependency(package: P, version: VS::V, dep: (&P, &VS)) -> Self {
109122
let set1 = VS::singleton(version);
@@ -206,6 +219,9 @@ impl<P: Package, VS: VersionSet> Incompatibility<P, VS> {
206219
Kind::UnavailableDependencies(package, set) => DerivationTree::External(
207220
External::UnavailableDependencies(package.clone(), set.clone()),
208221
),
222+
Kind::UnusableDependencies(package, set, reason) => DerivationTree::External(
223+
External::UnusableDependencies(package.clone(), set.clone(), reason.clone()),
224+
),
209225
Kind::FromDependencyOf(package, set, dep_package, dep_set) => {
210226
DerivationTree::External(External::FromDependencyOf(
211227
package.clone(),

vendor/pubgrub/src/range.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ impl<V> Range<V> {
117117
segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))),
118118
}
119119
}
120+
121+
pub fn is_empty(&self) -> bool {
122+
self.segments.is_empty()
123+
}
120124
}
121125

122126
impl<V: Clone> Range<V> {

vendor/pubgrub/src/report.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ pub enum External<P: Package, VS: VersionSet> {
4141
NoVersions(P, VS),
4242
/// Dependencies of the package are unavailable for versions in that set.
4343
UnavailableDependencies(P, VS),
44+
/// Dependencies of the package are unusable for versions in that set.
45+
UnusableDependencies(P, VS, Option<String>),
4446
/// Incompatibility coming from the dependencies of a given package.
4547
FromDependencyOf(P, VS, P, VS),
4648
}
@@ -113,6 +115,13 @@ impl<P: Package, VS: VersionSet> DerivationTree<P, VS> {
113115
DerivationTree::External(External::UnavailableDependencies(_, r)) => Some(
114116
DerivationTree::External(External::UnavailableDependencies(package, set.union(&r))),
115117
),
118+
DerivationTree::External(External::UnusableDependencies(_, r, reason)) => {
119+
Some(DerivationTree::External(External::UnusableDependencies(
120+
package,
121+
set.union(&r),
122+
reason,
123+
)))
124+
}
116125
DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => {
117126
if p1 == package {
118127
Some(DerivationTree::External(External::FromDependencyOf(
@@ -158,6 +167,29 @@ impl<P: Package, VS: VersionSet> fmt::Display for External<P, VS> {
158167
)
159168
}
160169
}
170+
Self::UnusableDependencies(package, set, reason) => {
171+
if let Some(reason) = reason {
172+
if set == &VS::full() {
173+
write!(f, "dependencies of {} are unusable: {reason}", package)
174+
} else {
175+
write!(
176+
f,
177+
"dependencies of {} at version {} are unusable: {reason}",
178+
package, set
179+
)
180+
}
181+
} else {
182+
if set == &VS::full() {
183+
write!(f, "dependencies of {} are unusable", package)
184+
} else {
185+
write!(
186+
f,
187+
"dependencies of {} at version {} are unusable",
188+
package, set
189+
)
190+
}
191+
}
192+
}
161193
Self::FromDependencyOf(p, set_p, dep, set_dep) => {
162194
if set_p == &VS::full() && set_dep == &VS::full() {
163195
write!(f, "{} depends on {}", p, dep)

0 commit comments

Comments
 (0)