1use crate::constraint::{Constraint, Op};
6use crate::package::Package;
7use crate::pool::Pool;
8use crate::version::stability_rank;
9use std::cmp::Ordering;
10use std::collections::HashMap;
11
12pub struct DefaultPolicy {
13 pub prefer_stable: bool,
14 pub prefer_lowest: bool,
15 pub prefer_dev_over_prerelease: bool,
17 pub preferred_versions: Option<HashMap<String, String>>,
19 result_cache: HashMap<(u64, String), Vec<i64>>,
22 sorting_cache: HashMap<(u64, String), Ordering>,
23}
24
25impl DefaultPolicy {
26 pub fn new(
27 prefer_stable: bool,
28 prefer_lowest: bool,
29 preferred_versions: Option<HashMap<String, String>>,
30 ) -> DefaultPolicy {
31 let prefer_dev = std::env::var("COMPOSER_PREFER_DEV_OVER_PRERELEASE")
32 .map(|v| !(v.is_empty() || v == "0"))
33 .unwrap_or(false);
34 DefaultPolicy {
35 prefer_stable,
36 prefer_lowest,
37 prefer_dev_over_prerelease: prefer_dev,
38 preferred_versions,
39 result_cache: HashMap::new(),
40 sorting_cache: HashMap::new(),
41 }
42 }
43
44 pub fn version_compare(&self, a: &Package, b: &Package, operator: Op) -> bool {
46 if self.prefer_stable && a.stability != b.stability {
47 let (mut stab_a, mut stab_b) = (a.stability, b.stability);
48 if self.prefer_lowest
49 && self.prefer_dev_over_prerelease
50 && stab_a != "stable"
51 && stab_b != "stable"
52 {
53 if stab_a == "dev" {
54 stab_a = "stable";
55 }
56 if stab_b == "dev" {
57 stab_b = "stable";
58 }
59 }
60 return stability_rank(stab_a) < stability_rank(stab_b);
61 }
62 if (a.is_dev() && a.version.starts_with("dev-"))
63 || (b.is_dev() && b.version.starts_with("dev-"))
64 {
65 let constraint = Constraint::new(operator, b.version.clone());
66 let version = Constraint::new(Op::Eq, a.version.clone());
67 return constraint.match_specific(&version, true);
68 }
69 Constraint::new(operator, b.version.clone()).matches_version(&a.version)
70 }
71
72 pub fn select_preferred_packages(
74 &mut self,
75 pool: &Pool,
76 arena: &[Package],
77 literals: &[i64],
78 required_package: Option<&str>,
79 ) -> Vec<i64> {
80 let mut literals = literals.to_vec();
81 literals.sort_unstable();
82 let key = format!(
83 "{}{}",
84 literals
85 .iter()
86 .map(|l| l.to_string())
87 .collect::<Vec<_>>()
88 .join(","),
89 required_package.unwrap_or("")
90 );
91 let key = (pool.identity, key);
92 if let Some(cached) = self.result_cache.get(&key) {
93 return cached.clone();
94 }
95 let mut groups: Vec<(String, Vec<i64>)> = Vec::new();
97 for &literal in &literals {
98 let name = &arena[pool.literal_to_package(literal)].name;
99 match groups.iter_mut().find(|(n, _)| n == name) {
100 Some((_, g)) => g.push(literal),
101 None => groups.push((name.clone(), vec![literal])),
102 }
103 }
104 for (_, group) in groups.iter_mut() {
105 let mut sorted = group.clone();
106 sorted.sort_by(|&a, &b| {
107 self.cached_compare(pool, arena, a, b, required_package, true, "i")
108 });
109 *group = self.prune_to_best_version(pool, arena, &sorted);
110 *group = Self::prune_remote_aliases(pool, arena, group);
111 }
112 let mut selected: Vec<i64> = groups.into_iter().flat_map(|(_, g)| g).collect();
113 selected
114 .sort_by(|&a, &b| self.cached_compare(pool, arena, a, b, required_package, false, ""));
115 self.result_cache.insert(key, selected.clone());
116 selected
117 }
118
119 #[allow(clippy::too_many_arguments)]
120 fn cached_compare(
121 &mut self,
122 pool: &Pool,
123 arena: &[Package],
124 a: i64,
125 b: i64,
126 required_package: Option<&str>,
127 ignore_replace: bool,
128 prefix: &str,
129 ) -> Ordering {
130 let key = (
131 pool.identity,
132 format!("{prefix}{a}.{b}{}", required_package.unwrap_or("")),
133 );
134 if let Some(o) = self.sorting_cache.get(&key) {
135 return *o;
136 }
137 let o = Self::compare_by_priority(pool, arena, a, b, required_package, ignore_replace);
138 self.sorting_cache.insert(key, o);
139 o
140 }
141
142 pub fn compare_by_priority(
144 pool: &Pool,
145 arena: &[Package],
146 la: i64,
147 lb: i64,
148 required_package: Option<&str>,
149 ignore_replace: bool,
150 ) -> Ordering {
151 let a = &arena[pool.literal_to_package(la)];
152 let b = &arena[pool.literal_to_package(lb)];
153 if a.name == b.name {
154 let (a_alias, b_alias) = (a.is_alias(), b.is_alias());
155 if a_alias && !b_alias {
156 return Ordering::Less;
157 }
158 if !a_alias && b_alias {
159 return Ordering::Greater;
160 }
161 }
162 if !ignore_replace {
163 if Self::replaces(a, b) {
164 return Ordering::Greater;
165 }
166 if Self::replaces(b, a) {
167 return Ordering::Less;
168 }
169 if let Some(req) = required_package {
170 if let Some(pos) = req.find('/') {
171 let vendor = &req[..pos];
172 let a_same = a.name.starts_with(vendor);
173 let b_same = b.name.starts_with(vendor);
174 if a_same != b_same {
175 return if a_same {
176 Ordering::Less
177 } else {
178 Ordering::Greater
179 };
180 }
181 }
182 }
183 }
184 la.abs().cmp(&lb.abs())
185 }
186
187 fn replaces(source: &Package, target: &Package) -> bool {
188 source.replaces.iter().any(|l| l.target == target.name)
189 }
190
191 fn prune_to_best_version(&self, pool: &Pool, arena: &[Package], literals: &[i64]) -> Vec<i64> {
193 if let Some(preferred) = &self.preferred_versions {
194 let name = &arena[pool.literal_to_package(literals[0])].name;
195 if let Some(version) = preferred.get(name) {
196 let best: Vec<i64> = literals
197 .iter()
198 .copied()
199 .filter(|&l| arena[pool.literal_to_package(l)].version == *version)
200 .collect();
201 if !best.is_empty() {
202 return best;
203 }
204 }
205 }
206 let operator = if self.prefer_lowest { Op::Lt } else { Op::Gt };
207 let mut best_literals = vec![literals[0]];
208 let mut best = pool.literal_to_package(literals[0]);
209 for &literal in &literals[1..] {
210 let package = pool.literal_to_package(literal);
211 if self.version_compare(&arena[package], &arena[best], operator) {
212 best = package;
213 best_literals = vec![literal];
214 } else if self.version_compare(&arena[package], &arena[best], Op::Eq) {
215 best_literals.push(literal);
216 }
217 }
218 best_literals
219 }
220
221 fn prune_remote_aliases(pool: &Pool, arena: &[Package], literals: &[i64]) -> Vec<i64> {
223 let is_root_alias = |l: i64| {
224 let p = &arena[pool.literal_to_package(l)];
225 p.is_alias() && p.root_package_alias
226 };
227 if !literals.iter().any(|&l| is_root_alias(l)) {
228 return literals.to_vec();
229 }
230 literals
231 .iter()
232 .copied()
233 .filter(|&l| is_root_alias(l))
234 .collect()
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::package::Origin;
242
243 #[test]
247 fn caches_are_scoped_to_the_pool() {
248 let mut arena = vec![Package::new(
249 "a/a",
250 "1.0.0.0",
251 "1.0.0",
252 Origin::Repository(0),
253 )];
254 let alias = arena[0].alias(0, "9999999-dev", "dev-main");
255 arena.push(alias);
256 arena.push(Package::new(
257 "b/b",
258 "1.0.0.0",
259 "1.0.0",
260 Origin::Repository(0),
261 ));
262 arena.push(Package::new(
263 "c/c",
264 "1.0.0.0",
265 "1.0.0",
266 Origin::Repository(0),
267 ));
268 let pool_a = Pool::new(vec![0, 1], Vec::new(), &arena);
269 let pool_b = Pool::new(vec![2, 3], Vec::new(), &arena);
270 let mut fresh = DefaultPolicy::new(true, false, None);
271 let expected = fresh.select_preferred_packages(&pool_b, &arena, &[1, 2], None);
272 let mut shared = DefaultPolicy::new(true, false, None);
273 shared.select_preferred_packages(&pool_a, &arena, &[1, 2], None);
274 assert_eq!(
275 shared.select_preferred_packages(&pool_b, &arena, &[1, 2], None),
276 expected
277 );
278 assert_eq!(expected, vec![1, 2]);
279 }
280}