Skip to content

Track priorities in a set and allow updating from uv #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/permaref.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Automatically creates a tag for each commit to `main` so when we rebase
# changes on top of the upstream, we retain permanent references to each
# previous commit so they are not orphaned and eventually deleted.
name: Create permanent reference

on:
push:
branches:
- "main"

jobs:
create-permaref:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Get the permanent ref number
id: get_version
run: |
# Enable pipefail so git command failures do not result in null versions downstream
set -x

echo "LAST_PERMA_NUMBER=$(\
git ls-remote --tags --refs --sort="v:refname" \
https://github.com/zanieb/pubgrub.git | grep "tags/perma-" | tail -n1 | sed 's/.*\/perma-//' \
)" >> $GITHUB_OUTPUT

- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"

- name: Create and push the new tag
run: |
TAG="perma-$((LAST_PERMA_NUMBER + 1))"
git tag -a "$TAG" -m 'Automatically created on push to `main`'
git push origin "$TAG"
env:
LAST_PERMA_NUMBER: ${{ steps.get_version.outputs.LAST_PERMA_NUMBER }}
20 changes: 14 additions & 6 deletions src/internal/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type FnvIndexSet<V> = indexmap::IndexSet<V, rustc_hash::FxBuildHasher>;
/// that we actually don't need since it is phantom.
///
/// <https://github.com/rust-lang/rust/issues/26925>
pub(crate) struct Id<T> {
pub struct Id<T> {
raw: u32,
_ty: PhantomData<fn() -> T>,
}
Expand Down Expand Up @@ -50,9 +50,11 @@ impl<T> fmt::Debug for Id<T> {
}

impl<T> Id<T> {
pub(crate) fn into_raw(self) -> usize {
/// The id as index.
pub fn into_raw(self) -> usize {
self.raw as usize
}

fn from(n: u32) -> Self {
Self {
raw: n,
Expand All @@ -73,7 +75,7 @@ impl<T> Id<T> {
/// to have references between those items.
/// They are all dropped at once when the arena is dropped.
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct Arena<T> {
pub struct Arena<T> {
data: Vec<T>,
}

Expand Down Expand Up @@ -150,9 +152,7 @@ impl<T: Hash + Eq + fmt::Debug> fmt::Debug for HashArena<T> {

impl<T: Hash + Eq> HashArena<T> {
pub fn new() -> Self {
HashArena {
data: FnvIndexSet::default(),
}
Self::default()
}

pub fn alloc(&mut self, value: T) -> Id<T> {
Expand All @@ -161,6 +161,14 @@ impl<T: Hash + Eq> HashArena<T> {
}
}

impl<T: Hash + Eq> Default for HashArena<T> {
fn default() -> Self {
Self {
data: FnvIndexSet::default(),
}
}
}

impl<T: Hash + Eq> Index<Id<T>> for HashArena<T> {
type Output = T;
fn index(&self, id: Id<T>) -> &T {
Expand Down
39 changes: 28 additions & 11 deletions src/internal/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ use crate::{DependencyProvider, DerivationTree, Map, NoSolutionError, VersionSet

/// Current state of the PubGrub algorithm.
#[derive(Clone)]
pub(crate) struct State<DP: DependencyProvider> {
pub struct State<DP: DependencyProvider> {
/// The root package and version.
pub root_package: Id<DP::P>,
root_version: DP::V,

/// All incompatibilities indexed by package.
#[allow(clippy::type_complexity)]
incompatibilities: Map<Id<DP::P>, Vec<IncompDpId<DP>>>,
pub incompatibilities: Map<Id<DP::P>, Vec<IncompDpId<DP>>>,

/// As an optimization, store the ids of incompatibilities that are already contradicted.
///
Expand All @@ -33,14 +35,13 @@ pub(crate) struct State<DP: DependencyProvider> {
merged_dependencies: Map<(Id<DP::P>, Id<DP::P>), SmallVec<IncompDpId<DP>>>,

/// Partial solution.
/// TODO: remove pub.
pub(crate) partial_solution: PartialSolution<DP>,
pub partial_solution: PartialSolution<DP>,

/// The store is the reference storage for all incompatibilities.
pub(crate) incompatibility_store: Arena<Incompatibility<DP::P, DP::VS, DP::M>>,
pub incompatibility_store: Arena<Incompatibility<DP::P, DP::VS, DP::M>>,

/// The store is the reference storage for all packages.
pub(crate) package_store: HashArena<DP::P>,
pub package_store: HashArena<DP::P>,

/// This is a stack of work to be done in `unit_propagation`.
/// It can definitely be a local variable to that method, but
Expand All @@ -50,7 +51,7 @@ pub(crate) struct State<DP: DependencyProvider> {

impl<DP: DependencyProvider> State<DP> {
/// Initialization of PubGrub state.
pub(crate) fn init(root_package: DP::P, root_version: DP::V) -> Self {
pub fn init(root_package: DP::P, root_version: DP::V) -> Self {
let mut incompatibility_store = Arena::new();
let mut package_store = HashArena::new();
let root_package = package_store.alloc(root_package);
Expand All @@ -74,7 +75,7 @@ impl<DP: DependencyProvider> State<DP> {
}

/// Add the dependencies for the current version of the current package as incompatibilities.
pub(crate) fn add_package_version_dependencies(
pub fn add_package_version_dependencies(
&mut self,
package: Id<DP::P>,
version: DP::V,
Expand All @@ -91,7 +92,7 @@ impl<DP: DependencyProvider> State<DP> {
}

/// Add an incompatibility to the state.
pub(crate) fn add_incompatibility(&mut self, incompat: Incompatibility<DP::P, DP::VS, DP::M>) {
pub fn add_incompatibility(&mut self, incompat: Incompatibility<DP::P, DP::VS, DP::M>) {
let id = self.incompatibility_store.alloc(incompat);
self.merge_incompatibility(id);
}
Expand Down Expand Up @@ -129,7 +130,7 @@ impl<DP: DependencyProvider> State<DP> {
/// incompatibility.
#[cold]
#[allow(clippy::type_complexity)] // Type definitions don't support impl trait.
pub(crate) fn unit_propagation(
pub fn unit_propagation(
&mut self,
package: Id<DP::P>,
) -> Result<SmallVec<(Id<DP::P>, IncompDpId<DP>)>, NoSolutionError<DP>> {
Expand Down Expand Up @@ -288,7 +289,8 @@ impl<DP: DependencyProvider> State<DP> {
}
}

/// Backtracking.
/// After a conflict occurred, backtrack the partial solution to a given decision level, and add
/// the incompatibility if it was new.
fn backtrack(
&mut self,
incompat: IncompDpId<DP>,
Expand All @@ -304,6 +306,21 @@ impl<DP: DependencyProvider> State<DP> {
}
}

/// Manually backtrack before the given package was selected.
///
/// This can be used to switch the order of packages if the previous prioritization was bad.
///
/// Returns the number of the decisions that were backtracked, or `None` if the package was not
/// decided on yet.
pub fn backtrack_package(&mut self, package: Id<DP::P>) -> Option<u32> {
let base_decision_level = self.partial_solution.current_decision_level();
let new_decision_level = self.partial_solution.backtrack_package(package).ok()?;
// Remove contradicted incompatibilities that depend on decisions we just backtracked away.
self.contradicted_incompatibilities
.retain(|_, dl| *dl <= new_decision_level);
Some(base_decision_level.0 - new_decision_level.0)
}

/// Add this incompatibility into the set of all incompatibilities.
///
/// PubGrub collapses identical dependencies from adjacent package versions
Expand Down
21 changes: 12 additions & 9 deletions src/internal/incompatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,24 @@ use crate::{
/// during conflict resolution. More about all this in
/// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility).
#[derive(Debug, Clone)]
pub(crate) struct Incompatibility<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
pub struct Incompatibility<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
package_terms: SmallMap<Id<P>, Term<VS>>,
kind: Kind<P, VS, M>,
/// The reason for the incompatibility.
pub kind: Kind<P, VS, M>,
}

/// Type alias of unique identifiers for incompatibilities.
pub(crate) type IncompId<P, VS, M> = Id<Incompatibility<P, VS, M>>;
pub type IncompId<P, VS, M> = Id<Incompatibility<P, VS, M>>;

pub(crate) type IncompDpId<DP> = IncompId<
<DP as DependencyProvider>::P,
<DP as DependencyProvider>::VS,
<DP as DependencyProvider>::M,
>;

/// The reason for the incompatibility.
#[derive(Debug, Clone)]
enum Kind<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
pub enum Kind<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
/// Initial incompatibility aiming at picking the root package for the first decision.
///
/// This incompatibility drives the resolution, it requires that we pick the (virtual) root
Expand Down Expand Up @@ -104,7 +106,7 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
}

/// Create an incompatibility to remember that a given set does not contain any version.
pub(crate) fn no_versions(package: Id<P>, term: Term<VS>) -> Self {
pub fn no_versions(package: Id<P>, term: Term<VS>) -> Self {
let set = match &term {
Term::Positive(r) => r.clone(),
Term::Negative(_) => panic!("No version should have a positive term"),
Expand All @@ -117,7 +119,7 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit

/// Create an incompatibility for a reason outside pubgrub.
#[allow(dead_code)] // Used by uv
pub(crate) fn custom_term(package: Id<P>, term: Term<VS>, metadata: M) -> Self {
pub fn custom_term(package: Id<P>, term: Term<VS>, metadata: M) -> Self {
let set = match &term {
Term::Positive(r) => r.clone(),
Term::Negative(_) => panic!("No version should have a positive term"),
Expand All @@ -129,7 +131,7 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
}

/// Create an incompatibility for a reason outside pubgrub.
pub(crate) fn custom_version(package: Id<P>, version: VS::V, metadata: M) -> Self {
pub fn custom_version(package: Id<P>, version: VS::V, metadata: M) -> Self {
let set = VS::singleton(version);
let term = Term::Positive(set.clone());
Self {
Expand All @@ -139,7 +141,7 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
}

/// Build an incompatibility from a given dependency.
pub(crate) fn from_dependency(package: Id<P>, versions: VS, dep: (Id<P>, VS)) -> Self {
pub fn from_dependency(package: Id<P>, versions: VS, dep: (Id<P>, VS)) -> Self {
let (p2, set2) = dep;
Self {
package_terms: if set2 == VS::empty() {
Expand Down Expand Up @@ -246,7 +248,7 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
}

/// Iterate over packages.
pub(crate) fn iter(&self) -> impl Iterator<Item = (Id<P>, &Term<VS>)> {
pub fn iter(&self) -> impl Iterator<Item = (Id<P>, &Term<VS>)> {
self.package_terms
.iter()
.map(|(package, term)| (*package, term))
Expand Down Expand Up @@ -345,6 +347,7 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a>
}

impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibility<P, VS, M> {
/// Display the incompatibility.
pub fn display<'a>(&'a self, package_store: &'a HashArena<P>) -> impl Display + 'a {
match self.iter().collect::<Vec<_>>().as_slice() {
[] => "version solving failed".into(),
Expand Down
10 changes: 7 additions & 3 deletions src/internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ mod partial_solution;
mod small_map;
mod small_vec;

pub(crate) use arena::{Arena, HashArena, Id};
pub(crate) use core::State;
pub(crate) use incompatibility::{IncompDpId, IncompId, Incompatibility, Relation};
pub(crate) use arena::{Arena, HashArena};
pub(crate) use incompatibility::{IncompDpId, Relation};
pub(crate) use partial_solution::{DecisionLevel, PartialSolution, SatisfierSearch};
pub(crate) use small_map::SmallMap;
pub(crate) use small_vec::SmallVec;

// uv-specific additions
pub use arena::Id;
pub use core::State;
pub use incompatibility::{IncompId, Incompatibility, Kind};
Loading
Loading