mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 05:03:46 +00:00
Use generic pubgrub incompatibility reason (#3335)
Pubgrub got a new feature where all unavailability is a custom, instead of the reasonless `UnavailableDependencies` and our custom `String` type previously (https://github.com/pubgrub-rs/pubgrub/pull/208). This PR introduces a `UnavailableReason` that tracks either an entire version being unusable, or a specific version. The error messages now also track this difference properly. The pubgrub commit is our main rebased onto the merged https://github.com/pubgrub-rs/pubgrub/pull/208, i'll push `konsti/main-rebase-generic-reason` to `main` after checking for rebase problems.
This commit is contained in:
parent
bd7860de17
commit
1ad6aa8a23
11 changed files with 215 additions and 165 deletions
|
|
@ -53,59 +53,59 @@ impl Display for IncompatibleDist {
|
|||
match self {
|
||||
Self::Wheel(incompatibility) => match incompatibility {
|
||||
IncompatibleWheel::NoBinary => {
|
||||
f.write_str("no source distribution is available and using wheels is disabled")
|
||||
f.write_str("has no available source distribution and using wheels is disabled")
|
||||
}
|
||||
IncompatibleWheel::Tag(tag) => match tag {
|
||||
IncompatibleTag::Invalid => {
|
||||
f.write_str("no wheels are available with valid tags")
|
||||
}
|
||||
IncompatibleTag::Python => {
|
||||
f.write_str("no wheels are available with a matching Python implementation")
|
||||
f.write_str("has no wheels are available with valid tags")
|
||||
}
|
||||
IncompatibleTag::Python => f.write_str(
|
||||
"has no wheels are available with a matching Python implementation",
|
||||
),
|
||||
IncompatibleTag::Abi => {
|
||||
f.write_str("no wheels are available with a matching Python ABI")
|
||||
f.write_str("has no wheels are available with a matching Python ABI")
|
||||
}
|
||||
IncompatibleTag::Platform => {
|
||||
f.write_str("no wheels are available with a matching platform")
|
||||
f.write_str("has no wheels are available with a matching platform")
|
||||
}
|
||||
},
|
||||
IncompatibleWheel::Yanked(yanked) => match yanked {
|
||||
Yanked::Bool(_) => f.write_str("it was yanked"),
|
||||
Yanked::Bool(_) => f.write_str("was yanked"),
|
||||
Yanked::Reason(reason) => write!(
|
||||
f,
|
||||
"it was yanked (reason: {})",
|
||||
"was yanked (reason: {})",
|
||||
reason.trim().trim_end_matches('.')
|
||||
),
|
||||
},
|
||||
IncompatibleWheel::ExcludeNewer(ts) => match ts {
|
||||
Some(_) => f.write_str("it was published after the exclude newer time"),
|
||||
None => f.write_str("it has no publish time"),
|
||||
Some(_) => f.write_str("was published after the exclude newer time"),
|
||||
None => f.write_str("has no publish time"),
|
||||
},
|
||||
IncompatibleWheel::RequiresPython(python) => {
|
||||
write!(f, "it requires at python {python}")
|
||||
write!(f, "requires at python {python}")
|
||||
}
|
||||
},
|
||||
Self::Source(incompatibility) => match incompatibility {
|
||||
IncompatibleSource::NoBuild => {
|
||||
f.write_str("no wheels are usable and building from source is disabled")
|
||||
f.write_str("has no usable wheels and building from source is disabled")
|
||||
}
|
||||
IncompatibleSource::Yanked(yanked) => match yanked {
|
||||
Yanked::Bool(_) => f.write_str("it was yanked"),
|
||||
Yanked::Bool(_) => f.write_str("was yanked"),
|
||||
Yanked::Reason(reason) => write!(
|
||||
f,
|
||||
"it was yanked (reason: {})",
|
||||
"was yanked (reason: {})",
|
||||
reason.trim().trim_end_matches('.')
|
||||
),
|
||||
},
|
||||
IncompatibleSource::ExcludeNewer(ts) => match ts {
|
||||
Some(_) => f.write_str("it was published after the exclude newer time"),
|
||||
None => f.write_str("it has no publish time"),
|
||||
Some(_) => f.write_str("was published after the exclude newer time"),
|
||||
None => f.write_str("has no publish time"),
|
||||
},
|
||||
IncompatibleSource::RequiresPython(python) => {
|
||||
write!(f, "it requires python {python}")
|
||||
write!(f, "requires python {python}")
|
||||
}
|
||||
},
|
||||
Self::Unavailable => f.write_str("no distributions are available"),
|
||||
Self::Unavailable => f.write_str("has no available distributions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use pubgrub::solver::{Dependencies, DependencyProvider};
|
|||
use pep440_rs::Version;
|
||||
|
||||
use crate::pubgrub::{PubGrubPackage, PubGrubPriority};
|
||||
use crate::resolver::UnavailableReason;
|
||||
|
||||
/// We don't use a dependency provider, we interact with state directly, but we still need this one
|
||||
/// for type
|
||||
|
|
@ -15,6 +16,8 @@ impl DependencyProvider for UvDependencyProvider {
|
|||
type P = PubGrubPackage;
|
||||
type V = Version;
|
||||
type VS = Range<Version>;
|
||||
type M = UnavailableReason;
|
||||
|
||||
fn prioritize(&self, _package: &Self::P, _range: &Self::VS) -> Self::Priority {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
@ -34,7 +37,7 @@ impl DependencyProvider for UvDependencyProvider {
|
|||
&self,
|
||||
_package: &Self::P,
|
||||
_version: &Self::V,
|
||||
) -> Result<Dependencies<Vec<(Self::P, Self::VS)>>, Self::Err> {
|
||||
) -> Result<Dependencies<Vec<(Self::P, Self::VS)>, Self::M>, Self::Err> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ use crate::dependency_provider::UvDependencyProvider;
|
|||
use crate::pubgrub::{PubGrubPackage, PubGrubPython, PubGrubReportFormatter};
|
||||
use crate::python_requirement::PythonRequirement;
|
||||
use crate::resolver::{
|
||||
IncompletePackage, SharedMap, SharedSet, UnavailablePackage, VersionsResponse,
|
||||
IncompletePackage, SharedMap, SharedSet, UnavailablePackage, UnavailableReason,
|
||||
VersionsResponse,
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
|
@ -119,7 +120,9 @@ impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
|
|||
|
||||
/// Given a [`DerivationTree`], collapse any [`External::FromDependencyOf`] incompatibilities
|
||||
/// wrap an [`PubGrubPackage::Extra`] package.
|
||||
fn collapse_extra_proxies(derivation_tree: &mut DerivationTree<PubGrubPackage, Range<Version>>) {
|
||||
fn collapse_extra_proxies(
|
||||
derivation_tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
) {
|
||||
match derivation_tree {
|
||||
DerivationTree::External(_) => {}
|
||||
DerivationTree::Derived(derived) => {
|
||||
|
|
@ -193,7 +196,7 @@ impl From<pubgrub::error::PubGrubError<UvDependencyProvider>> for ResolveError {
|
|||
/// A wrapper around [`pubgrub::error::PubGrubError::NoSolution`] that displays a resolution failure report.
|
||||
#[derive(Debug)]
|
||||
pub struct NoSolutionError {
|
||||
derivation_tree: DerivationTree<PubGrubPackage, Range<Version>>,
|
||||
derivation_tree: DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
available_versions: IndexMap<PubGrubPackage, BTreeSet<Version>>,
|
||||
selector: Option<CandidateSelector>,
|
||||
python_requirement: Option<PythonRequirement>,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use uv_normalize::PackageName;
|
|||
|
||||
use crate::candidate_selector::CandidateSelector;
|
||||
use crate::python_requirement::PythonRequirement;
|
||||
use crate::resolver::{IncompletePackage, UnavailablePackage};
|
||||
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason};
|
||||
|
||||
use super::PubGrubPackage;
|
||||
|
||||
|
|
@ -30,15 +30,20 @@ pub(crate) struct PubGrubReportFormatter<'a> {
|
|||
pub(crate) python_requirement: Option<&'a PythonRequirement>,
|
||||
}
|
||||
|
||||
impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<'_> {
|
||||
impl ReportFormatter<PubGrubPackage, Range<Version>, UnavailableReason>
|
||||
for PubGrubReportFormatter<'_>
|
||||
{
|
||||
type Output = String;
|
||||
|
||||
fn format_external(&self, external: &External<PubGrubPackage, Range<Version>>) -> Self::Output {
|
||||
fn format_external(
|
||||
&self,
|
||||
external: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
) -> Self::Output {
|
||||
match external {
|
||||
External::NotRoot(package, version) => {
|
||||
format!("we are solving dependencies of {package} {version}")
|
||||
}
|
||||
External::NoVersions(package, set, reason) => {
|
||||
External::NoVersions(package, set) => {
|
||||
if matches!(package, PubGrubPackage::Python(_)) {
|
||||
if let Some(python) = self.python_requirement {
|
||||
if python.target() == python.installed() {
|
||||
|
|
@ -79,16 +84,6 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
}
|
||||
let set = self.simplify_set(set, package);
|
||||
|
||||
// Check for a reason
|
||||
if let Some(reason) = reason {
|
||||
let formatted = if set.as_ref() == &Range::full() {
|
||||
format!("{package} {reason}")
|
||||
} else {
|
||||
format!("{package}{set} {reason}")
|
||||
};
|
||||
return formatted;
|
||||
}
|
||||
|
||||
if set.as_ref() == &Range::full() {
|
||||
format!("there are no versions of {package}")
|
||||
} else if set.as_singleton().is_some() {
|
||||
|
|
@ -112,17 +107,26 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
}
|
||||
}
|
||||
}
|
||||
External::Unavailable(package, set, reason) => match package {
|
||||
External::Custom(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), " ")
|
||||
),
|
||||
_ => match reason {
|
||||
UnavailableReason::Package(reason) => {
|
||||
// While there may be a term attached, this error applies to the entire
|
||||
// package, so we show it for the entire package
|
||||
format!("{}{reason}", Padded::new("", &package, " "))
|
||||
}
|
||||
UnavailableReason::Version(reason) => {
|
||||
format!(
|
||||
"{}{reason}",
|
||||
Padded::new("", &PackageRange::compatibility(package, set), " ")
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
External::FromDependencyOf(package, package_set, dependency, dependency_set) => {
|
||||
let package_set = self.simplify_set(package_set, package);
|
||||
|
|
@ -198,8 +202,8 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
/// Simplest case, we just combine two external incompatibilities.
|
||||
fn explain_both_external(
|
||||
&self,
|
||||
external1: &External<PubGrubPackage, Range<Version>>,
|
||||
external2: &External<PubGrubPackage, Range<Version>>,
|
||||
external1: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
external2: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
|
||||
) -> String {
|
||||
let external = self.format_both_external(external1, external2);
|
||||
|
|
@ -216,9 +220,9 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
fn explain_both_ref(
|
||||
&self,
|
||||
ref_id1: usize,
|
||||
derived1: &Derived<PubGrubPackage, Range<Version>>,
|
||||
derived1: &Derived<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
ref_id2: usize,
|
||||
derived2: &Derived<PubGrubPackage, Range<Version>>,
|
||||
derived2: &Derived<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
|
||||
) -> String {
|
||||
// TODO: order should be chosen to make it more logical.
|
||||
|
|
@ -243,8 +247,8 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
fn explain_ref_and_external(
|
||||
&self,
|
||||
ref_id: usize,
|
||||
derived: &Derived<PubGrubPackage, Range<Version>>,
|
||||
external: &External<PubGrubPackage, Range<Version>>,
|
||||
derived: &Derived<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
external: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
|
||||
) -> String {
|
||||
// TODO: order should be chosen to make it more logical.
|
||||
|
|
@ -265,7 +269,7 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
/// Add an external cause to the chain of explanations.
|
||||
fn and_explain_external(
|
||||
&self,
|
||||
external: &External<PubGrubPackage, Range<Version>>,
|
||||
external: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
|
||||
) -> String {
|
||||
let external = self.format_external(external);
|
||||
|
|
@ -282,7 +286,7 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
fn and_explain_ref(
|
||||
&self,
|
||||
ref_id: usize,
|
||||
derived: &Derived<PubGrubPackage, Range<Version>>,
|
||||
derived: &Derived<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
|
||||
) -> String {
|
||||
let derived = self.format_terms(&derived.terms);
|
||||
|
|
@ -299,8 +303,8 @@ impl ReportFormatter<PubGrubPackage, Range<Version>> for PubGrubReportFormatter<
|
|||
/// Add an already explained incompat to the chain of explanations.
|
||||
fn and_explain_prior_and_external(
|
||||
&self,
|
||||
prior_external: &External<PubGrubPackage, Range<Version>>,
|
||||
external: &External<PubGrubPackage, Range<Version>>,
|
||||
prior_external: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
external: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
current_terms: &Map<PubGrubPackage, Term<Range<Version>>>,
|
||||
) -> String {
|
||||
let external = self.format_both_external(prior_external, external);
|
||||
|
|
@ -318,8 +322,8 @@ impl PubGrubReportFormatter<'_> {
|
|||
/// Format two external incompatibilities, combining them if possible.
|
||||
fn format_both_external(
|
||||
&self,
|
||||
external1: &External<PubGrubPackage, Range<Version>>,
|
||||
external2: &External<PubGrubPackage, Range<Version>>,
|
||||
external1: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
external2: &External<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
) -> String {
|
||||
match (external1, external2) {
|
||||
(
|
||||
|
|
@ -387,7 +391,7 @@ impl PubGrubReportFormatter<'_> {
|
|||
/// their requirements.
|
||||
pub(crate) fn hints(
|
||||
&self,
|
||||
derivation_tree: &DerivationTree<PubGrubPackage, Range<Version>>,
|
||||
derivation_tree: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
|
||||
selector: &Option<CandidateSelector>,
|
||||
index_locations: &Option<IndexLocations>,
|
||||
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
|
||||
|
|
@ -404,7 +408,7 @@ impl PubGrubReportFormatter<'_> {
|
|||
let mut hints = IndexSet::default();
|
||||
match derivation_tree {
|
||||
DerivationTree::External(external) => match external {
|
||||
External::Unavailable(package, set, _) | External::NoVersions(package, set, _) => {
|
||||
External::Custom(package, set, _) | External::NoVersions(package, set) => {
|
||||
// Check for no versions due to pre-release options
|
||||
if let Some(selector) = selector {
|
||||
let any_prerelease = set.iter().any(|(start, end)| {
|
||||
|
|
|
|||
|
|
@ -66,16 +66,62 @@ mod provider;
|
|||
mod reporter;
|
||||
mod urls;
|
||||
|
||||
/// The package version is unavailable and cannot be used
|
||||
/// Unlike [`PackageUnavailable`] this applies to a single version of the package
|
||||
#[derive(Debug, Clone)]
|
||||
/// The reason why a package or a version cannot be used.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum UnavailableReason {
|
||||
/// The entire package cannot be used.
|
||||
Package(UnavailablePackage),
|
||||
/// A single version cannot be used.
|
||||
Version(UnavailableVersion),
|
||||
}
|
||||
|
||||
impl Display for UnavailableReason {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Version(version) => Display::fmt(version, f),
|
||||
Self::Package(package) => Display::fmt(package, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The package version is unavailable and cannot be used. Unlike [`PackageUnavailable`], this
|
||||
/// applies to a single version of the package.
|
||||
///
|
||||
/// Most variant are from [`MetadataResponse`] without the error source (since we don't format
|
||||
/// the source).
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum UnavailableVersion {
|
||||
/// Version is incompatible because it has no usable distributions
|
||||
IncompatibleDist(IncompatibleDist),
|
||||
/// The wheel metadata was found, but could not be parsed.
|
||||
InvalidMetadata,
|
||||
/// The wheel metadata was found, but the metadata was inconsistent.
|
||||
InconsistentMetadata,
|
||||
/// The wheel has an invalid structure.
|
||||
InvalidStructure,
|
||||
/// The wheel metadata was not found in the cache and the network is not available.
|
||||
Offline,
|
||||
/// Forward any kind of resolver error.
|
||||
ResolverError(String),
|
||||
}
|
||||
|
||||
impl Display for UnavailableVersion {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UnavailableVersion::IncompatibleDist(invalid_dist) => Display::fmt(invalid_dist, f),
|
||||
UnavailableVersion::InvalidMetadata => f.write_str("has invalid metadata"),
|
||||
UnavailableVersion::InconsistentMetadata => f.write_str("has inconsistent metadata"),
|
||||
UnavailableVersion::InvalidStructure => f.write_str("has an invalid package format"),
|
||||
UnavailableVersion::Offline => f.write_str(
|
||||
"network connectivity is disabled, but the metadata wasn't found in the cache",
|
||||
),
|
||||
UnavailableVersion::ResolverError(err) => f.write_str(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The package is unavailable and cannot be used.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum UnavailablePackage {
|
||||
/// Index lookups were disabled (i.e., `--no-index`) and the package was not found in a flat index (i.e. from `--find-links`).
|
||||
NoIndex,
|
||||
|
|
@ -89,6 +135,24 @@ pub(crate) enum UnavailablePackage {
|
|||
InvalidStructure(String),
|
||||
}
|
||||
|
||||
impl UnavailablePackage {
|
||||
pub(crate) fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
UnavailablePackage::NoIndex => "was not found in the provided package locations",
|
||||
UnavailablePackage::Offline => "was not found in the cache",
|
||||
UnavailablePackage::NotFound => "was not found in the package registry",
|
||||
UnavailablePackage::InvalidMetadata(_) => "has invalid metadata",
|
||||
UnavailablePackage::InvalidStructure(_) => "has an invalid package format",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UnavailablePackage {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// The package is unavailable at specific versions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum IncompletePackage {
|
||||
|
|
@ -359,84 +423,61 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
.term_intersection_for_package(&next)
|
||||
.expect("a package was chosen but we don't have a term.");
|
||||
|
||||
let reason = {
|
||||
if let PubGrubPackage::Package(ref package_name, _, _) = next {
|
||||
// Check if the decision was due to the package being unavailable
|
||||
self.unavailable_packages
|
||||
.borrow()
|
||||
.get(package_name)
|
||||
.map(|entry| match *entry {
|
||||
UnavailablePackage::NoIndex => {
|
||||
"was not found in the provided package locations"
|
||||
}
|
||||
UnavailablePackage::Offline => "was not found in the cache",
|
||||
UnavailablePackage::NotFound => {
|
||||
"was not found in the package registry"
|
||||
}
|
||||
UnavailablePackage::InvalidMetadata(_) => {
|
||||
"was found, but the metadata could not be parsed"
|
||||
}
|
||||
UnavailablePackage::InvalidStructure(_) => {
|
||||
"was found, but has an invalid format"
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
// Check if the decision was due to the package being unavailable
|
||||
if let PubGrubPackage::Package(ref package_name, _, _) = next {
|
||||
if let Some(entry) = self.unavailable_packages.borrow().get(package_name) {
|
||||
state.add_incompatibility(Incompatibility::custom_term(
|
||||
next.clone(),
|
||||
term_intersection.clone(),
|
||||
UnavailableReason::Package(entry.clone()),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let inc = Incompatibility::no_versions(
|
||||
state.add_incompatibility(Incompatibility::no_versions(
|
||||
next.clone(),
|
||||
term_intersection.clone(),
|
||||
reason.map(ToString::to_string),
|
||||
);
|
||||
|
||||
state.add_incompatibility(inc);
|
||||
));
|
||||
continue;
|
||||
}
|
||||
Some(version) => version,
|
||||
};
|
||||
let version = match version {
|
||||
ResolverVersion::Available(version) => version,
|
||||
ResolverVersion::Unavailable(version, unavailable) => {
|
||||
let reason = match unavailable {
|
||||
// Incompatible requires-python versions are special in that we track
|
||||
// them as incompatible dependencies instead of marking the package version
|
||||
// as unavailable directly
|
||||
UnavailableVersion::IncompatibleDist(
|
||||
IncompatibleDist::Source(IncompatibleSource::RequiresPython(
|
||||
requires_python,
|
||||
))
|
||||
| IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
|
||||
requires_python,
|
||||
)),
|
||||
) => {
|
||||
let python_version = requires_python
|
||||
.iter()
|
||||
.map(PubGrubSpecifier::try_from)
|
||||
.fold_ok(Range::full(), |range, specifier| {
|
||||
range.intersection(&specifier.into())
|
||||
})?;
|
||||
ResolverVersion::Unavailable(version, reason) => {
|
||||
// Incompatible requires-python versions are special in that we track
|
||||
// them as incompatible dependencies instead of marking the package version
|
||||
// as unavailable directly
|
||||
if let UnavailableVersion::IncompatibleDist(
|
||||
IncompatibleDist::Source(IncompatibleSource::RequiresPython(
|
||||
requires_python,
|
||||
))
|
||||
| IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python)),
|
||||
) = reason
|
||||
{
|
||||
let python_version = requires_python
|
||||
.iter()
|
||||
.map(PubGrubSpecifier::try_from)
|
||||
.fold_ok(Range::full(), |range, specifier| {
|
||||
range.intersection(&specifier.into())
|
||||
})?;
|
||||
|
||||
let package = &next;
|
||||
for kind in [PubGrubPython::Installed, PubGrubPython::Target] {
|
||||
state.add_incompatibility(Incompatibility::from_dependency(
|
||||
package.clone(),
|
||||
Range::singleton(version.clone()),
|
||||
(PubGrubPackage::Python(kind), python_version.clone()),
|
||||
));
|
||||
}
|
||||
state.partial_solution.add_decision(next.clone(), version);
|
||||
continue;
|
||||
}
|
||||
UnavailableVersion::IncompatibleDist(incompatibility) => {
|
||||
incompatibility.to_string()
|
||||
let package = &next;
|
||||
for kind in [PubGrubPython::Installed, PubGrubPython::Target] {
|
||||
state.add_incompatibility(Incompatibility::from_dependency(
|
||||
package.clone(),
|
||||
Range::singleton(version.clone()),
|
||||
(PubGrubPackage::Python(kind), python_version.clone()),
|
||||
));
|
||||
}
|
||||
state.partial_solution.add_decision(next.clone(), version);
|
||||
continue;
|
||||
};
|
||||
state.add_incompatibility(Incompatibility::unavailable(
|
||||
state.add_incompatibility(Incompatibility::custom_version(
|
||||
next.clone(),
|
||||
version.clone(),
|
||||
reason,
|
||||
UnavailableReason::Version(reason),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
|
@ -467,10 +508,10 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
.await?
|
||||
{
|
||||
Dependencies::Unavailable(reason) => {
|
||||
state.add_incompatibility(Incompatibility::unavailable(
|
||||
state.add_incompatibility(Incompatibility::custom_version(
|
||||
package.clone(),
|
||||
version.clone(),
|
||||
reason.clone(),
|
||||
UnavailableReason::Version(reason),
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
|
@ -816,7 +857,9 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
let mut constraints = match constraints {
|
||||
Ok(constraints) => constraints,
|
||||
Err(err) => {
|
||||
return Ok(Dependencies::Unavailable(uncapitalize(err.to_string())));
|
||||
return Ok(Dependencies::Unavailable(
|
||||
UnavailableVersion::ResolverError(uncapitalize(err.to_string())),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -939,9 +982,9 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
false,
|
||||
"Dependencies were requested for a package that is not available"
|
||||
);
|
||||
return Ok(Dependencies::Unavailable(
|
||||
"The package is unavailable".to_string(),
|
||||
));
|
||||
return Err(ResolveError::Failure(format!(
|
||||
"The package is unavailable: {package_name}"
|
||||
)));
|
||||
}
|
||||
|
||||
// Wait for the metadata to be available.
|
||||
|
|
@ -962,10 +1005,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
.or_default()
|
||||
.borrow_mut()
|
||||
.insert(version.clone(), IncompletePackage::Offline);
|
||||
return Ok(Dependencies::Unavailable(
|
||||
"network connectivity is disabled, but the metadata wasn't found in the cache"
|
||||
.to_string(),
|
||||
));
|
||||
return Ok(Dependencies::Unavailable(UnavailableVersion::Offline));
|
||||
}
|
||||
MetadataResponse::InvalidMetadata(err) => {
|
||||
warn!("Unable to extract metadata for {package_name}: {err}");
|
||||
|
|
@ -979,7 +1019,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
IncompletePackage::InvalidMetadata(err.to_string()),
|
||||
);
|
||||
return Ok(Dependencies::Unavailable(
|
||||
"the package metadata could not be parsed".to_string(),
|
||||
UnavailableVersion::InvalidMetadata,
|
||||
));
|
||||
}
|
||||
MetadataResponse::InconsistentMetadata(err) => {
|
||||
|
|
@ -994,7 +1034,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
IncompletePackage::InconsistentMetadata(err.to_string()),
|
||||
);
|
||||
return Ok(Dependencies::Unavailable(
|
||||
"the package metadata was inconsistent".to_string(),
|
||||
UnavailableVersion::InconsistentMetadata,
|
||||
));
|
||||
}
|
||||
MetadataResponse::InvalidStructure(err) => {
|
||||
|
|
@ -1009,7 +1049,7 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
IncompletePackage::InvalidStructure(err.to_string()),
|
||||
);
|
||||
return Ok(Dependencies::Unavailable(
|
||||
"the package has an invalid format".to_string(),
|
||||
UnavailableVersion::InvalidStructure,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
|
@ -1342,7 +1382,7 @@ enum Response {
|
|||
#[derive(Clone)]
|
||||
enum Dependencies {
|
||||
/// Package dependencies are not available.
|
||||
Unavailable(String),
|
||||
Unavailable(UnavailableVersion),
|
||||
/// Container for all available package versions.
|
||||
Available(Vec<(PubGrubPackage, Range<Version>)>),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2668,7 +2668,7 @@ fn compile_yanked_version_indirect() -> Result<()> {
|
|||
attrs<=20.3.0
|
||||
attrs==21.1.0
|
||||
attrs>=21.2.0
|
||||
and attrs==21.1.0 is unusable because it was yanked (reason: Installable but not importable on Python 3.4), we can conclude that attrs>20.3.0,<21.2.0 cannot be used.
|
||||
and attrs==21.1.0 was yanked (reason: Installable but not importable on Python 3.4), we can conclude that attrs>20.3.0,<21.2.0 cannot be used.
|
||||
And because you require attrs>20.3.0,<21.2.0, we can conclude that the requirements are unsatisfiable.
|
||||
"###
|
||||
);
|
||||
|
|
@ -4668,7 +4668,7 @@ fn offline_registry() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because black==23.10.1 was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because black was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Packages were unavailable because the network was disabled
|
||||
"###
|
||||
|
|
@ -4898,7 +4898,7 @@ fn invalid_metadata_requires_python() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because validation==2.0.0 is unusable because the package metadata could not be parsed and you require validation==2.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because validation==2.0.0 has invalid metadata and you require validation==2.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Metadata for validation==2.0.0 could not be parsed:
|
||||
Failed to parse version: Unexpected end of version specifier, expected operator:
|
||||
|
|
@ -4930,7 +4930,7 @@ fn invalid_metadata_multiple_dist_info() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because validation==3.0.0 is unusable because the package has an invalid format and you require validation==3.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because validation==3.0.0 has an invalid package format and you require validation==3.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: The structure of validation==3.0.0 was invalid:
|
||||
Multiple .dist-info directories found: validation-2.0.0, validation-3.0.0
|
||||
|
|
@ -5171,7 +5171,7 @@ fn index_url_in_requirements() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because anyio<4 was not found in the package registry and you require anyio<4, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because anyio was not found in the package registry and you require anyio<4, we can conclude that the requirements are unsatisfiable.
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1018,7 +1018,7 @@ fn install_no_index_version() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because flask==3.0.0 was not found in the provided package locations and you require flask==3.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because flask was not found in the provided package locations and you require flask==3.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
|
||||
"###
|
||||
|
|
@ -1558,7 +1558,7 @@ fn only_binary_requirements_txt() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because django-allauth==0.51.0 is unusable because no wheels are usable and building from source is disabled and you require django-allauth==0.51.0, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because django-allauth==0.51.0 has no usable wheels and building from source is disabled and you require django-allauth==0.51.0, we can conclude that the requirements are unsatisfiable.
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
@ -3890,7 +3890,7 @@ fn already_installed_local_version_of_remote_package() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because anyio==4.2.0 was not found in the provided package locations and you require anyio==4.2.0, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because anyio was not found in the provided package locations and you require anyio==4.2.0, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
|
||||
"###
|
||||
|
|
@ -4188,7 +4188,7 @@ fn already_installed_remote_url() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because uv-public-pypackage==0.2.0 was not found in the provided package locations and you require uv-public-pypackage==0.2.0, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because uv-public-pypackage was not found in the provided package locations and you require uv-public-pypackage==0.2.0, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
|
||||
"###);
|
||||
|
|
|
|||
|
|
@ -1419,7 +1419,7 @@ fn local_used_without_sdist() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-a==1.2.3 is unusable because no wheels are available with a matching Python ABI and you require package-a==1.2.3, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because package-a==1.2.3 has no wheels are available with a matching Python ABI and you require package-a==1.2.3, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
// The version '1.2.3+foo' satisfies the constraint '==1.2.3'.
|
||||
|
|
@ -1793,7 +1793,7 @@ fn local_transitive_confounding() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-b==2.0.0 is unusable because no wheels are available with a matching Python ABI and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
|
||||
╰─▶ Because package-b==2.0.0 has no wheels are available with a matching Python ABI and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
|
||||
And because only package-a==1.0.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4204,7 +4204,7 @@ fn no_sdist_no_wheels_with_matching_platform() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are available with a matching platform, we can conclude that all versions of package-a cannot be used.
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels are available with a matching platform, we can conclude that all versions of package-a cannot be used.
|
||||
And because you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4245,7 +4245,7 @@ fn no_sdist_no_wheels_with_matching_python() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are available with a matching Python implementation, we can conclude that all versions of package-a cannot be used.
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels are available with a matching Python implementation, we can conclude that all versions of package-a cannot be used.
|
||||
And because you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4286,7 +4286,7 @@ fn no_sdist_no_wheels_with_matching_abi() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are available with a matching Python ABI, we can conclude that all versions of package-a cannot be used.
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no wheels are available with a matching Python ABI, we can conclude that all versions of package-a cannot be used.
|
||||
And because you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4329,7 +4329,7 @@ fn no_wheels_no_build() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no wheels are usable and building from source is disabled, we can conclude that all versions of package-a cannot be used.
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no usable wheels and building from source is disabled, we can conclude that all versions of package-a cannot be used.
|
||||
And because you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4368,7 +4368,7 @@ fn only_wheels_no_binary() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because no source distribution is available and using wheels is disabled, we can conclude that all versions of package-a cannot be used.
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no available source distribution and using wheels is disabled, we can conclude that all versions of package-a cannot be used.
|
||||
And because you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4485,7 +4485,7 @@ fn package_only_yanked() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that all versions of package-a cannot be used.
|
||||
╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that all versions of package-a cannot be used.
|
||||
And because you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4527,7 +4527,7 @@ fn package_only_yanked_in_range() {
|
|||
╰─▶ Because only the following versions of package-a are available:
|
||||
package-a<=0.1.0
|
||||
package-a==1.0.0
|
||||
and package-a==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that package-a>0.1.0 cannot be used.
|
||||
and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-a>0.1.0 cannot be used.
|
||||
And because you require package-a>0.1.0, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
@ -4667,7 +4667,7 @@ fn transitive_package_only_yanked() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that all versions of package-b cannot be used.
|
||||
╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that all versions of package-b cannot be used.
|
||||
And because package-a==0.1.0 depends on package-b, we can conclude that package-a==0.1.0 cannot be used.
|
||||
And because only package-a==0.1.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
|
@ -4719,7 +4719,7 @@ fn transitive_package_only_yanked_in_range() {
|
|||
╰─▶ Because only the following versions of package-b are available:
|
||||
package-b<=0.1
|
||||
package-b==1.0.0
|
||||
and package-b==1.0.0 is unusable because it was yanked (reason: Yanked for testing), we can conclude that package-b>0.1 cannot be used.
|
||||
and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-b>0.1 cannot be used.
|
||||
And because package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used.
|
||||
And because only package-a==0.1.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
|
@ -4840,7 +4840,7 @@ fn transitive_yanked_and_unyanked_dependency() {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because package-c==2.0.0 is unusable because it was yanked (reason: Yanked for testing) and package-a==1.0.0 depends on package-c==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
|
||||
╰─▶ Because package-c==2.0.0 was yanked (reason: Yanked for testing) and package-a==1.0.0 depends on package-c==2.0.0, we can conclude that package-a==1.0.0 cannot be used.
|
||||
And because only package-a==1.0.0 is available and you require package-a, we can conclude that the requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
|
|
|
|||
|
|
@ -850,7 +850,7 @@ fn install_no_index() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because markupsafe was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
|
||||
"###
|
||||
|
|
@ -902,7 +902,7 @@ fn install_no_index_cached() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because markupsafe==2.1.3 was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because markupsafe was not found in the provided package locations and you require markupsafe==2.1.3, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Packages were unavailable because index lookups were disabled and no additional package locations were provided (try: `--find-links <uri>`)
|
||||
"###
|
||||
|
|
@ -1107,7 +1107,7 @@ fn mismatched_name() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because foo was found, but has an invalid format and you require foo, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because foo has an invalid package format and you require foo, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: The structure of foo was invalid:
|
||||
The .dist-info directory tomli-2.0.1 does not start with the normalized package name: foo
|
||||
|
|
@ -2766,7 +2766,7 @@ fn offline() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because black==23.10.1 was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because black was not found in the cache and you require black==23.10.1, we can conclude that the requirements are unsatisfiable.
|
||||
|
||||
hint: Packages were unavailable because the network was disabled
|
||||
"###
|
||||
|
|
@ -4658,7 +4658,7 @@ fn require_hashes_registry_valid_hash() -> Result<()> {
|
|||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because example-a-961b4c22==1.0.0 was not found in the package registry and you require example-a-961b4c22==1.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
╰─▶ Because example-a-961b4c22 was not found in the package registry and you require example-a-961b4c22==1.0.0, we can conclude that the requirements are unsatisfiable.
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue