diff --git a/.github/workflows/permaref.yaml b/.github/workflows/permaref.yaml new file mode 100644 index 00000000..eee99e6f --- /dev/null +++ b/.github/workflows/permaref.yaml @@ -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 "$GITHUB_ACTOR@users.noreply.github.com" + + - 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 }} diff --git a/src/internal/core.rs b/src/internal/core.rs index 06e3ae21..a4a23b34 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -24,7 +24,7 @@ pub struct State { root_package: P, root_version: VS::V, - incompatibilities: Map>>, + pub incompatibilities: Map>>, /// Store the ids of incompatibilities that are already contradicted /// and will stay that way until the next conflict and backtrack is operated. diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index b56a3c44..8de990c2 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -31,20 +31,22 @@ use crate::version_set::VersionSet; #[derive(Debug, Clone)] pub struct Incompatibility { package_terms: SmallMap>, - kind: Kind, + pub kind: Kind, } /// Type alias of unique identifiers for incompatibilities. pub type IncompId = Id>; #[derive(Debug, Clone)] -enum Kind { +pub enum Kind { /// 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. NoVersions(P, VS), /// Dependencies of the package are unavailable for versions in that range. UnavailableDependencies(P, VS), + /// Dependencies of the package are unusable for versions in that range. + UnusableDependencies(P, VS, Option), /// Incompatibility coming from the dependencies of a given package. FromDependencyOf(P, VS, P, VS), /// Derived from two causes. Stores cause ids. @@ -104,6 +106,17 @@ impl Incompatibility { } } + /// Create an incompatibility to remember + /// that a package version is not selectable + /// because its dependencies are not usable. + pub fn unusable_dependencies(package: P, version: VS::V, reason: Option) -> Self { + let set = VS::singleton(version); + Self { + package_terms: SmallMap::One([(package.clone(), Term::Positive(set.clone()))]), + kind: Kind::UnusableDependencies(package, set, reason), + } + } + /// Build an incompatibility from a given dependency. pub fn from_dependency(package: P, version: VS::V, dep: (&P, &VS)) -> Self { let set1 = VS::singleton(version); @@ -206,6 +219,9 @@ impl Incompatibility { Kind::UnavailableDependencies(package, set) => DerivationTree::External( External::UnavailableDependencies(package.clone(), set.clone()), ), + Kind::UnusableDependencies(package, set, reason) => DerivationTree::External( + External::UnusableDependencies(package.clone(), set.clone(), reason.clone()), + ), Kind::FromDependencyOf(package, set, dep_package, dep_set) => { DerivationTree::External(External::FromDependencyOf( package.clone(), diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 057dea13..6a8a2bff 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -252,6 +252,24 @@ impl PartialSolution impl Iterator { + let check_all = self.changed_this_decision_level + == self.current_decision_level.0.saturating_sub(1) as usize; + let current_decision_level = self.current_decision_level; + self.package_assignments + .get_range(self.changed_this_decision_level..) + .unwrap() + .iter() + .filter(move |(_, pa)| { + // We only actually need to update the package if its Been changed + // since the last time we called prioritize. + // Which means it's highest decision level is the current decision level, + // or if we backtracked in the mean time. + check_all || pa.highest_decision_level == current_decision_level + }) + .filter_map(|(p, pa)| pa.assignments_intersection.potential_package_filter(p)) + } + pub fn pick_highest_priority_pkg( &mut self, prioritizer: impl Fn(&P, &VS) -> Priority, diff --git a/src/lib.rs b/src/lib.rs index 5f61fb51..0150c52a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,8 +217,7 @@ //! with a cache, you may want to know that some versions //! do not exist in your cache. -#![allow(clippy::rc_buffer)] -#![warn(missing_docs)] +#![allow(clippy::all, unreachable_pub)] pub mod error; pub mod package; diff --git a/src/range.rs b/src/range.rs index be7ff250..a9de3962 100644 --- a/src/range.rs +++ b/src/range.rs @@ -117,6 +117,10 @@ impl Range { segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))), } } + + pub fn is_empty(&self) -> bool { + self.segments.is_empty() + } } impl Range { @@ -289,34 +293,43 @@ impl Range { let mut left_iter = self.segments.iter().peekable(); let mut right_iter = other.segments.iter().peekable(); - while let (Some((left_start, left_end)), Some((right_start, right_end))) = - (left_iter.peek(), right_iter.peek()) + while let Some(((left_start, left_end), (right_start, right_end))) = + left_iter.peek().zip(right_iter.peek()) { + let left_end_is_smaller = match (left_end, right_end) { + (Included(l), Included(r)) + | (Excluded(l), Excluded(r)) + | (Excluded(l), Included(r)) => l <= r, + + (Included(l), Excluded(r)) => l < r, + (_, Unbounded) => true, + (Unbounded, _) => false, + }; + let (other_start, end) = if left_end_is_smaller { + left_iter.next(); + (right_start, left_end) + } else { + right_iter.next(); + (left_start, right_end) + }; + if !valid_segment(other_start, end) { + continue; + } let start = match (left_start, right_start) { (Included(l), Included(r)) => Included(std::cmp::max(l, r)), (Excluded(l), Excluded(r)) => Excluded(std::cmp::max(l, r)), - (Included(i), Excluded(e)) | (Excluded(e), Included(i)) if i <= e => Excluded(e), - (Included(i), Excluded(e)) | (Excluded(e), Included(i)) if e < i => Included(i), + (Included(i), Excluded(e)) | (Excluded(e), Included(i)) => { + if i <= e { + Excluded(e) + } else { + Included(i) + } + } (s, Unbounded) | (Unbounded, s) => s.as_ref(), - _ => unreachable!(), - } - .cloned(); - let end = match (left_end, right_end) { - (Included(l), Included(r)) => Included(std::cmp::min(l, r)), - (Excluded(l), Excluded(r)) => Excluded(std::cmp::min(l, r)), + }; - (Included(i), Excluded(e)) | (Excluded(e), Included(i)) if i >= e => Excluded(e), - (Included(i), Excluded(e)) | (Excluded(e), Included(i)) if e > i => Included(i), - (s, Unbounded) | (Unbounded, s) => s.as_ref(), - _ => unreachable!(), - } - .cloned(); - left_iter.next_if(|(_, e)| e == &end); - right_iter.next_if(|(_, e)| e == &end); - if valid_segment(&start, &end) { - segments.push((start, end)) - } + segments.push((start.cloned(), end.clone())) } Self { segments }.check_invariants() @@ -373,7 +386,7 @@ impl Display for Range { (Included(v), Unbounded) => write!(f, ">={v}")?, (Included(v), Included(b)) => { if v == b { - write!(f, "{v}")? + write!(f, "=={v}")? } else { write!(f, ">={v}, <={b}")? } diff --git a/src/report.rs b/src/report.rs index ff0b2d3f..57e862bd 100644 --- a/src/report.rs +++ b/src/report.rs @@ -41,6 +41,8 @@ pub enum External { NoVersions(P, VS), /// Dependencies of the package are unavailable for versions in that set. UnavailableDependencies(P, VS), + /// Dependencies of the package are unusable for versions in that set. + UnusableDependencies(P, VS, Option), /// Incompatibility coming from the dependencies of a given package. FromDependencyOf(P, VS, P, VS), } @@ -113,6 +115,13 @@ impl DerivationTree { DerivationTree::External(External::UnavailableDependencies(_, r)) => Some( DerivationTree::External(External::UnavailableDependencies(package, set.union(&r))), ), + DerivationTree::External(External::UnusableDependencies(_, r, reason)) => { + Some(DerivationTree::External(External::UnusableDependencies( + package, + set.union(&r), + reason, + ))) + } DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => { if p1 == package { Some(DerivationTree::External(External::FromDependencyOf( @@ -158,6 +167,29 @@ impl fmt::Display for External { ) } } + Self::UnusableDependencies(package, set, reason) => { + if let Some(reason) = reason { + if set == &VS::full() { + write!(f, "dependencies of {} are unusable: {reason}", package) + } else { + write!( + f, + "dependencies of {} at version {} are unusable: {reason}", + package, set + ) + } + } else { + if set == &VS::full() { + write!(f, "dependencies of {} are unusable", package) + } else { + write!( + f, + "dependencies of {} at version {} are unusable", + package, set + ) + } + } + } Self::FromDependencyOf(p, set_p, dep, set_dep) => { if set_p == &VS::full() && set_dep == &VS::full() { write!(f, "{} depends on {}", p, dep) diff --git a/src/solver.rs b/src/solver.rs index a1e5e06c..1728d1fe 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -73,8 +73,8 @@ use std::collections::{BTreeMap, BTreeSet as Set}; use std::error::Error; use crate::error::PubGrubError; -use crate::internal::core::State; -use crate::internal::incompatibility::Incompatibility; +pub use crate::internal::core::State; +pub use crate::internal::incompatibility::{Incompatibility, Kind}; use crate::package::Package; use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies}; use crate::version_set::VersionSet; diff --git a/src/term.rs b/src/term.rs index cf7aa6f7..895afdfe 100644 --- a/src/term.rs +++ b/src/term.rs @@ -64,7 +64,7 @@ impl Term { /// Unwrap the set contained in a positive term. /// Will panic if used on a negative set. - pub(crate) fn unwrap_positive(&self) -> &VS { + pub fn unwrap_positive(&self) -> &VS { match self { Self::Positive(set) => set, _ => panic!("Negative term cannot unwrap positive set"),