Skip to content

Commit 0d9d4f9

Browse files
zaniebkonstin
andauthored
Add an UnusableDependencies incompatibility kind and use for conflicting versions (#424)
Addresses #309 (comment) Similar to #338 this throws an error when merging versions results in an empty set. Instead of propagating that error, we capture it and return a new dependency type of `Unusable`. Unusable dependencies are a new incompatibility kind which includes an arbitrary "reason" string that we present to the user. Adding a new incompatibility kind requires changes to the vendored pubgrub crate. We could use this same incompatibility kind for conflicting urls as in #284 which should allow the solver to backtrack to another valid version instead of failing (see #425). Unlike #383 this does not require changes to PubGrub's package mapping model. I think in the long run we'll want PubGrub to accept multiple versions per package to solve this specific issue, but we're interested in it being merged upstream first. This pull request is just using the issue as a simple case to explore adding a new incompatibility type. We may or may not be able convince them to add this new incompatibility type upstream. As discussed in pubgrub-rs/pubgrub#152, we may want a more general incompatibility kind instead which can be used for arbitrary problems. An upstream pull request has been opened for discussion at pubgrub-rs/pubgrub#153. Related to: - pubgrub-rs/pubgrub#152 - #338 - #383 --------- Co-authored-by: konsti <[email protected]>
1 parent 832058d commit 0d9d4f9

File tree

7 files changed

+61
-10
lines changed

7 files changed

+61
-10
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ once_cell = { version = "1.18.0" }
4848
petgraph = { version = "0.6.4" }
4949
platform-info = { version = "2.0.2" }
5050
plist = { version = "1.6.0" }
51-
pubgrub = { git = "https://github.com/zanieb/pubgrub", rev = "46f1214fe6b7886709a35d8d2f2c0e1b56433b26" }
51+
pubgrub = { git = "https://github.com/zanieb/pubgrub", rev = "efe34571a876831dacac1cbba3ce5bc358f2a6e7" }
5252
pyproject-toml = { version = "0.8.0" }
5353
rand = { version = "0.8.5" }
5454
rayon = { version = "1.8.0" }

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/.tmpJ9Dkj3
1010
env:
11-
VIRTUAL_ENV: /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmp99w9dK/.venv
11+
VIRTUAL_ENV: /var/folders/bc/qlsk3t6x7c9fhhbvvcg68k9c0000gp/T/.tmpBOyFGn/.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: Conflicting versions for `django`:
20+
`django==5.0b1` does not intersect 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("Conflicting versions for `{0}`: {1}")]
48+
ConflictingVersions(String, String),
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: 13 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,19 @@ 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.to_string(),
189+
format!("`{package}{left}` does not intersect with `{package}{right}`"),
190+
))
191+
} else {
192+
Ok(result)
193+
}
185194
}
186195

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

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ enum PuffinExternal {
345345
NoVersions(PubGrubPackage, Range<PubGrubVersion>),
346346
/// Dependencies of the package are unavailable for versions in that set.
347347
UnavailableDependencies(PubGrubPackage, Range<PubGrubVersion>),
348+
/// Dependencies of the package are unusable for versions in that set.
349+
UnusableDependencies(PubGrubPackage, Range<PubGrubVersion>, Option<String>),
348350
/// Incompatibility coming from the dependencies of a given package.
349351
FromDependencyOf(
350352
PubGrubPackage,
@@ -362,6 +364,9 @@ impl PuffinExternal {
362364
External::UnavailableDependencies(p, vs) => {
363365
PuffinExternal::UnavailableDependencies(p, vs)
364366
}
367+
External::UnusableDependencies(p, vs, reason) => {
368+
PuffinExternal::UnusableDependencies(p, vs, reason)
369+
}
365370
External::FromDependencyOf(p, vs, p_dep, vs_dep) => {
366371
PuffinExternal::FromDependencyOf(p, vs, p_dep, vs_dep)
367372
}
@@ -395,6 +400,25 @@ impl fmt::Display for PuffinExternal {
395400
)
396401
}
397402
}
403+
Self::UnusableDependencies(package, set, reason) => {
404+
if let Some(reason) = reason {
405+
if matches!(package, PubGrubPackage::Root(_)) {
406+
write!(f, "{package} dependencies are unusable: {reason}")
407+
} else {
408+
if set == &Range::full() {
409+
write!(f, "dependencies of {package} are unusable: {reason}")
410+
} else {
411+
write!(f, "dependencies of {package}{set} are unusable: {reason}",)
412+
}
413+
}
414+
} else {
415+
if set == &Range::full() {
416+
write!(f, "dependencies of {package} are unusable")
417+
} else {
418+
write!(f, "dependencies of {package}{set} are unusable")
419+
}
420+
}
421+
}
398422
Self::FromDependencyOf(package, package_set, dependency, dependency_set) => {
399423
if package_set == &Range::full() && dependency_set == &Range::full() {
400424
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
@@ -241,6 +241,14 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
241241
));
242242
continue;
243243
}
244+
Dependencies::Unusable(reason) => {
245+
state.add_incompatibility(Incompatibility::unusable_dependencies(
246+
package.clone(),
247+
version.clone(),
248+
reason.clone(),
249+
));
250+
continue;
251+
}
244252
Dependencies::Known(constraints) if constraints.contains_key(package) => {
245253
return Err(PubGrubError::SelfDependency {
246254
package: package.clone(),
@@ -457,7 +465,11 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
457465
None,
458466
None,
459467
self.markers,
460-
)?;
468+
);
469+
if let Err(err @ ResolveError::ConflictingVersions(..)) = constraints {
470+
return Ok(Dependencies::Unusable(Some(err.to_string())));
471+
}
472+
let constraints = constraints?;
461473

462474
for (package, version) in constraints.iter() {
463475
debug!("Adding direct dependency: {package}{version}");
@@ -862,6 +874,8 @@ impl<'a> FromIterator<&'a Url> for AllowedUrls {
862874
enum Dependencies {
863875
/// Package dependencies are unavailable.
864876
Unknown,
877+
/// Package dependencies are not usable
878+
Unusable(Option<String>),
865879
/// Container for all available package versions.
866880
Known(DependencyConstraints<PubGrubPackage, Range<PubGrubVersion>>),
867881
}

0 commit comments

Comments
 (0)