Replace PubGrubDependencies by PubGrubDependency (#4481)

In the last PR (#4430), we flatten the requirements. In the next PR
(#4435), we want to pass `Url` around next to `PubGrubPackage` and
`Range<Version>` to keep track of which `Requirement`s added a url
across forking. This PR is a refactoring split out from #4435 that rolls
the dependency conversion into a single iterator and introduces a new
`PubGrubDependency` struct as abstraction over `(PubGrubPackage,
Range<Version>)` (or `(PubGrubPackage, Range<Version>,
VerbatimParsedUrl)` in the next PR), and it removes the now unnecessary
`PubGrubDependencies` abstraction.
This commit is contained in:
konsti 2024-06-26 00:11:52 +02:00 committed by GitHub
parent e6103dcab1
commit ff2f927579
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 122 additions and 121 deletions

View file

@ -15,76 +15,59 @@ use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
use crate::resolver::{Locals, Urls};
use crate::{PubGrubSpecifier, ResolveError};
#[derive(Debug)]
pub struct PubGrubDependencies(Vec<(PubGrubPackage, Range<Version>)>);
#[derive(Clone, Debug)]
pub(crate) struct PubGrubDependency {
pub(crate) package: PubGrubPackage,
pub(crate) version: Range<Version>,
}
impl PubGrubDependencies {
/// Generate a set of PubGrub dependencies from a set of requirements.
#[allow(clippy::too_many_arguments)]
pub(crate) fn from_requirements(
flattened_requirements: &[&Requirement],
source_name: Option<&PackageName>,
urls: &Urls,
locals: &Locals,
git: &GitResolver,
) -> Result<Self, ResolveError> {
let mut dependencies = Vec::new();
for requirement in flattened_requirements {
// Add the package, plus any extra variants.
for result in std::iter::once(PubGrubRequirement::from_requirement(
requirement,
None,
urls,
locals,
git,
))
.chain(requirement.extras.clone().into_iter().map(|extra| {
PubGrubRequirement::from_requirement(requirement, Some(extra), urls, locals, git)
})) {
let PubGrubRequirement { package, version } = result?;
impl PubGrubDependency {
pub(crate) fn from_requirement<'a>(
requirement: &'a Requirement,
source_name: Option<&'a PackageName>,
urls: &'a Urls,
locals: &'a Locals,
git: &'a GitResolver,
) -> impl Iterator<Item = Result<Self, ResolveError>> + 'a {
// Add the package, plus any extra variants.
std::iter::once(None)
.chain(requirement.extras.clone().into_iter().map(Some))
.map(|extra| {
PubGrubRequirement::from_requirement(requirement, extra, urls, locals, git)
})
.filter_map_ok(move |pubgrub_requirement| {
let PubGrubRequirement { package, version } = pubgrub_requirement;
match &*package {
PubGrubPackageInner::Package { name, .. } => {
// Detect self-dependencies.
if source_name.is_some_and(|source_name| source_name == name) {
warn!("{name} has a dependency on itself");
continue;
return None;
}
dependencies.push((package.clone(), version.clone()));
}
PubGrubPackageInner::Marker { .. } => {
dependencies.push((package.clone(), version.clone()));
Some(PubGrubDependency {
package: package.clone(),
version: version.clone(),
})
}
PubGrubPackageInner::Marker { .. } => Some(PubGrubDependency {
package: package.clone(),
version: version.clone(),
}),
PubGrubPackageInner::Extra { name, .. } => {
debug_assert!(
!source_name.is_some_and(|source_name| source_name == name),
"extras not flattened for {name}"
);
dependencies.push((package.clone(), version.clone()));
Some(PubGrubDependency {
package: package.clone(),
version: version.clone(),
})
}
_ => {}
_ => None,
}
}
}
Ok(Self(dependencies))
}
/// Add a [`PubGrubPackage`] and [`PubGrubVersion`] range into the dependencies.
pub(crate) fn push(&mut self, package: PubGrubPackage, version: Range<Version>) {
self.0.push((package, version));
}
/// Iterate over the dependencies.
pub(crate) fn iter(&self) -> impl Iterator<Item = &(PubGrubPackage, Range<Version>)> {
self.0.iter()
}
}
/// Convert a [`PubGrubDependencies`] to a [`DependencyConstraints`].
impl From<PubGrubDependencies> for Vec<(PubGrubPackage, Range<Version>)> {
fn from(dependencies: PubGrubDependencies) -> Self {
dependencies.0
})
}
}

View file

@ -1,4 +1,4 @@
pub(crate) use crate::pubgrub::dependencies::PubGrubDependencies;
pub(crate) use crate::pubgrub::dependencies::PubGrubDependency;
pub(crate) use crate::pubgrub::distribution::PubGrubDistribution;
pub(crate) use crate::pubgrub::package::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority};

View file

@ -44,8 +44,8 @@ use crate::manifest::Manifest;
use crate::pins::FilePins;
use crate::preferences::Preferences;
use crate::pubgrub::{
PubGrubDependencies, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner,
PubGrubPriorities, PubGrubPython, PubGrubSpecifier,
PubGrubDependency, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
PubGrubPython, PubGrubSpecifier,
};
use crate::python_requirement::PythonRequirement;
use crate::resolution::ResolutionGraph;
@ -943,13 +943,18 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
markers,
);
let dependencies = PubGrubDependencies::from_requirements(
&requirements,
None,
&self.urls,
&self.locals,
&self.git,
)?;
let dependencies = requirements
.iter()
.flat_map(|requirement| {
PubGrubDependency::from_requirement(
requirement,
None,
&self.urls,
&self.locals,
&self.git,
)
})
.collect::<Result<Vec<_>, _>>()?;
(dependencies, None)
}
@ -1075,13 +1080,18 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
markers,
);
let mut dependencies = PubGrubDependencies::from_requirements(
&requirements,
Some(name),
&self.urls,
&self.locals,
&self.git,
)?;
let mut dependencies = requirements
.iter()
.flat_map(|requirement| {
PubGrubDependency::from_requirement(
requirement,
Some(name),
&self.urls,
&self.locals,
&self.git,
)
})
.collect::<Result<Vec<_>, _>>()?;
// If a package has metadata for an enabled dependency group,
// add a dependency from it to the same package with the group
// enabled.
@ -1090,15 +1100,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
if !metadata.dev_dependencies.contains_key(group) {
continue;
}
dependencies.push(
PubGrubPackage::from(PubGrubPackageInner::Dev {
dependencies.push(PubGrubDependency {
package: PubGrubPackage::from(PubGrubPackageInner::Dev {
name: name.clone(),
dev: group.clone(),
marker: marker.clone(),
url: url.clone(),
}),
Range::singleton(version.clone()),
);
version: Range::singleton(version.clone()),
});
}
}
@ -1111,17 +1121,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
return Ok(Dependencies::Available(
[None, Some(marker)]
.into_iter()
.map(move |marker| {
(
PubGrubPackage::from(PubGrubPackageInner::Package {
name: name.clone(),
extra: None,
dev: None,
marker: marker.cloned(),
url: url.clone(),
}),
Range::singleton(version.clone()),
)
.map(move |marker| PubGrubDependency {
package: PubGrubPackage::from(PubGrubPackageInner::Package {
name: name.clone(),
extra: None,
dev: None,
marker: marker.cloned(),
url: url.clone(),
}),
version: Range::singleton(version.clone()),
})
.collect(),
))
@ -1139,18 +1147,18 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
.into_iter()
.dedup()
.flat_map(move |marker| {
[None, Some(extra)].into_iter().map(move |extra| {
(
PubGrubPackage::from(PubGrubPackageInner::Package {
[None, Some(extra)]
.into_iter()
.map(move |extra| PubGrubDependency {
package: PubGrubPackage::from(PubGrubPackageInner::Package {
name: name.clone(),
extra: extra.cloned(),
dev: None,
marker: marker.cloned(),
url: url.clone(),
}),
Range::singleton(version.clone()),
)
})
version: Range::singleton(version.clone()),
})
})
.collect(),
))
@ -1169,40 +1177,41 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
.into_iter()
.dedup()
.flat_map(move |marker| {
[None, Some(dev)].into_iter().map(move |dev| {
(
PubGrubPackage::from(PubGrubPackageInner::Package {
[None, Some(dev)]
.into_iter()
.map(move |dev| PubGrubDependency {
package: PubGrubPackage::from(PubGrubPackageInner::Package {
name: name.clone(),
extra: None,
dev: dev.cloned(),
marker: marker.cloned(),
url: url.clone(),
}),
Range::singleton(version.clone()),
)
})
version: Range::singleton(version.clone()),
})
})
.collect(),
))
}
};
for (dep_package, dep_version) in dependencies.iter() {
for dependency in &dependencies {
let PubGrubDependency { package, version } = dependency;
if let Some(name) = name {
debug!("Adding transitive dependency for {name}=={version}: {dep_package}{dep_version}");
debug!("Adding transitive dependency for {name}=={version}: {package}{version}");
} else {
// A dependency from the root package or requirements.txt.
debug!("Adding direct dependency: {dep_package}{dep_version}");
debug!("Adding direct dependency: {package}{version}");
}
// Update the package priorities.
priorities.insert(dep_package, dep_version);
priorities.insert(package, version);
// Emit a request to fetch the metadata for this package.
self.visit_package(dep_package, request_sink)?;
self.visit_package(package, request_sink)?;
}
Ok(Dependencies::Available(dependencies.into()))
Ok(Dependencies::Available(dependencies))
}
/// The regular and dev dependencies filtered by Python version and the markers of this fork,
@ -1648,12 +1657,12 @@ impl SolveState {
fn add_package_version_dependencies(
&mut self,
version: &Version,
dependencies: Vec<(PubGrubPackage, Range<Version>)>,
dependencies: Vec<PubGrubDependency>,
prefetcher: &BatchPrefetcher,
) -> Result<(), ResolveError> {
if dependencies
.iter()
.any(|(dependency, _)| dependency == &self.next)
.any(|dependency| dependency.package == self.next)
{
if enabled!(Level::DEBUG) {
prefetcher.log_tried_versions();
@ -1667,7 +1676,10 @@ impl SolveState {
self.pubgrub.add_package_version_dependencies(
self.next.clone(),
version.clone(),
dependencies,
dependencies.into_iter().map(|dependency| {
let PubGrubDependency { package, version } = dependency;
(package, version)
}),
);
Ok(())
}
@ -1994,7 +2006,6 @@ enum Response {
/// This effectively distills the dependency metadata of a package down into
/// its pubgrub specific constituent parts: each dependency package has a range
/// of possible versions.
#[derive(Clone)]
enum Dependencies {
/// Package dependencies are not available.
Unavailable(UnavailableVersion),
@ -2003,7 +2014,7 @@ enum Dependencies {
/// Note that in universal mode, it is possible and allowed for multiple
/// `PubGrubPackage` values in this list to have the same package name.
/// These conflicts are resolved via `Dependencies::fork`.
Available(Vec<(PubGrubPackage, Range<Version>)>),
Available(Vec<PubGrubDependency>),
}
impl Dependencies {
@ -2022,13 +2033,16 @@ impl Dependencies {
};
let mut by_name: FxHashMap<&PackageName, PossibleForks> = FxHashMap::default();
for (index, (ref pkg, _)) in deps.iter().enumerate() {
for (index, dependency) in deps.iter().enumerate() {
// A root can never be a dependency of another package,
// and a `Python` pubgrub package is never returned by
// `get_dependencies`. So a pubgrub package always has a
// name in this context.
let name = pkg.name().expect("dependency always has a name");
let marker = pkg.marker();
let name = dependency
.package
.name()
.expect("dependency always has a name");
let marker = dependency.package.marker();
let Some(marker) = marker else {
// When no marker is found, it implies there is a dependency on
// this package that is unconditional with respect to marker
@ -2157,7 +2171,7 @@ enum ForkedDependencies {
/// No forking occurred.
///
/// This is the same as `Dependencies::Available`.
Unforked(Vec<(PubGrubPackage, Range<Version>)>),
Unforked(Vec<PubGrubDependency>),
/// Forked containers for all available package versions.
///
/// Note that there is always at least two forks. If there would
@ -2189,7 +2203,7 @@ struct Fork {
/// they should use `add_forked_package` or `add_nonfork_package`. Namely,
/// it should be impossible for a package with a marker expression that is
/// disjoint from the marker expression on this fork to be added.
dependencies: Vec<(PubGrubPackage, Range<Version>)>,
dependencies: Vec<PubGrubDependency>,
/// The markers that provoked this fork.
///
/// So in the example above, the `a<2` fork would have
@ -2215,15 +2229,16 @@ impl Fork {
/// can cause the resolver to explore otherwise impossible resolutions,
/// and also run into conflicts (and thus a failed resolution) that don't
/// actually exist.
fn add_forked_package(&mut self, (pkg, range): (PubGrubPackage, Range<Version>)) {
fn add_forked_package(&mut self, dependency: PubGrubDependency) {
// OK because a package without a marker is unconditional and
// thus can never provoke a fork.
let marker = pkg
let marker = dependency
.package
.marker()
.cloned()
.expect("forked package always has a marker");
self.remove_disjoint_packages(&marker);
self.dependencies.push((pkg, range));
self.dependencies.push(dependency);
// Each marker expression in a single fork is,
// by construction, overlapping with at least
// one other marker expression in this fork.
@ -2239,14 +2254,15 @@ impl Fork {
///
/// It is only added if the markers on the given package are not disjoint
/// with this fork's markers.
fn add_nonfork_package(&mut self, (pkg, range): (PubGrubPackage, Range<Version>)) {
fn add_nonfork_package(&mut self, dependency: PubGrubDependency) {
use crate::marker::is_disjoint;
if pkg
if dependency
.package
.marker()
.map_or(true, |marker| !is_disjoint(marker, &self.markers))
{
self.dependencies.push((pkg, range));
self.dependencies.push(dependency);
}
}
@ -2255,8 +2271,10 @@ impl Fork {
fn remove_disjoint_packages(&mut self, fork_marker: &MarkerTree) {
use crate::marker::is_disjoint;
self.dependencies.retain(|(pkg, _)| {
pkg.marker()
self.dependencies.retain(|dependency| {
dependency
.package
.marker()
.map_or(true, |pkg_marker| !is_disjoint(pkg_marker, fork_marker))
});
}