mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
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:
parent
e662fe341b
commit
8001c792e7
20 changed files with 336 additions and 233 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -737,6 +737,12 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
|
@ -1795,6 +1801,16 @@ version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
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]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
|
@ -2139,9 +2155,11 @@ dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
"colored",
|
"colored",
|
||||||
"futures",
|
"futures",
|
||||||
|
"insta",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pep440_rs 0.3.12",
|
"pep440_rs 0.3.12",
|
||||||
"pep508_rs",
|
"pep508_rs",
|
||||||
|
"petgraph",
|
||||||
"platform-host",
|
"platform-host",
|
||||||
"platform-tags",
|
"platform-tags",
|
||||||
"pubgrub",
|
"pubgrub",
|
||||||
|
|
|
@ -71,11 +71,11 @@ pub(crate) async fn pip_compile(
|
||||||
derivation_tree.collapse_no_versions();
|
derivation_tree.collapse_no_versions();
|
||||||
#[allow(clippy::print_stderr)]
|
#[allow(clippy::print_stderr)]
|
||||||
{
|
{
|
||||||
eprintln!("{}: {}", "error".red().bold(), "no solution found".bold());
|
let report = miette::Report::msg(pubgrub::report::DefaultStringReporter::report(
|
||||||
eprintln!(
|
&derivation_tree,
|
||||||
"{}",
|
))
|
||||||
pubgrub::report::DefaultStringReporter::report(&derivation_tree)
|
.context("No solution found when resolving dependencies:");
|
||||||
);
|
eprint!("{report:?}");
|
||||||
}
|
}
|
||||||
return Ok(ExitStatus::Failure);
|
return Ok(ExitStatus::Failure);
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ pub(crate) async fn pip_compile(
|
||||||
"{}",
|
"{}",
|
||||||
format!("# {}", env::args().join(" ")).green()
|
format!("# {}", env::args().join(" ")).green()
|
||||||
)?;
|
)?;
|
||||||
writeln!(writer, "{resolution}")?;
|
write!(writer, "{resolution}")?;
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ use puffin_installer::{
|
||||||
};
|
};
|
||||||
use puffin_interpreter::PythonExecutable;
|
use puffin_interpreter::PythonExecutable;
|
||||||
use puffin_package::package_name::PackageName;
|
use puffin_package::package_name::PackageName;
|
||||||
use puffin_resolver::Resolution;
|
|
||||||
|
|
||||||
use crate::commands::reporters::{
|
use crate::commands::reporters::{
|
||||||
DownloadReporter, InstallReporter, UnzipReporter, WheelFinderReporter,
|
DownloadReporter, InstallReporter, UnzipReporter, WheelFinderReporter,
|
||||||
|
@ -93,8 +92,8 @@ pub(crate) async fn sync_requirements(
|
||||||
let client = PypiClientBuilder::default().cache(cache).build();
|
let client = PypiClientBuilder::default().cache(cache).build();
|
||||||
|
|
||||||
// Resolve the dependencies.
|
// Resolve the dependencies.
|
||||||
let resolution = if remote.is_empty() {
|
let remote = if remote.is_empty() {
|
||||||
Resolution::default()
|
Vec::new()
|
||||||
} else {
|
} else {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
|
@ -115,33 +114,32 @@ pub(crate) async fn sync_requirements(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
resolution
|
resolution
|
||||||
|
.into_files()
|
||||||
|
.map(RemoteDistribution::from_file)
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Download any missing distributions.
|
// Download any missing distributions.
|
||||||
let staging = tempfile::tempdir()?;
|
let staging = tempfile::tempdir()?;
|
||||||
let uncached = resolution
|
let downloads = if remote.is_empty() {
|
||||||
.into_files()
|
|
||||||
.map(RemoteDistribution::from_file)
|
|
||||||
.collect::<Result<Vec<_>>>()?;
|
|
||||||
let downloads = if uncached.is_empty() {
|
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
let downloader = puffin_installer::Downloader::new(&client, cache)
|
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
|
let downloads = downloader
|
||||||
.download(&uncached, cache.unwrap_or(staging.path()))
|
.download(&remote, cache.unwrap_or(staging.path()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let s = if uncached.len() == 1 { "" } else { "s" };
|
let s = if remote.len() == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer,
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
format!(
|
format!(
|
||||||
"Downloaded {} in {}",
|
"Downloaded {} in {}",
|
||||||
format!("{} package{}", uncached.len(), s).bold(),
|
format!("{} package{}", remote.len(), s).bold(),
|
||||||
elapsed(start.elapsed())
|
elapsed(start.elapsed())
|
||||||
)
|
)
|
||||||
.dimmed()
|
.dimmed()
|
||||||
|
|
|
@ -28,6 +28,8 @@ thiserror = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
waitmap = { workspace = true }
|
waitmap = { workspace = true }
|
||||||
|
petgraph = "0.6.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
once_cell = { version = "1.18.0" }
|
once_cell = { version = "1.18.0" }
|
||||||
|
insta = { version = "1.34.0" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
pub use error::ResolveError;
|
pub use error::ResolveError;
|
||||||
pub use resolution::{PinnedPackage, Resolution};
|
pub use resolution::PinnedPackage;
|
||||||
pub use resolver::Resolver;
|
pub use resolver::Resolver;
|
||||||
pub use wheel_finder::{Reporter, WheelFinder};
|
pub use wheel_finder::{Reporter, WheelFinder};
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
impl<'a> From<&'a PubGrubVersion> for &'a pep440_rs::Version {
|
||||||
fn from(version: &'a PubGrubVersion) -> Self {
|
fn from(version: &'a PubGrubVersion) -> Self {
|
||||||
&version.0
|
&version.0
|
||||||
|
|
|
@ -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 pep440_rs::Version;
|
||||||
use puffin_client::File;
|
use puffin_client::File;
|
||||||
use puffin_package::package_name::PackageName;
|
use puffin_package::package_name::PackageName;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
use crate::pubgrub::package::PubGrubPackage;
|
||||||
pub struct Resolution(BTreeMap<PackageName, PinnedPackage>);
|
use crate::pubgrub::version::PubGrubVersion;
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A package pinned at a specific version.
|
/// A package pinned at a specific version.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -82,3 +46,146 @@ impl PinnedPackage {
|
||||||
&self.file
|
&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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt};
|
||||||
use pubgrub::error::PubGrubError;
|
use pubgrub::error::PubGrubError;
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
use pubgrub::solver::{Incompatibility, State};
|
use pubgrub::solver::{Incompatibility, State};
|
||||||
use pubgrub::type_aliases::{DependencyConstraints, SelectedDependencies};
|
use pubgrub::type_aliases::DependencyConstraints;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
use waitmap::WaitMap;
|
use waitmap::WaitMap;
|
||||||
|
@ -30,7 +30,7 @@ use crate::error::ResolveError;
|
||||||
use crate::pubgrub::package::PubGrubPackage;
|
use crate::pubgrub::package::PubGrubPackage;
|
||||||
use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
|
use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
|
||||||
use crate::pubgrub::{iter_requirements, version_range};
|
use crate::pubgrub::{iter_requirements, version_range};
|
||||||
use crate::resolution::{PinnedPackage, Resolution};
|
use crate::resolution::Graph;
|
||||||
|
|
||||||
pub struct Resolver<'a> {
|
pub struct Resolver<'a> {
|
||||||
requirements: Vec<Requirement>,
|
requirements: Vec<Requirement>,
|
||||||
|
@ -61,7 +61,7 @@ impl<'a> Resolver<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a set of requirements into a set of pinned versions.
|
/// 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
|
// 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).
|
// metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
|
||||||
let (request_sink, request_stream) = futures::channel::mpsc::unbounded();
|
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.
|
/// Run the `PubGrub` solver.
|
||||||
async fn solve(
|
async fn solve(
|
||||||
&self,
|
&self,
|
||||||
request_sink: &futures::channel::mpsc::UnboundedSender<Request>,
|
request_sink: &futures::channel::mpsc::UnboundedSender<Request>,
|
||||||
) -> Result<PubGrubResolution, ResolveError> {
|
) -> Result<Graph, ResolveError> {
|
||||||
let root = PubGrubPackage::Root;
|
let root = PubGrubPackage::Root;
|
||||||
|
|
||||||
// Keep track of the packages for which we've requested metadata.
|
// Keep track of the packages for which we've requested metadata.
|
||||||
|
@ -126,7 +126,8 @@ impl<'a> Resolver<'a> {
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
};
|
};
|
||||||
return Ok(PubGrubResolution { selection, pins });
|
|
||||||
|
return Ok(Graph::from_state(&selection, &pins, &state));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Choose a package version.
|
// Choose a package version.
|
||||||
|
@ -306,8 +307,6 @@ impl<'a> Resolver<'a> {
|
||||||
PubGrubPackage::Root => Ok((package, Some(MIN_VERSION.clone()))),
|
PubGrubPackage::Root => Ok((package, Some(MIN_VERSION.clone()))),
|
||||||
PubGrubPackage::Package(package_name, _) => {
|
PubGrubPackage::Package(package_name, _) => {
|
||||||
// Wait for the metadata to be available.
|
// 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 entry = self.cache.packages.wait(package_name).await.unwrap();
|
||||||
let simple_json = entry.value();
|
let simple_json = entry.value();
|
||||||
|
|
||||||
|
@ -572,33 +571,3 @@ enum Dependencies {
|
||||||
/// Container for all available package versions.
|
/// Container for all available package versions.
|
||||||
Known(DependencyConstraints<PubGrubPackage, Range<PubGrubVersion>>),
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ use puffin_resolver::Resolver;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn pylint() -> Result<()> {
|
async fn pylint() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let client = PypiClientBuilder::default().build();
|
let client = PypiClientBuilder::default().build();
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("pylint==2.3.0").unwrap()];
|
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 resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
assert_eq!(
|
insta::assert_display_snapshot!(resolution);
|
||||||
format!("{resolution}"),
|
|
||||||
[
|
|
||||||
"astroid==3.0.1",
|
|
||||||
"isort==6.0.0b2",
|
|
||||||
"mccabe==0.7.0",
|
|
||||||
"pylint==2.3.0"
|
|
||||||
]
|
|
||||||
.join("\n")
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn black() -> Result<()> {
|
async fn black() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let client = PypiClientBuilder::default().build();
|
let client = PypiClientBuilder::default().build();
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
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 resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
assert_eq!(
|
insta::assert_display_snapshot!(resolution);
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn black_colorama() -> Result<()> {
|
async fn black_colorama() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let client = PypiClientBuilder::default().build();
|
let client = PypiClientBuilder::default().build();
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()];
|
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 resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
assert_eq!(
|
insta::assert_display_snapshot!(resolution);
|
||||||
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")
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn black_python_310() -> Result<()> {
|
async fn black_python_310() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let client = PypiClientBuilder::default().build();
|
let client = PypiClientBuilder::default().build();
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
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 resolver = Resolver::new(requirements, constraints, &MARKERS_310, &TAGS_310, &client);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
assert_eq!(
|
insta::assert_display_snapshot!(resolution);
|
||||||
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")
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -117,6 +80,8 @@ async fn black_python_310() -> Result<()> {
|
||||||
/// respected.
|
/// respected.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn black_mypy_extensions() -> Result<()> {
|
async fn black_mypy_extensions() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let client = PypiClientBuilder::default().build();
|
let client = PypiClientBuilder::default().build();
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
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 resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
assert_eq!(
|
insta::assert_display_snapshot!(resolution);
|
||||||
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")
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -144,6 +98,8 @@ async fn black_mypy_extensions() -> Result<()> {
|
||||||
/// ignored when resolving constraints.
|
/// ignored when resolving constraints.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn black_mypy_extensions_extra() -> Result<()> {
|
async fn black_mypy_extensions_extra() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let client = PypiClientBuilder::default().build();
|
let client = PypiClientBuilder::default().build();
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
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 resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
assert_eq!(
|
insta::assert_display_snapshot!(resolution);
|
||||||
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")
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -171,6 +116,8 @@ async fn black_mypy_extensions_extra() -> Result<()> {
|
||||||
/// introduce new dependencies.
|
/// introduce new dependencies.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn black_flake8() -> Result<()> {
|
async fn black_flake8() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
let client = PypiClientBuilder::default().build();
|
let client = PypiClientBuilder::default().build();
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
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 resolver = Resolver::new(requirements, constraints, &MARKERS_311, &TAGS_311, &client);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
assert_eq!(
|
insta::assert_display_snapshot!(resolution);
|
||||||
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")
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
16
crates/puffin-resolver/tests/snapshots/resolver__black.snap
Normal file
16
crates/puffin-resolver/tests/snapshots/resolver__black.snap
Normal 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
12
crates/puffin-resolver/tests/snapshots/resolver__pylint.snap
Normal file
12
crates/puffin-resolver/tests/snapshots/resolver__pylint.snap
Normal 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
|
||||||
|
|
2
vendor/pubgrub/src/internal/core.rs
vendored
2
vendor/pubgrub/src/internal/core.rs
vendored
|
@ -24,7 +24,7 @@ pub struct State<P: Package, VS: VersionSet> {
|
||||||
root_package: P,
|
root_package: P,
|
||||||
root_version: VS::V,
|
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
|
/// Store the ids of incompatibilities that are already contradicted
|
||||||
/// and will stay that way until the next conflict and backtrack is operated.
|
/// and will stay that way until the next conflict and backtrack is operated.
|
||||||
|
|
|
@ -30,15 +30,15 @@ use crate::version_set::VersionSet;
|
||||||
/// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility).
|
/// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility).
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Incompatibility<P: Package, VS: VersionSet> {
|
pub struct Incompatibility<P: Package, VS: VersionSet> {
|
||||||
package_terms: SmallMap<P, Term<VS>>,
|
pub package_terms: SmallMap<P, Term<VS>>,
|
||||||
kind: Kind<P, VS>,
|
pub kind: Kind<P, VS>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type alias of unique identifiers for incompatibilities.
|
/// Type alias of unique identifiers for incompatibilities.
|
||||||
pub type IncompId<P, VS> = Id<Incompatibility<P, VS>>;
|
pub type IncompId<P, VS> = Id<Incompatibility<P, VS>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[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.
|
/// Initial incompatibility aiming at picking the root package for the first decision.
|
||||||
NotRoot(P, VS::V),
|
NotRoot(P, VS::V),
|
||||||
/// There are no versions in the given range for this package.
|
/// There are no versions in the given range for this package.
|
||||||
|
|
3
vendor/pubgrub/src/lib.rs
vendored
3
vendor/pubgrub/src/lib.rs
vendored
|
@ -215,9 +215,6 @@
|
||||||
//! with a cache, you may want to know that some versions
|
//! with a cache, you may want to know that some versions
|
||||||
//! do not exist in your cache.
|
//! do not exist in your cache.
|
||||||
|
|
||||||
#![allow(clippy::rc_buffer)]
|
|
||||||
#![warn(missing_docs)]
|
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod package;
|
pub mod package;
|
||||||
pub mod range;
|
pub mod range;
|
||||||
|
|
2
vendor/pubgrub/src/solver.rs
vendored
2
vendor/pubgrub/src/solver.rs
vendored
|
@ -74,7 +74,7 @@ use std::error::Error;
|
||||||
|
|
||||||
use crate::error::PubGrubError;
|
use crate::error::PubGrubError;
|
||||||
pub use crate::internal::core::State;
|
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::package::Package;
|
||||||
use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies};
|
use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies};
|
||||||
use crate::version_set::VersionSet;
|
use crate::version_set::VersionSet;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue