1use std::cmp::Ordering;
5use std::collections::{HashMap, HashSet};
6
7use cargo_util_schemas::core::PartialVersion;
8
9use crate::core::{Dependency, PackageId, Summary};
10use crate::util::interning::InternedString;
11
12#[derive(Default)]
20pub struct VersionPreferences {
21 try_to_use: HashSet<PackageId>,
22 prefer_patch_deps: HashMap<InternedString, HashSet<Dependency>>,
23 version_ordering: VersionOrdering,
24 rust_versions: Vec<PartialVersion>,
25}
26
27#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
28pub enum VersionOrdering {
29 #[default]
30 MaximumVersionsFirst,
31 MinimumVersionsFirst,
32}
33
34impl VersionPreferences {
35 pub fn prefer_package_id(&mut self, pkg_id: PackageId) {
37 self.try_to_use.insert(pkg_id);
38 }
39
40 pub fn prefer_dependency(&mut self, dep: Dependency) {
42 self.prefer_patch_deps
43 .entry(dep.package_name())
44 .or_insert_with(HashSet::new)
45 .insert(dep);
46 }
47
48 pub fn version_ordering(&mut self, ordering: VersionOrdering) {
49 self.version_ordering = ordering;
50 }
51
52 pub fn rust_versions(&mut self, vers: Vec<PartialVersion>) {
53 self.rust_versions = vers;
54 }
55
56 pub fn sort_summaries(
68 &self,
69 summaries: &mut Vec<Summary>,
70 first_version: Option<VersionOrdering>,
71 ) {
72 let should_prefer = |pkg_id: &PackageId| {
73 self.try_to_use.contains(pkg_id)
74 || self
75 .prefer_patch_deps
76 .get(&pkg_id.name())
77 .map(|deps| deps.iter().any(|d| d.matches_id(*pkg_id)))
78 .unwrap_or(false)
79 };
80 summaries.sort_unstable_by(|a, b| {
81 let prefer_a = should_prefer(&a.package_id());
82 let prefer_b = should_prefer(&b.package_id());
83 let previous_cmp = prefer_a.cmp(&prefer_b).reverse();
84 if previous_cmp != Ordering::Equal {
85 return previous_cmp;
86 }
87
88 if !self.rust_versions.is_empty() {
89 let a_compat_count = self.msrv_compat_count(a);
90 let b_compat_count = self.msrv_compat_count(b);
91 if b_compat_count != a_compat_count {
92 return b_compat_count.cmp(&a_compat_count);
93 }
94 }
95
96 let cmp = a.version().cmp(b.version());
97 match first_version.unwrap_or(self.version_ordering) {
98 VersionOrdering::MaximumVersionsFirst => cmp.reverse(),
99 VersionOrdering::MinimumVersionsFirst => cmp,
100 }
101 });
102 if first_version.is_some() && !summaries.is_empty() {
103 let _ = summaries.split_off(1);
104 }
105 }
106
107 fn msrv_compat_count(&self, summary: &Summary) -> usize {
108 let Some(rust_version) = summary.rust_version() else {
109 return self.rust_versions.len();
110 };
111
112 self.rust_versions
113 .iter()
114 .filter(|max| rust_version.is_compatible_with(max))
115 .count()
116 }
117}
118
119#[cfg(test)]
120mod test {
121 use super::*;
122 use crate::core::SourceId;
123 use std::collections::BTreeMap;
124
125 fn pkgid(name: &str, version: &str) -> PackageId {
126 let src_id =
127 SourceId::from_url("registry+https://github.com/rust-lang/crates.io-index").unwrap();
128 PackageId::try_new(name, version, src_id).unwrap()
129 }
130
131 fn dep(name: &str, version: &str) -> Dependency {
132 let src_id =
133 SourceId::from_url("registry+https://github.com/rust-lang/crates.io-index").unwrap();
134 Dependency::parse(name, Some(version), src_id).unwrap()
135 }
136
137 fn summ(name: &str, version: &str, msrv: Option<&str>) -> Summary {
138 let pkg_id = pkgid(name, version);
139 let features = BTreeMap::new();
140 Summary::new(
141 pkg_id,
142 Vec::new(),
143 &features,
144 None::<&String>,
145 msrv.map(|m| m.parse().unwrap()),
146 )
147 .unwrap()
148 }
149
150 fn describe(summaries: &Vec<Summary>) -> String {
151 let strs: Vec<String> = summaries
152 .iter()
153 .map(|summary| format!("{}/{}", summary.name(), summary.version()))
154 .collect();
155 strs.join(", ")
156 }
157
158 #[test]
159 fn test_prefer_package_id() {
160 let mut vp = VersionPreferences::default();
161 vp.prefer_package_id(pkgid("foo", "1.2.3"));
162
163 let mut summaries = vec![
164 summ("foo", "1.2.4", None),
165 summ("foo", "1.2.3", None),
166 summ("foo", "1.1.0", None),
167 summ("foo", "1.0.9", None),
168 ];
169
170 vp.version_ordering(VersionOrdering::MaximumVersionsFirst);
171 vp.sort_summaries(&mut summaries, None);
172 assert_eq!(
173 describe(&summaries),
174 "foo/1.2.3, foo/1.2.4, foo/1.1.0, foo/1.0.9".to_string()
175 );
176
177 vp.version_ordering(VersionOrdering::MinimumVersionsFirst);
178 vp.sort_summaries(&mut summaries, None);
179 assert_eq!(
180 describe(&summaries),
181 "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string()
182 );
183 }
184
185 #[test]
186 fn test_prefer_dependency() {
187 let mut vp = VersionPreferences::default();
188 vp.prefer_dependency(dep("foo", "=1.2.3"));
189
190 let mut summaries = vec![
191 summ("foo", "1.2.4", None),
192 summ("foo", "1.2.3", None),
193 summ("foo", "1.1.0", None),
194 summ("foo", "1.0.9", None),
195 ];
196
197 vp.version_ordering(VersionOrdering::MaximumVersionsFirst);
198 vp.sort_summaries(&mut summaries, None);
199 assert_eq!(
200 describe(&summaries),
201 "foo/1.2.3, foo/1.2.4, foo/1.1.0, foo/1.0.9".to_string()
202 );
203
204 vp.version_ordering(VersionOrdering::MinimumVersionsFirst);
205 vp.sort_summaries(&mut summaries, None);
206 assert_eq!(
207 describe(&summaries),
208 "foo/1.2.3, foo/1.0.9, foo/1.1.0, foo/1.2.4".to_string()
209 );
210 }
211
212 #[test]
213 fn test_prefer_both() {
214 let mut vp = VersionPreferences::default();
215 vp.prefer_package_id(pkgid("foo", "1.2.3"));
216 vp.prefer_dependency(dep("foo", "=1.1.0"));
217
218 let mut summaries = vec![
219 summ("foo", "1.2.4", None),
220 summ("foo", "1.2.3", None),
221 summ("foo", "1.1.0", None),
222 summ("foo", "1.0.9", None),
223 ];
224
225 vp.version_ordering(VersionOrdering::MaximumVersionsFirst);
226 vp.sort_summaries(&mut summaries, None);
227 assert_eq!(
228 describe(&summaries),
229 "foo/1.2.3, foo/1.1.0, foo/1.2.4, foo/1.0.9".to_string()
230 );
231
232 vp.version_ordering(VersionOrdering::MinimumVersionsFirst);
233 vp.sort_summaries(&mut summaries, None);
234 assert_eq!(
235 describe(&summaries),
236 "foo/1.1.0, foo/1.2.3, foo/1.0.9, foo/1.2.4".to_string()
237 );
238 }
239
240 #[test]
241 fn test_single_rust_version() {
242 let mut vp = VersionPreferences::default();
243 vp.rust_versions(vec!["1.50".parse().unwrap()]);
244
245 let mut summaries = vec![
246 summ("foo", "1.2.4", None),
247 summ("foo", "1.2.3", Some("1.60")),
248 summ("foo", "1.2.2", None),
249 summ("foo", "1.2.1", Some("1.50")),
250 summ("foo", "1.2.0", None),
251 summ("foo", "1.1.0", Some("1.40")),
252 summ("foo", "1.0.9", None),
253 ];
254
255 vp.version_ordering(VersionOrdering::MaximumVersionsFirst);
256 vp.sort_summaries(&mut summaries, None);
257 assert_eq!(
258 describe(&summaries),
259 "foo/1.2.4, foo/1.2.2, foo/1.2.1, foo/1.2.0, foo/1.1.0, foo/1.0.9, foo/1.2.3"
260 .to_string()
261 );
262
263 vp.version_ordering(VersionOrdering::MinimumVersionsFirst);
264 vp.sort_summaries(&mut summaries, None);
265 assert_eq!(
266 describe(&summaries),
267 "foo/1.0.9, foo/1.1.0, foo/1.2.0, foo/1.2.1, foo/1.2.2, foo/1.2.4, foo/1.2.3"
268 .to_string()
269 );
270 }
271
272 #[test]
273 fn test_multiple_rust_versions() {
274 let mut vp = VersionPreferences::default();
275 vp.rust_versions(vec!["1.45".parse().unwrap(), "1.55".parse().unwrap()]);
276
277 let mut summaries = vec![
278 summ("foo", "1.2.4", None),
279 summ("foo", "1.2.3", Some("1.60")),
280 summ("foo", "1.2.2", None),
281 summ("foo", "1.2.1", Some("1.50")),
282 summ("foo", "1.2.0", None),
283 summ("foo", "1.1.0", Some("1.40")),
284 summ("foo", "1.0.9", None),
285 ];
286
287 vp.version_ordering(VersionOrdering::MaximumVersionsFirst);
288 vp.sort_summaries(&mut summaries, None);
289 assert_eq!(
290 describe(&summaries),
291 "foo/1.2.4, foo/1.2.2, foo/1.2.0, foo/1.1.0, foo/1.0.9, foo/1.2.1, foo/1.2.3"
292 .to_string()
293 );
294
295 vp.version_ordering(VersionOrdering::MinimumVersionsFirst);
296 vp.sort_summaries(&mut summaries, None);
297 assert_eq!(
298 describe(&summaries),
299 "foo/1.0.9, foo/1.1.0, foo/1.2.0, foo/1.2.2, foo/1.2.4, foo/1.2.1, foo/1.2.3"
300 .to_string()
301 );
302 }
303
304 #[test]
305 fn test_empty_summaries() {
306 let vp = VersionPreferences::default();
307 let mut summaries = vec![];
308
309 vp.sort_summaries(&mut summaries, Some(VersionOrdering::MaximumVersionsFirst));
310 assert_eq!(summaries, vec![]);
311 }
312}