Show requirement sources in pip-compile output (#149)

Builds up a complete resolved graph from PubGrub, and shows the sources
that led to each package being included in the resolution, like
`pip-compile`.

Closes https://github.com/astral-sh/puffin/issues/60.
This commit is contained in:
Charlie Marsh 2023-10-20 01:14:59 -04:00 committed by GitHub
parent e662fe341b
commit 8001c792e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 336 additions and 233 deletions

18
Cargo.lock generated
View file

@ -737,6 +737,12 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.0.28"
@ -1795,6 +1801,16 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "petgraph"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
dependencies = [
"fixedbitset",
"indexmap 2.0.2",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
@ -2139,9 +2155,11 @@ dependencies = [
"bitflags 2.4.1",
"colored",
"futures",
"insta",
"once_cell",
"pep440_rs 0.3.12",
"pep508_rs",
"petgraph",
"platform-host",
"platform-tags",
"pubgrub",

View file

@ -71,11 +71,11 @@ pub(crate) async fn pip_compile(
derivation_tree.collapse_no_versions();
#[allow(clippy::print_stderr)]
{
eprintln!("{}: {}", "error".red().bold(), "no solution found".bold());
eprintln!(
"{}",
pubgrub::report::DefaultStringReporter::report(&derivation_tree)
);
let report = miette::Report::msg(pubgrub::report::DefaultStringReporter::report(
&derivation_tree,
))
.context("No solution found when resolving dependencies:");
eprint!("{report:?}");
}
return Ok(ExitStatus::Failure);
}
@ -111,7 +111,7 @@ pub(crate) async fn pip_compile(
"{}",
format!("# {}", env::args().join(" ")).green()
)?;
writeln!(writer, "{resolution}")?;
write!(writer, "{resolution}")?;
Ok(ExitStatus::Success)
}

View file

@ -16,7 +16,6 @@ use puffin_installer::{
};
use puffin_interpreter::PythonExecutable;
use puffin_package::package_name::PackageName;
use puffin_resolver::Resolution;
use crate::commands::reporters::{
DownloadReporter, InstallReporter, UnzipReporter, WheelFinderReporter,
@ -93,8 +92,8 @@ pub(crate) async fn sync_requirements(
let client = PypiClientBuilder::default().cache(cache).build();
// Resolve the dependencies.
let resolution = if remote.is_empty() {
Resolution::default()
let remote = if remote.is_empty() {
Vec::new()
} else {
let start = std::time::Instant::now();
@ -115,33 +114,32 @@ pub(crate) async fn sync_requirements(
)?;
resolution
.into_files()
.map(RemoteDistribution::from_file)
.collect::<Result<Vec<_>>>()?
};
// Download any missing distributions.
let staging = tempfile::tempdir()?;
let uncached = resolution
.into_files()
.map(RemoteDistribution::from_file)
.collect::<Result<Vec<_>>>()?;
let downloads = if uncached.is_empty() {
let downloads = if remote.is_empty() {
vec![]
} else {
let start = std::time::Instant::now();
let downloader = puffin_installer::Downloader::new(&client, cache)
.with_reporter(DownloadReporter::from(printer).with_length(uncached.len() as u64));
.with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64));
let downloads = downloader
.download(&uncached, cache.unwrap_or(staging.path()))
.download(&remote, cache.unwrap_or(staging.path()))
.await?;
let s = if uncached.len() == 1 { "" } else { "s" };
let s = if remote.len() == 1 { "" } else { "s" };
writeln!(
printer,
"{}",
format!(
"Downloaded {} in {}",
format!("{} package{}", uncached.len(), s).bold(),
format!("{} package{}", remote.len(), s).bold(),
elapsed(start.elapsed())
)
.dimmed()

View file

@ -28,6 +28,8 @@ thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
waitmap = { workspace = true }
petgraph = "0.6.4"
[dev-dependencies]
once_cell = { version = "1.18.0" }
insta = { version = "1.34.0" }

View file

@ -1,5 +1,5 @@
pub use error::ResolveError;
pub use resolution::{PinnedPackage, Resolution};
pub use resolution::PinnedPackage;
pub use resolver::Resolver;
pub use wheel_finder::{Reporter, WheelFinder};

View file

@ -38,12 +38,6 @@ impl pubgrub::version::Version for PubGrubVersion {
}
}
// impl From<PubGrubVersion> for Range<PubGrubVersion> {
// fn from(value: PubGrubVersion) -> Self {
// Range::from(value)
// }
// }
impl<'a> From<&'a PubGrubVersion> for &'a pep440_rs::Version {
fn from(version: &'a PubGrubVersion) -> Self {
&version.0

View file

@ -1,53 +1,17 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use colored::Colorize;
use petgraph::visit::EdgeRef;
use pubgrub::range::Range;
use pubgrub::solver::{Kind, State};
use pubgrub::type_aliases::SelectedDependencies;
use pep440_rs::Version;
use puffin_client::File;
use puffin_package::package_name::PackageName;
#[derive(Debug, Default)]
pub struct Resolution(BTreeMap<PackageName, PinnedPackage>);
impl Resolution {
/// Create a new resolution from the given pinned packages.
pub(crate) fn new(packages: BTreeMap<PackageName, PinnedPackage>) -> Self {
Self(packages)
}
/// Iterate over the pinned packages in this resolution.
pub fn iter(&self) -> impl Iterator<Item = (&PackageName, &PinnedPackage)> {
self.0.iter()
}
/// Iterate over the wheels in this resolution.
pub fn into_files(self) -> impl Iterator<Item = File> {
self.0.into_values().map(|package| package.file)
}
/// Return the number of pinned packages in this resolution.
pub fn len(&self) -> usize {
self.0.len()
}
/// Return `true` if there are no pinned packages in this resolution.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
/// Write the resolution in the `{name}=={version}` format of requirements.txt that pip uses.
impl std::fmt::Display for Resolution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for (name, pin) in self.iter() {
if !first {
writeln!(f)?;
}
first = false;
write!(f, "{}=={}", name, pin.version())?;
}
Ok(())
}
}
use crate::pubgrub::package::PubGrubPackage;
use crate::pubgrub::version::PubGrubVersion;
/// A package pinned at a specific version.
#[derive(Debug)]
@ -82,3 +46,146 @@ impl PinnedPackage {
&self.file
}
}
/// A set of packages pinned at specific versions.
#[derive(Debug, Default)]
pub struct Resolution(BTreeMap<PackageName, PinnedPackage>);
impl Resolution {
/// Create a new resolution from the given pinned packages.
pub(crate) fn new(packages: BTreeMap<PackageName, PinnedPackage>) -> Self {
Self(packages)
}
/// Iterate over the pinned packages in this resolution.
pub fn iter(&self) -> impl Iterator<Item = (&PackageName, &PinnedPackage)> {
self.0.iter()
}
/// Iterate over the wheels in this resolution.
pub fn into_files(self) -> impl Iterator<Item = File> {
self.0.into_values().map(|package| package.file)
}
/// Return the number of pinned packages in this resolution.
pub fn len(&self) -> usize {
self.0.len()
}
/// Return `true` if there are no pinned packages in this resolution.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
/// A complete resolution graph in which every node represents a pinned package and every edge
/// represents a dependency between two pinned packages.
#[derive(Debug)]
pub struct Graph(petgraph::graph::Graph<PinnedPackage, (), petgraph::Directed>);
impl Graph {
/// Create a new graph from the resolved `PubGrub` state.
pub fn from_state(
selection: &SelectedDependencies<PubGrubPackage, PubGrubVersion>,
pins: &HashMap<PackageName, HashMap<Version, File>>,
state: &State<PubGrubPackage, Range<PubGrubVersion>>,
) -> Self {
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
// write our own graph, given that our requirements are so simple.
let mut graph = petgraph::graph::Graph::with_capacity(selection.len(), selection.len());
// Add every package to the graph.
let mut inverse = HashMap::with_capacity(selection.len());
for (package, version) in selection {
let PubGrubPackage::Package(package_name, None) = package else {
continue;
};
let version = Version::from(version.clone());
let file = pins
.get(package_name)
.and_then(|versions| versions.get(&version))
.unwrap()
.clone();
let pinned_package = PinnedPackage::new(package_name.clone(), version, file);
let index = graph.add_node(pinned_package);
inverse.insert(package_name, index);
}
// Add every edge to the graph.
for (package, version) in selection {
for id in &state.incompatibilities[package] {
if let Kind::FromDependencyOf(self_package, self_version, dependency_package, _) =
&state.incompatibility_store[*id].kind
{
let PubGrubPackage::Package(self_package, None) = self_package else {
continue;
};
let PubGrubPackage::Package(dependency_package, None) = dependency_package
else {
continue;
};
if self_version.contains(version) {
let self_index = &inverse[self_package];
let dependency_index = &inverse[dependency_package];
graph.add_edge(*dependency_index, *self_index, ());
}
}
}
}
Self(graph)
}
/// Return the number of packages in the graph.
pub fn len(&self) -> usize {
self.0.node_count()
}
/// Return `true` if there are no packages in the graph.
pub fn is_empty(&self) -> bool {
self.0.node_count() == 0
}
}
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
impl std::fmt::Display for Graph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Collect and sort all packages.
let mut nodes = self
.0
.node_indices()
.map(|node| (node, &self.0[node]))
.collect::<Vec<_>>();
nodes.sort_unstable_by_key(|(_, package)| package.name());
// Print out the dependency graph.
for (index, package) in nodes {
writeln!(f, "{}=={}", package.name(), package.version())?;
let mut edges = self
.0
.edges(index)
.map(|edge| &self.0[edge.target()])
.collect::<Vec<_>>();
edges.sort_unstable_by_key(|package| package.name());
match edges.len() {
0 => {}
1 => {
for dependency in edges {
writeln!(f, "{}", format!(" # via {}", dependency.name()).green())?;
}
}
_ => {
writeln!(f, "{}", " # via".green())?;
for dependency in edges {
writeln!(f, "{}", format!(" # {}", dependency.name()).green())?;
}
}
}
}
Ok(())
}
}

View file

@ -2,7 +2,7 @@
use std::borrow::Borrow;
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
use std::sync::Arc;
@ -13,7 +13,7 @@ use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt};
use pubgrub::error::PubGrubError;
use pubgrub::range::Range;
use pubgrub::solver::{Incompatibility, State};
use pubgrub::type_aliases::{DependencyConstraints, SelectedDependencies};
use pubgrub::type_aliases::DependencyConstraints;
use tokio::select;
use tracing::{debug, trace};
use waitmap::WaitMap;
@ -30,7 +30,7 @@ use crate::error::ResolveError;
use crate::pubgrub::package::PubGrubPackage;
use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
use crate::pubgrub::{iter_requirements, version_range};
use crate::resolution::{PinnedPackage, Resolution};
use crate::resolution::Graph;
pub struct Resolver<'a> {
requirements: Vec<Requirement>,
@ -61,7 +61,7 @@ impl<'a> Resolver<'a> {
}
/// Resolve a set of requirements into a set of pinned versions.
pub async fn resolve(self) -> Result<Resolution, ResolveError> {
pub async fn resolve(self) -> Result<Graph, ResolveError> {
// A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version
// metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
let (request_sink, request_stream) = futures::channel::mpsc::unbounded();
@ -93,14 +93,14 @@ impl<'a> Resolver<'a> {
}
};
Ok(resolution.into())
Ok(resolution)
}
/// Run the `PubGrub` solver.
async fn solve(
&self,
request_sink: &futures::channel::mpsc::UnboundedSender<Request>,
) -> Result<PubGrubResolution, ResolveError> {
) -> Result<Graph, ResolveError> {
let root = PubGrubPackage::Root;
// Keep track of the packages for which we've requested metadata.
@ -126,7 +126,8 @@ impl<'a> Resolver<'a> {
)
.into());
};
return Ok(PubGrubResolution { selection, pins });
return Ok(Graph::from_state(&selection, &pins, &state));
};
// Choose a package version.
@ -306,8 +307,6 @@ impl<'a> Resolver<'a> {
PubGrubPackage::Root => Ok((package, Some(MIN_VERSION.clone()))),
PubGrubPackage::Package(package_name, _) => {
// Wait for the metadata to be available.
// TODO(charlie): Ideally, we'd choose the first package for which metadata is
// available.
let entry = self.cache.packages.wait(package_name).await.unwrap();
let simple_json = entry.value();
@ -572,33 +571,3 @@ enum Dependencies {
/// Container for all available package versions.
Known(DependencyConstraints<PubGrubPackage, Range<PubGrubVersion>>),
}
#[derive(Debug)]
struct PubGrubResolution {
/// The selected dependencies.
selection: SelectedDependencies<PubGrubPackage, PubGrubVersion>,
/// The selected file (source or built distribution) for each package.
pins: HashMap<PackageName, HashMap<pep440_rs::Version, File>>,
}
impl From<PubGrubResolution> for Resolution {
fn from(value: PubGrubResolution) -> Self {
let mut packages = BTreeMap::new();
for (package, version) in value.selection {
let PubGrubPackage::Package(package_name, None) = package else {
continue;
};
let version = pep440_rs::Version::from(version);
let file = value
.pins
.get(&package_name)
.and_then(|versions| versions.get(&version))
.unwrap()
.clone();
let pinned_package = PinnedPackage::new(package_name.clone(), version, file);
packages.insert(package_name, pinned_package);
}
Resolution::new(packages)
}
}

View file

@ -14,6 +14,8 @@ use puffin_resolver::Resolver;
#[tokio::test]
async fn pylint() -> Result<()> {
colored::control::set_override(false);
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("pylint==2.3.0").unwrap()];
@ -21,22 +23,15 @@ async fn pylint() -> Result<()> {
let resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
let resolution = resolver.resolve().await?;
assert_eq!(
format!("{resolution}"),
[
"astroid==3.0.1",
"isort==6.0.0b2",
"mccabe==0.7.0",
"pylint==2.3.0"
]
.join("\n")
);
insta::assert_display_snapshot!(resolution);
Ok(())
}
#[tokio::test]
async fn black() -> Result<()> {
colored::control::set_override(false);
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
@ -44,24 +39,15 @@ async fn black() -> Result<()> {
let resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
let resolution = resolver.resolve().await?;
assert_eq!(
format!("{resolution}"),
[
"black==23.9.1",
"click==8.1.7",
"mypy-extensions==1.0.0",
"packaging==23.2",
"pathspec==0.11.2",
"platformdirs==3.11.0"
]
.join("\n")
);
insta::assert_display_snapshot!(resolution);
Ok(())
}
#[tokio::test]
async fn black_colorama() -> Result<()> {
colored::control::set_override(false);
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()];
@ -69,25 +55,15 @@ async fn black_colorama() -> Result<()> {
let resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
let resolution = resolver.resolve().await?;
assert_eq!(
format!("{resolution}"),
[
"black==23.9.1",
"click==8.1.7",
"colorama==0.4.6",
"mypy-extensions==1.0.0",
"packaging==23.2",
"pathspec==0.11.2",
"platformdirs==3.11.0"
]
.join("\n")
);
insta::assert_display_snapshot!(resolution);
Ok(())
}
#[tokio::test]
async fn black_python_310() -> Result<()> {
colored::control::set_override(false);
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
@ -95,20 +71,7 @@ async fn black_python_310() -> Result<()> {
let resolver = Resolver::new(requirements, constraints, &MARKERS_310, &TAGS_310, &client);
let resolution = resolver.resolve().await?;
assert_eq!(
format!("{resolution}"),
[
"black==23.9.1",
"click==8.1.7",
"mypy-extensions==1.0.0",
"packaging==23.2",
"pathspec==0.11.2",
"platformdirs==3.11.0",
"tomli==2.0.1",
"typing-extensions==4.8.0"
]
.join("\n")
);
insta::assert_display_snapshot!(resolution);
Ok(())
}
@ -117,6 +80,8 @@ async fn black_python_310() -> Result<()> {
/// respected.
#[tokio::test]
async fn black_mypy_extensions() -> Result<()> {
colored::control::set_override(false);
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
@ -124,18 +89,7 @@ async fn black_mypy_extensions() -> Result<()> {
let resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
let resolution = resolver.resolve().await?;
assert_eq!(
format!("{resolution}"),
[
"black==23.9.1",
"click==8.1.7",
"mypy-extensions==0.4.3",
"packaging==23.2",
"pathspec==0.11.2",
"platformdirs==3.11.0"
]
.join("\n")
);
insta::assert_display_snapshot!(resolution);
Ok(())
}
@ -144,6 +98,8 @@ async fn black_mypy_extensions() -> Result<()> {
/// ignored when resolving constraints.
#[tokio::test]
async fn black_mypy_extensions_extra() -> Result<()> {
colored::control::set_override(false);
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
@ -151,18 +107,7 @@ async fn black_mypy_extensions_extra() -> Result<()> {
let resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
let resolution = resolver.resolve().await?;
assert_eq!(
format!("{resolution}"),
[
"black==23.9.1",
"click==8.1.7",
"mypy-extensions==0.4.3",
"packaging==23.2",
"pathspec==0.11.2",
"platformdirs==3.11.0"
]
.join("\n")
);
insta::assert_display_snapshot!(resolution);
Ok(())
}
@ -171,6 +116,8 @@ async fn black_mypy_extensions_extra() -> Result<()> {
/// introduce new dependencies.
#[tokio::test]
async fn black_flake8() -> Result<()> {
colored::control::set_override(false);
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
@ -178,49 +125,7 @@ async fn black_flake8() -> Result<()> {
let resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
let resolution = resolver.resolve().await?;
assert_eq!(
format!("{resolution}"),
[
"black==23.9.1",
"click==8.1.7",
"mypy-extensions==1.0.0",
"packaging==23.2",
"pathspec==0.11.2",
"platformdirs==3.11.0"
]
.join("\n")
);
Ok(())
}
#[tokio::test]
async fn htmldate() -> Result<()> {
let client = PypiClientBuilder::default().build();
let requirements = vec![Requirement::from_str("htmldate<=1.5.0").unwrap()];
let constraints = vec![];
let resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
let resolution = resolver.resolve().await?;
// Resolves to `htmldate==1.4.3` (rather than `htmldate==1.5.2`) because `htmldate==1.5.2` has
// a dependency on `lxml` versions that don't provide universal wheels.
assert_eq!(
format!("{resolution}"),
[
"charset-normalizer==3.3.0",
"dateparser==1.1.8",
"htmldate==1.4.3",
"lxml==4.9.3",
"python-dateutil==2.8.2",
"pytz==2023.3.post1",
"regex==2023.10.3",
"six==1.16.0",
"tzlocal==5.1",
"urllib3==2.0.7"
]
.join("\n")
);
insta::assert_display_snapshot!(resolution);
Ok(())
}

View file

@ -0,0 +1,16 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
black==23.9.1
click==8.1.7
# via black
mypy-extensions==1.0.0
# via black
packaging==23.2
# via black
pathspec==0.11.2
# via black
platformdirs==3.11.0
# via black

View file

@ -0,0 +1,17 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
black==23.9.1
click==8.1.7
# via black
colorama==0.4.6
mypy-extensions==1.0.0
# via black
packaging==23.2
# via black
pathspec==0.11.2
# via black
platformdirs==3.11.0
# via black

View file

@ -0,0 +1,16 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
black==23.9.1
click==8.1.7
# via black
mypy-extensions==1.0.0
# via black
packaging==23.2
# via black
pathspec==0.11.2
# via black
platformdirs==3.11.0
# via black

View file

@ -0,0 +1,16 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
black==23.9.1
click==8.1.7
# via black
mypy-extensions==0.4.3
# via black
packaging==23.2
# via black
pathspec==0.11.2
# via black
platformdirs==3.11.0
# via black

View file

@ -0,0 +1,16 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
black==23.9.1
click==8.1.7
# via black
mypy-extensions==0.4.3
# via black
packaging==23.2
# via black
pathspec==0.11.2
# via black
platformdirs==3.11.0
# via black

View file

@ -0,0 +1,20 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
black==23.9.1
click==8.1.7
# via black
mypy-extensions==1.0.0
# via black
packaging==23.2
# via black
pathspec==0.11.2
# via black
platformdirs==3.11.0
# via black
tomli==2.0.1
# via black
typing-extensions==4.8.0
# via black

View file

@ -0,0 +1,12 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
astroid==3.0.1
# via pylint
isort==6.0.0b2
# via pylint
mccabe==0.7.0
# via pylint
pylint==2.3.0

View file

@ -24,7 +24,7 @@ pub struct State<P: Package, VS: VersionSet> {
root_package: P,
root_version: VS::V,
incompatibilities: Map<P, Vec<IncompId<P, VS>>>,
pub incompatibilities: Map<P, Vec<IncompId<P, VS>>>,
/// Store the ids of incompatibilities that are already contradicted
/// and will stay that way until the next conflict and backtrack is operated.

View file

@ -30,15 +30,15 @@ use crate::version_set::VersionSet;
/// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility).
#[derive(Debug, Clone)]
pub struct Incompatibility<P: Package, VS: VersionSet> {
package_terms: SmallMap<P, Term<VS>>,
kind: Kind<P, VS>,
pub package_terms: SmallMap<P, Term<VS>>,
pub kind: Kind<P, VS>,
}
/// Type alias of unique identifiers for incompatibilities.
pub type IncompId<P, VS> = Id<Incompatibility<P, VS>>;
#[derive(Debug, Clone)]
enum Kind<P: Package, VS: VersionSet> {
pub enum Kind<P: Package, VS: VersionSet> {
/// Initial incompatibility aiming at picking the root package for the first decision.
NotRoot(P, VS::V),
/// There are no versions in the given range for this package.

View file

@ -215,9 +215,6 @@
//! with a cache, you may want to know that some versions
//! do not exist in your cache.
#![allow(clippy::rc_buffer)]
#![warn(missing_docs)]
pub mod error;
pub mod package;
pub mod range;

View file

@ -74,7 +74,7 @@ use std::error::Error;
use crate::error::PubGrubError;
pub use crate::internal::core::State;
pub use crate::internal::incompatibility::Incompatibility;
pub use crate::internal::incompatibility::{Incompatibility, Kind};
use crate::package::Package;
use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies};
use crate::version_set::VersionSet;