1use 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 added: HashSet<usize>,
20 added_order: Vec<usize>,
21 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 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 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 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 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 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 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 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}