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