| //! This module implements support for preferring some versions of a package |
| //! over other versions. |
| |
| use std::cmp::Ordering; |
| use std::collections::{HashMap, HashSet}; |
| |
| use crate::core::{Dependency, PackageId, Summary}; |
| use crate::util::interning::InternedString; |
| use crate::util::RustVersion; |
| |
| /// A collection of preferences for particular package versions. |
| /// |
| /// This is built up with [`Self::prefer_package_id`] and [`Self::prefer_dependency`], then used to sort the set of |
| /// summaries for a package during resolution via [`Self::sort_summaries`]. |
| /// |
| /// As written, a version is either "preferred" or "not preferred". Later extensions may |
| /// introduce more granular preferences. |
| #[derive(Default)] |
| pub struct VersionPreferences { |
| try_to_use: HashSet<PackageId>, |
| prefer_patch_deps: HashMap<InternedString, HashSet<Dependency>>, |
| version_ordering: VersionOrdering, |
| max_rust_version: Option<RustVersion>, |
| } |
| |
| #[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)] |
| pub enum VersionOrdering { |
| #[default] |
| MaximumVersionsFirst, |
| MinimumVersionsFirst, |
| } |
| |
| impl VersionPreferences { |
| /// Indicate that the given package (specified as a [`PackageId`]) should be preferred. |
| pub fn prefer_package_id(&mut self, pkg_id: PackageId) { |
| self.try_to_use.insert(pkg_id); |
| } |
| |
| /// Indicate that the given package (specified as a [`Dependency`]) should be preferred. |
| pub fn prefer_dependency(&mut self, dep: Dependency) { |
| self.prefer_patch_deps |
| .entry(dep.package_name()) |
| .or_insert_with(HashSet::new) |
| .insert(dep); |
| } |
| |
| pub fn version_ordering(&mut self, ordering: VersionOrdering) { |
| self.version_ordering = ordering; |
| } |
| |
| pub fn max_rust_version(&mut self, ver: Option<RustVersion>) { |
| self.max_rust_version = ver; |
| } |
| |
| /// Sort (and filter) the given vector of summaries in-place |
| /// |
| /// Note: all summaries presumed to be for the same package. |
| /// |
| /// Sort order: |
| /// 1. Preferred packages |
| /// 2. `first_version`, falling back to [`VersionPreferences::version_ordering`] when `None` |
| /// |
| /// Filtering: |
| /// - [`VersionPreferences::max_rust_version`] |
| /// - `first_version` |
| pub fn sort_summaries( |
| &self, |
| summaries: &mut Vec<Summary>, |
| first_version: Option<VersionOrdering>, |
| ) { |
| let should_prefer = |pkg_id: &PackageId| { |
| self.try_to_use.contains(pkg_id) |
| || self |
| .prefer_patch_deps |
| .get(&pkg_id.name()) |
| .map(|deps| deps.iter().any(|d| d.matches_id(*pkg_id))) |
| .unwrap_or(false) |
| }; |
| if self.max_rust_version.is_some() { |
| summaries.retain(|s| s.rust_version() <= self.max_rust_version.as_ref()); |
| } |
| summaries.sort_unstable_by(|a, b| { |
| let prefer_a = should_prefer(&a.package_id()); |
| let prefer_b = should_prefer(&b.package_id()); |
| let previous_cmp = prefer_a.cmp(&prefer_b).reverse(); |
| if previous_cmp != Ordering::Equal { |
| return previous_cmp; |
| } |
| |
| let cmp = a.version().cmp(b.version()); |
| match first_version.unwrap_or(self.version_ordering) { |
| VersionOrdering::MaximumVersionsFirst => cmp.reverse(), |
| VersionOrdering::MinimumVersionsFirst => cmp, |
| } |
| }); |
| if first_version.is_some() { |
| let _ = summaries.split_off(1); |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::core::SourceId; |
| use std::collections::BTreeMap; |
| |
| fn pkgid(name: &str, version: &str) -> PackageId { |
| let src_id = |
| SourceId::from_url("registry+https://github.com/rust-lang/crates.io-index").unwrap(); |
| PackageId::new(name, version, src_id).unwrap() |
| } |
| |
| fn dep(name: &str, version: &str) -> Dependency { |
| let src_id = |
| SourceId::from_url("registry+https://github.com/rust-lang/crates.io-index").unwrap(); |
| Dependency::parse(name, Some(version), src_id).unwrap() |
| } |
| |
| fn summ(name: &str, version: &str, msrv: Option<&str>) -> Summary { |
| let pkg_id = pkgid(name, version); |
| let features = BTreeMap::new(); |
| Summary::new( |
| pkg_id, |
| Vec::new(), |
| &features, |
| None::<&String>, |
| msrv.map(|m| m.parse().unwrap()), |
| ) |
| .unwrap() |
| } |
| |
| fn describe(summaries: &Vec<Summary>) -> String { |
| let strs: Vec<String> = summaries |
| .iter() |
| .map(|summary| format!("{}/{}", summary.name(), summary.version())) |
| .collect(); |
| strs.join(", ") |
| } |
| |
| #[test] |
| fn test_prefer_package_id() { |
| let mut vp = VersionPreferences::default(); |
| vp.prefer_package_id(pkgid("foo", "1.2.3")); |
| |
| let mut summaries = vec![ |
| summ("foo", "1.2.4", None), |
| summ("foo", "1.2.3", None), |
| summ("foo", "1.1.0", None), |
| summ("foo", "1.0.9", None), |
| ]; |
| |
| vp.version_ordering(VersionOrdering::MaximumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.2.3, foo/1.2.4, foo/1.1.0, foo/1.0.9".to_string() |
| ); |
| |
| vp.version_ordering(VersionOrdering::MinimumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string() |
| ); |
| } |
| |
| #[test] |
| fn test_prefer_dependency() { |
| let mut vp = VersionPreferences::default(); |
| vp.prefer_dependency(dep("foo", "=1.2.3")); |
| |
| let mut summaries = vec![ |
| summ("foo", "1.2.4", None), |
| summ("foo", "1.2.3", None), |
| summ("foo", "1.1.0", None), |
| summ("foo", "1.0.9", None), |
| ]; |
| |
| vp.version_ordering(VersionOrdering::MaximumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.2.3, foo/1.2.4, foo/1.1.0, foo/1.0.9".to_string() |
| ); |
| |
| vp.version_ordering(VersionOrdering::MinimumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string() |
| ); |
| } |
| |
| #[test] |
| fn test_prefer_both() { |
| let mut vp = VersionPreferences::default(); |
| vp.prefer_package_id(pkgid("foo", "1.2.3")); |
| vp.prefer_dependency(dep("foo", "=1.1.0")); |
| |
| let mut summaries = vec![ |
| summ("foo", "1.2.4", None), |
| summ("foo", "1.2.3", None), |
| summ("foo", "1.1.0", None), |
| summ("foo", "1.0.9", None), |
| ]; |
| |
| vp.version_ordering(VersionOrdering::MaximumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.2.3, foo/1.1.0, foo/1.2.4, foo/1.0.9".to_string() |
| ); |
| |
| vp.version_ordering(VersionOrdering::MinimumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.1.0, foo/1.2.3, foo/1.0.9, foo/1.2.4".to_string() |
| ); |
| } |
| |
| #[test] |
| fn test_max_rust_version() { |
| let mut vp = VersionPreferences::default(); |
| vp.max_rust_version(Some("1.50".parse().unwrap())); |
| |
| let mut summaries = vec![ |
| summ("foo", "1.2.4", Some("1.60")), |
| summ("foo", "1.2.3", Some("1.50")), |
| summ("foo", "1.1.0", Some("1.40")), |
| summ("foo", "1.0.9", None), |
| ]; |
| |
| vp.version_ordering(VersionOrdering::MaximumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.2.3, foo/1.1.0, foo/1.0.9".to_string() |
| ); |
| |
| vp.version_ordering(VersionOrdering::MinimumVersionsFirst); |
| vp.sort_summaries(&mut summaries, None); |
| assert_eq!( |
| describe(&summaries), |
| "foo/1.0.9, foo/1.1.0, foo/1.2.3".to_string() |
| ); |
| } |
| } |