Skip to main content

vivacity_resolver/
rules_gen.rs

1//! Port of `Composer\DependencyResolver\RuleSetGenerator`.
2
3use crate::constraint::Constraint;
4use crate::package::Package;
5use crate::platform_filter::PlatformRequirementFilter;
6use crate::pool::{Pool, Request};
7use crate::rule::{Reason, Rule, RuleSet, RuleType};
8use std::collections::{HashMap, HashSet, VecDeque};
9
10#[derive(Debug, thiserror::Error)]
11#[error("{0}")]
12pub struct RulesError(pub String);
13
14pub struct RuleSetGenerator<'a> {
15    pool: &'a Pool,
16    arena: &'a [Package],
17    rules: RuleSet,
18    /// `addedMap`: pool ids already processed (insertion order).
19    added: HashSet<usize>,
20    added_order: Vec<usize>,
21    /// `addedPackagesByNames`: name -> pool ids (ordered).
22    added_by_name: Vec<(String, Vec<usize>)>,
23    added_by_name_index: HashMap<String, usize>,
24}
25
26impl<'a> RuleSetGenerator<'a> {
27    pub fn new(pool: &'a Pool, arena: &'a [Package]) -> RuleSetGenerator<'a> {
28        RuleSetGenerator {
29            pool,
30            arena,
31            rules: RuleSet::new(),
32            added: HashSet::new(),
33            added_order: Vec::new(),
34            added_by_name: Vec::new(),
35            added_by_name_index: HashMap::new(),
36        }
37    }
38
39    fn package(&self, id: usize) -> &'a Package {
40        &self.arena[self.pool.package_by_id(id)]
41    }
42
43    /// `createRequireRule`: None if the package is among its own providers.
44    fn create_require_rule(&self, id: usize, providers: &[usize], reason: Reason) -> Option<Rule> {
45        let mut literals = vec![-(id as i64)];
46        for &p in providers {
47            if p == id {
48                return None;
49            }
50            literals.push(p as i64);
51        }
52        Some(Rule::generic(literals, reason))
53    }
54
55    fn add_rule(&mut self, rule_type: RuleType, rule: Option<Rule>) {
56        if let Some(r) = rule {
57            let _ = self.rules.add(r, rule_type);
58        }
59    }
60
61    fn add_by_name(&mut self, name: &str, id: usize) {
62        match self.added_by_name_index.get(name) {
63            Some(&i) => self.added_by_name[i].1.push(id),
64            None => {
65                self.added_by_name_index
66                    .insert(name.to_owned(), self.added_by_name.len());
67                self.added_by_name.push((name.to_owned(), vec![id]));
68            }
69        }
70    }
71
72    /// `addRulesForPackage`.
73    fn add_rules_for_package(&mut self, id: usize, filter: &PlatformRequirementFilter) {
74        let mut queue: VecDeque<usize> = VecDeque::new();
75        queue.push_back(id);
76        while let Some(id) = queue.pop_front() {
77            if !self.added.insert(id) {
78                continue;
79            }
80            self.added_order.push(id);
81            let package = self.package(id);
82            if let Some(base_idx) = package.alias_of {
83                let base = self
84                    .pool
85                    .id_of(base_idx)
86                    .expect("aliased package is in the pool");
87                queue.push_back(base);
88                let alias_arena = self.pool.package_by_id(id);
89                let r1 = self.create_require_rule(
90                    id,
91                    &[base],
92                    Reason::PackageAlias { alias: alias_arena },
93                );
94                self.add_rule(RuleType::Package, r1);
95                let r2 = self.create_require_rule(
96                    base,
97                    &[id],
98                    Reason::PackageInverseAlias { package: base_idx },
99                );
100                self.add_rule(RuleType::Package, r2);
101                if !package.has_self_version_requires {
102                    continue;
103                }
104            } else {
105                for name in package.names(false) {
106                    self.add_by_name(&name, id);
107                }
108            }
109            for link in package.requires.iter() {
110                if filter.is_ignored(&link.target) {
111                    continue;
112                }
113                let constraint = filter.filter_constraint(&link.target, &link.constraint, true);
114                let possible = self
115                    .pool
116                    .what_provides(self.arena, &link.target, Some(&constraint));
117                let rule =
118                    self.create_require_rule(id, &possible, Reason::PackageRequires(link.clone()));
119                self.add_rule(RuleType::Package, rule);
120                for p in possible {
121                    queue.push_back(p);
122                }
123            }
124        }
125    }
126
127    /// `addConflictRules`.
128    fn add_conflict_rules(&mut self, filter: &PlatformRequirementFilter) {
129        for &id in &self.added_order.clone() {
130            let package = self.package(id);
131            for link in package.conflicts.iter() {
132                if !self.added_by_name_index.contains_key(&link.target) {
133                    continue;
134                }
135                if filter.is_ignored(&link.target) {
136                    continue;
137                }
138                let constraint = filter.filter_constraint(&link.target, &link.constraint, false);
139                let conflicts =
140                    self.pool
141                        .what_provides(self.arena, &link.target, Some(&constraint));
142                for conflict in conflicts {
143                    let cp = self.package(conflict);
144                    if !cp.is_alias() || cp.name == link.target {
145                        if conflict == id {
146                            continue;
147                        }
148                        let rule = Rule::two_literals(
149                            -(id as i64),
150                            -(conflict as i64),
151                            Reason::PackageConflict(link.clone()),
152                        );
153                        let _ = self.rules.add(rule, RuleType::Package);
154                    }
155                }
156            }
157        }
158        for (name, ids) in self.added_by_name.clone() {
159            if ids.len() > 1 {
160                let literals: Vec<i64> = ids.iter().map(|&i| -(i as i64)).collect();
161                let reason = Reason::PackageSameName(name.clone());
162                let rule = if literals.len() == 2 {
163                    Rule::two_literals(literals[0], literals[1], reason)
164                } else {
165                    Rule::multi_conflict(literals, reason)
166                };
167                let _ = self.rules.add(rule, RuleType::Package);
168            }
169        }
170    }
171
172    /// `addRulesForRequest`.
173    fn add_rules_for_request(
174        &mut self,
175        request: &Request,
176        filter: &PlatformRequirementFilter,
177    ) -> Result<(), RulesError> {
178        for &fixed in &request.fixed_packages {
179            // A locked package removed by a filter list gets no rule: the
180            // solver will turn it into a problem.
181            if request.is_locked_package(fixed)
182                && self
183                    .pool
184                    .is_filter_list_removed(&self.arena[fixed].name, &self.arena[fixed].version)
185            {
186                continue;
187            }
188            let Some(id) = self.pool.id_of(fixed) else {
189                if self.pool.is_unacceptable_fixed_or_locked(fixed) {
190                    continue;
191                }
192                return Err(RulesError(format!(
193                    "Fixed package {} was not added to solver pool.",
194                    self.arena[fixed].pretty_string()
195                )));
196            };
197            self.add_rules_for_package(id, filter);
198            let rule = Rule::generic(vec![id as i64], Reason::Fixed { package: fixed });
199            let _ = self.rules.add(rule, RuleType::Request);
200        }
201        for (name, constraint) in request.requires.iter() {
202            if filter.is_ignored(name) {
203                continue;
204            }
205            let constraint: Constraint = filter.filter_constraint(name, constraint, true);
206            let packages = self.pool.what_provides(self.arena, name, Some(&constraint));
207            if !packages.is_empty() {
208                for &p in &packages {
209                    self.add_rules_for_package(p, filter);
210                }
211                let pretty = request.pretty_require(name, &constraint);
212                let rule = Rule::generic(
213                    packages.iter().map(|&p| p as i64).collect(),
214                    Reason::RootRequire {
215                        package_name: name.clone(),
216                        constraint,
217                        pretty,
218                    },
219                );
220                let _ = self.rules.add(rule, RuleType::Request);
221            }
222        }
223        Ok(())
224    }
225
226    /// `addRulesForRootAliases`.
227    fn add_rules_for_root_aliases(&mut self, filter: &PlatformRequirementFilter) {
228        for id in 1..=self.pool.len() {
229            if self.added.contains(&id) {
230                continue;
231            }
232            let package = self.package(id);
233            let Some(base_idx) = package.alias_of else {
234                continue;
235            };
236            let base_added = self
237                .pool
238                .id_of(base_idx)
239                .is_some_and(|b| self.added.contains(&b));
240            if package.root_package_alias || base_added {
241                self.add_rules_for_package(id, filter);
242            }
243        }
244    }
245
246    /// `getRulesFor`.
247    pub fn rules_for(
248        mut self,
249        request: &Request,
250        filter: &PlatformRequirementFilter,
251    ) -> Result<RuleSet, RulesError> {
252        self.add_rules_for_request(request, filter)?;
253        self.add_rules_for_root_aliases(filter);
254        self.add_conflict_rules(filter);
255        Ok(self.rules)
256    }
257}