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