Skip to content
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

Testing refactor: more efficient intersection (https://github.com/pubgrub-rs/pubgrub/pull/157) #9

Closed
wants to merge 6 commits into from
Closed
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 }}
2 changes: 1 addition & 1 deletion src/internal/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct State<P: Package, VS: VersionSet, Priority: Ord + Clone> {
root_package: P,
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
/// and will stay that way until the next conflict and backtrack is operated.
Expand Down
20 changes: 18 additions & 2 deletions src/internal/incompatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,22 @@ use crate::version_set::VersionSet;
#[derive(Debug, Clone)]
pub struct Incompatibility<P: Package, VS: VersionSet> {
package_terms: SmallMap<P, Term<VS>>,
kind: Kind<P, VS>,
pub kind: Kind<P, VS>,
}

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

#[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.
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<String>),
/// Incompatibility coming from the dependencies of a given package.
FromDependencyOf(P, VS, P, VS),
/// Derived from two causes. Stores cause ids.
Expand Down Expand Up @@ -104,6 +106,17 @@ impl<P: Package, VS: VersionSet> Incompatibility<P, VS> {
}
}

/// 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<String>) -> 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);
Expand Down Expand Up @@ -206,6 +219,9 @@ impl<P: Package, VS: VersionSet> Incompatibility<P, VS> {
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(),
Expand Down
18 changes: 18 additions & 0 deletions src/internal/partial_solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,24 @@ impl<P: Package, VS: VersionSet, Priority: Ord + Clone> PartialSolution<P, VS, P
}
}

pub fn prioritized_packages(&self) -> impl Iterator<Item = (&P, &VS)> {
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,
Expand Down
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
57 changes: 35 additions & 22 deletions src/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ impl<V> Range<V> {
segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))),
}
}

pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
}

impl<V: Clone> Range<V> {
Expand Down Expand Up @@ -289,34 +293,43 @@ impl<V: Ord + Clone> Range<V> {
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()
Expand Down Expand Up @@ -373,7 +386,7 @@ impl<V: Display + Eq> Display for Range<V> {
(Included(v), Unbounded) => write!(f, ">={v}")?,
(Included(v), Included(b)) => {
if v == b {
write!(f, "{v}")?
write!(f, "=={v}")?
} else {
write!(f, ">={v}, <={b}")?
}
Expand Down
32 changes: 32 additions & 0 deletions src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub enum External<P: Package, VS: VersionSet> {
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<String>),
/// Incompatibility coming from the dependencies of a given package.
FromDependencyOf(P, VS, P, VS),
}
Expand Down Expand Up @@ -113,6 +115,13 @@ impl<P: Package, VS: VersionSet> DerivationTree<P, VS> {
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(
Expand Down Expand Up @@ -158,6 +167,29 @@ impl<P: Package, VS: VersionSet> fmt::Display for External<P, VS> {
)
}
}
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)
Expand Down
4 changes: 2 additions & 2 deletions src/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl<VS: VersionSet> Term<VS> {

/// 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"),
Expand Down
Loading