mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-01 14:31:12 +00:00
Unify resolutions only during graph building (#5479)
With our previous eager union, we were losing the fork markers. We now carry this information into the resolution graph construction and, in the next step, can read the markers there. Part of https://github.com/astral-sh/uv/issues/5180#issuecomment-2247696198
This commit is contained in:
parent
77b005244d
commit
cb505d24f8
3 changed files with 47 additions and 74 deletions
|
@ -29,15 +29,4 @@ impl FilePins {
|
||||||
) -> Option<&ResolvedDist> {
|
) -> Option<&ResolvedDist> {
|
||||||
self.0.get(name)?.get(version)
|
self.0.get(name)?.get(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add the pins in `other` to `self`.
|
|
||||||
///
|
|
||||||
/// This assumes that if a version for a particular package exists in
|
|
||||||
/// both `self` and `other`, then they will both correspond to identical
|
|
||||||
/// distributions.
|
|
||||||
pub(crate) fn union(&mut self, other: FilePins) {
|
|
||||||
for (name, versions) in other.0 {
|
|
||||||
self.0.entry(name).or_default().extend(versions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
use indexmap::IndexSet;
|
|
||||||
use petgraph::{
|
|
||||||
graph::{Graph, NodeIndex},
|
|
||||||
Directed,
|
|
||||||
};
|
|
||||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
|
||||||
|
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
Dist, DistributionMetadata, Name, ResolutionDiagnostic, ResolvedDist, VersionId,
|
Dist, DistributionMetadata, Name, ResolutionDiagnostic, ResolvedDist, VersionId,
|
||||||
VersionOrUrlRef,
|
VersionOrUrlRef,
|
||||||
};
|
};
|
||||||
|
use indexmap::IndexSet;
|
||||||
use pep440_rs::{Version, VersionSpecifier};
|
use pep440_rs::{Version, VersionSpecifier};
|
||||||
use pep508_rs::{MarkerEnvironment, MarkerTree};
|
use pep508_rs::{MarkerEnvironment, MarkerTree};
|
||||||
|
use petgraph::{
|
||||||
|
graph::{Graph, NodeIndex},
|
||||||
|
Directed,
|
||||||
|
};
|
||||||
use pypi_types::{HashDigest, ParsedUrlError, Requirement, VerbatimParsedUrl, Yanked};
|
use pypi_types::{HashDigest, ParsedUrlError, Requirement, VerbatimParsedUrl, Yanked};
|
||||||
|
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||||
use uv_configuration::{Constraints, Overrides};
|
use uv_configuration::{Constraints, Overrides};
|
||||||
use uv_distribution::Metadata;
|
use uv_distribution::Metadata;
|
||||||
use uv_git::GitResolver;
|
use uv_git::GitResolver;
|
||||||
|
@ -67,7 +66,7 @@ struct PackageRef<'a> {
|
||||||
impl ResolutionGraph {
|
impl ResolutionGraph {
|
||||||
/// Create a new graph from the resolved PubGrub state.
|
/// Create a new graph from the resolved PubGrub state.
|
||||||
pub(crate) fn from_state(
|
pub(crate) fn from_state(
|
||||||
resolution: Resolution,
|
resolutions: &[Resolution],
|
||||||
requirements: &[Requirement],
|
requirements: &[Requirement],
|
||||||
constraints: &Constraints,
|
constraints: &Constraints,
|
||||||
overrides: &Overrides,
|
overrides: &Overrides,
|
||||||
|
@ -77,18 +76,24 @@ impl ResolutionGraph {
|
||||||
python: &PythonRequirement,
|
python: &PythonRequirement,
|
||||||
options: Options,
|
options: Options,
|
||||||
) -> Result<Self, ResolveError> {
|
) -> Result<Self, ResolveError> {
|
||||||
|
let size_guess = resolutions[0].nodes.len();
|
||||||
let mut petgraph: Graph<ResolutionGraphNode, Option<MarkerTree>, Directed> =
|
let mut petgraph: Graph<ResolutionGraphNode, Option<MarkerTree>, Directed> =
|
||||||
Graph::with_capacity(resolution.nodes.len(), resolution.nodes.len());
|
Graph::with_capacity(size_guess, size_guess);
|
||||||
let mut inverse: FxHashMap<PackageRef, NodeIndex<u32>> =
|
let mut inverse: FxHashMap<PackageRef, NodeIndex<u32>> =
|
||||||
FxHashMap::with_capacity_and_hasher(resolution.nodes.len(), FxBuildHasher);
|
FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
|
|
||||||
// Add the root node.
|
// Add the root node.
|
||||||
let root_index = petgraph.add_node(ResolutionGraphNode::Root);
|
let root_index = petgraph.add_node(ResolutionGraphNode::Root);
|
||||||
|
|
||||||
// Add every package to the graph.
|
let mut seen = FxHashSet::default();
|
||||||
for (package, versions) in &resolution.nodes {
|
for resolution in resolutions {
|
||||||
for version in versions {
|
// Add every package to the graph.
|
||||||
|
for (package, version) in &resolution.nodes {
|
||||||
|
if !seen.insert((package, version)) {
|
||||||
|
// Insert each node only once.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Self::add_version(
|
Self::add_version(
|
||||||
&mut petgraph,
|
&mut petgraph,
|
||||||
&mut inverse,
|
&mut inverse,
|
||||||
|
@ -102,10 +107,17 @@ impl ResolutionGraph {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut seen = FxHashSet::default();
|
||||||
|
for resolution in resolutions {
|
||||||
|
// Add every edge to the graph.
|
||||||
|
for edge in &resolution.edges {
|
||||||
|
if !seen.insert(edge) {
|
||||||
|
// Insert each node only once.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Add every edge to the graph.
|
Self::add_edge(&mut petgraph, &mut inverse, root_index, edge);
|
||||||
for edge in resolution.edges {
|
}
|
||||||
Self::add_edge(&mut petgraph, &mut inverse, root_index, edge);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the `Requires-Python` range, if provided.
|
// Extract the `Requires-Python` range, if provided.
|
||||||
|
@ -141,7 +153,7 @@ impl ResolutionGraph {
|
||||||
petgraph: &mut Graph<ResolutionGraphNode, Option<MarkerTree>>,
|
petgraph: &mut Graph<ResolutionGraphNode, Option<MarkerTree>>,
|
||||||
inverse: &mut FxHashMap<PackageRef<'_>, NodeIndex>,
|
inverse: &mut FxHashMap<PackageRef<'_>, NodeIndex>,
|
||||||
root_index: NodeIndex,
|
root_index: NodeIndex,
|
||||||
edge: ResolutionDependencyEdge,
|
edge: &ResolutionDependencyEdge,
|
||||||
) {
|
) {
|
||||||
let from_index = edge.from.as_ref().map_or(root_index, |from| {
|
let from_index = edge.from.as_ref().map_or(root_index, |from| {
|
||||||
inverse[&PackageRef {
|
inverse[&PackageRef {
|
||||||
|
@ -166,7 +178,8 @@ impl ResolutionGraph {
|
||||||
{
|
{
|
||||||
// If either the existing marker or new marker is `None`, then the dependency is
|
// If either the existing marker or new marker is `None`, then the dependency is
|
||||||
// included unconditionally, and so the combined marker should be `None`.
|
// included unconditionally, and so the combined marker should be `None`.
|
||||||
if let (Some(marker), Some(ref version_marker)) = (marker.as_mut(), edge.marker) {
|
if let (Some(marker), Some(ref version_marker)) = (marker.as_mut(), edge.marker.clone())
|
||||||
|
{
|
||||||
marker.or(version_marker.clone());
|
marker.or(version_marker.clone());
|
||||||
} else {
|
} else {
|
||||||
*marker = None;
|
*marker = None;
|
||||||
|
|
|
@ -380,11 +380,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
let resolution = state.into_resolution();
|
let resolution = state.into_resolution();
|
||||||
|
|
||||||
// Walk over the selected versions, and mark them as preferences.
|
// Walk over the selected versions, and mark them as preferences.
|
||||||
for (package, versions) in &resolution.nodes {
|
for (package, version) in &resolution.nodes {
|
||||||
if let Entry::Vacant(entry) = preferences.entry(package.name.clone()) {
|
if let Entry::Vacant(entry) = preferences.entry(package.name.clone()) {
|
||||||
if let Some(version) = versions.iter().next() {
|
entry.insert(version.clone().into());
|
||||||
entry.insert(version.clone().into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,6 +407,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
continue 'FORK;
|
continue 'FORK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Self::trace_resolution(&resolution);
|
||||||
resolutions.push(resolution);
|
resolutions.push(resolution);
|
||||||
continue 'FORK;
|
continue 'FORK;
|
||||||
};
|
};
|
||||||
|
@ -591,25 +590,22 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut combined = Resolution::universal();
|
|
||||||
if resolutions.len() > 1 {
|
if resolutions.len() > 1 {
|
||||||
info!(
|
info!(
|
||||||
"Solved your requirements for {} environments",
|
"Solved your requirements for {} environments",
|
||||||
resolutions.len()
|
resolutions.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for resolution in resolutions {
|
for resolution in &resolutions {
|
||||||
if let Some(markers) = resolution.markers.fork_markers() {
|
if let Some(markers) = resolution.markers.fork_markers() {
|
||||||
debug!(
|
debug!(
|
||||||
"Distinct solution for ({markers}) with {} packages",
|
"Distinct solution for ({markers}) with {} packages",
|
||||||
resolution.nodes.len()
|
resolution.nodes.len()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
combined.union(resolution);
|
|
||||||
}
|
}
|
||||||
Self::trace_resolution(&combined);
|
|
||||||
ResolutionGraph::from_state(
|
ResolutionGraph::from_state(
|
||||||
combined,
|
&resolutions,
|
||||||
&self.requirements,
|
&self.requirements,
|
||||||
&self.constraints,
|
&self.constraints,
|
||||||
&self.overrides,
|
&self.overrides,
|
||||||
|
@ -2244,7 +2240,7 @@ impl ForkState {
|
||||||
|
|
||||||
fn into_resolution(self) -> Resolution {
|
fn into_resolution(self) -> Resolution {
|
||||||
let solution = self.pubgrub.partial_solution.extract_solution();
|
let solution = self.pubgrub.partial_solution.extract_solution();
|
||||||
let mut dependencies: FxHashSet<ResolutionDependencyEdge> = FxHashSet::default();
|
let mut edges: FxHashSet<ResolutionDependencyEdge> = FxHashSet::default();
|
||||||
for (package, self_version) in &solution {
|
for (package, self_version) in &solution {
|
||||||
for id in &self.pubgrub.incompatibilities[package] {
|
for id in &self.pubgrub.incompatibilities[package] {
|
||||||
let pubgrub::solver::Kind::FromDependencyOf(
|
let pubgrub::solver::Kind::FromDependencyOf(
|
||||||
|
@ -2307,7 +2303,7 @@ impl ForkState {
|
||||||
to_dev: dependency_dev.clone(),
|
to_dev: dependency_dev.clone(),
|
||||||
marker: None,
|
marker: None,
|
||||||
};
|
};
|
||||||
dependencies.insert(edge);
|
edges.insert(edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
PubGrubPackageInner::Marker {
|
PubGrubPackageInner::Marker {
|
||||||
|
@ -2332,7 +2328,7 @@ impl ForkState {
|
||||||
to_dev: None,
|
to_dev: None,
|
||||||
marker: Some(dependency_marker.clone()),
|
marker: Some(dependency_marker.clone()),
|
||||||
};
|
};
|
||||||
dependencies.insert(edge);
|
edges.insert(edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
PubGrubPackageInner::Extra {
|
PubGrubPackageInner::Extra {
|
||||||
|
@ -2358,7 +2354,7 @@ impl ForkState {
|
||||||
to_dev: None,
|
to_dev: None,
|
||||||
marker: dependency_marker.clone(),
|
marker: dependency_marker.clone(),
|
||||||
};
|
};
|
||||||
dependencies.insert(edge);
|
edges.insert(edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
PubGrubPackageInner::Dev {
|
PubGrubPackageInner::Dev {
|
||||||
|
@ -2384,7 +2380,7 @@ impl ForkState {
|
||||||
to_dev: Some(dependency_dev.clone()),
|
to_dev: Some(dependency_dev.clone()),
|
||||||
marker: dependency_marker.clone(),
|
marker: dependency_marker.clone(),
|
||||||
};
|
};
|
||||||
dependencies.insert(edge);
|
edges.insert(edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -2392,7 +2388,7 @@ impl ForkState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let packages = solution
|
let nodes = solution
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(package, version)| {
|
.filter_map(|(package, version)| {
|
||||||
if let PubGrubPackageInner::Package {
|
if let PubGrubPackageInner::Package {
|
||||||
|
@ -2409,7 +2405,7 @@ impl ForkState {
|
||||||
dev: dev.clone(),
|
dev: dev.clone(),
|
||||||
url: self.fork_urls.get(name).cloned(),
|
url: self.fork_urls.get(name).cloned(),
|
||||||
},
|
},
|
||||||
FxHashSet::from_iter([version]),
|
version,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -2418,21 +2414,18 @@ impl ForkState {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Resolution {
|
Resolution {
|
||||||
nodes: packages,
|
nodes,
|
||||||
edges: dependencies,
|
edges,
|
||||||
pins: self.pins,
|
pins: self.pins,
|
||||||
markers: self.markers,
|
markers: self.markers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The resolution from one or more forks including the virtual packages and the edges between them.
|
/// The resolution from a single fork including the virtual packages and the edges between them.
|
||||||
///
|
|
||||||
/// Each package can have multiple versions and each edge between two packages can have multiple
|
|
||||||
/// version specifiers to support diverging versions and requirements in different forks.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Resolution {
|
pub(crate) struct Resolution {
|
||||||
pub(crate) nodes: FxHashMap<ResolutionPackage, FxHashSet<Version>>,
|
pub(crate) nodes: FxHashMap<ResolutionPackage, Version>,
|
||||||
/// The directed connections between the nodes, where the marker is the node weight. We don't
|
/// The directed connections between the nodes, where the marker is the node weight. We don't
|
||||||
/// store the requirement itself, but it can be retrieved from the package metadata.
|
/// store the requirement itself, but it can be retrieved from the package metadata.
|
||||||
pub(crate) edges: FxHashSet<ResolutionDependencyEdge>,
|
pub(crate) edges: FxHashSet<ResolutionDependencyEdge>,
|
||||||
|
@ -2471,17 +2464,6 @@ pub(crate) struct ResolutionDependencyEdge {
|
||||||
pub(crate) marker: Option<MarkerTree>,
|
pub(crate) marker: Option<MarkerTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resolution {
|
|
||||||
fn universal() -> Self {
|
|
||||||
Self {
|
|
||||||
nodes: FxHashMap::default(),
|
|
||||||
edges: FxHashSet::default(),
|
|
||||||
pins: FilePins::default(),
|
|
||||||
markers: ResolverMarkers::Universal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolution {
|
impl Resolution {
|
||||||
/// Whether we got two identical resolutions in two separate forks.
|
/// Whether we got two identical resolutions in two separate forks.
|
||||||
///
|
///
|
||||||
|
@ -2495,17 +2477,6 @@ impl Resolution {
|
||||||
// change which packages and versions are installed.
|
// change which packages and versions are installed.
|
||||||
self.nodes == other.nodes && self.edges == other.edges
|
self.nodes == other.nodes && self.edges == other.edges
|
||||||
}
|
}
|
||||||
|
|
||||||
fn union(&mut self, other: Resolution) {
|
|
||||||
for (other_package, other_versions) in other.nodes {
|
|
||||||
self.nodes
|
|
||||||
.entry(other_package)
|
|
||||||
.or_default()
|
|
||||||
.extend(other_versions);
|
|
||||||
}
|
|
||||||
self.edges.extend(other.edges);
|
|
||||||
self.pins.union(other.pins);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch the metadata for an item
|
/// Fetch the metadata for an item
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue