1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
4
5#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
7pub struct ResolvedCrate {
8 pub name: String,
9 pub version: String,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ResolvedGraph {
15 pub crates: BTreeSet<ResolvedCrate>,
17 pub roots: Vec<ResolvedCrate>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct IndexEntry {
24 pub name: String,
25 #[serde(rename = "vers")]
26 pub version: String,
27 #[serde(default)]
28 pub deps: Vec<IndexDep>,
29 #[serde(rename = "cksum")]
30 pub checksum: String,
31 #[serde(default)]
32 pub features: BTreeMap<String, Vec<String>>,
33 #[serde(default)]
34 pub features2: BTreeMap<String, Vec<String>>,
35 #[serde(default)]
36 pub yanked: bool,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct IndexDep {
41 pub name: String,
42 pub req: String,
43 pub features: Vec<String>,
44 #[serde(default)]
45 pub optional: bool,
46 #[serde(default = "default_true")]
47 pub default_features: bool,
48 #[serde(default)]
49 pub target: Option<String>,
50 #[serde(default)]
51 pub kind: Option<String>,
52 #[serde(default)]
53 pub registry: Option<String>,
54 #[serde(default)]
55 pub package: Option<String>,
56}
57
58fn default_true() -> bool {
59 true
60}
61
62impl ResolvedGraph {
63 pub fn new() -> Self {
64 Self {
65 crates: BTreeSet::new(),
66 roots: Vec::new(),
67 }
68 }
69
70 pub fn crate_count(&self) -> usize {
72 self.crates.len()
73 }
74
75 pub fn crate_set(&self) -> BTreeSet<(String, String)> {
77 self.crates
78 .iter()
79 .map(|c| (c.name.clone(), c.version.clone()))
80 .collect()
81 }
82}
83
84#[derive(Debug, Clone)]
87struct ResolveRequest {
88 name: String,
89 version_req: String,
90 use_default_features: bool,
91}
92
93pub struct Resolver {
106 index_cache: HashMap<String, Vec<IndexEntry>>,
108 queue: VecDeque<ResolveRequest>,
110 resolved: BTreeSet<ResolvedCrate>,
112 visited: HashSet<(String, String)>,
114 roots: Vec<ResolvedCrate>,
116}
117
118impl Resolver {
119 pub fn new() -> Self {
120 Self {
121 index_cache: HashMap::new(),
122 queue: VecDeque::new(),
123 resolved: BTreeSet::new(),
124 visited: HashSet::new(),
125 roots: Vec::new(),
126 }
127 }
128
129 pub fn seed(&mut self, deps: &BTreeMap<String, String>) {
131 for (name, version_req) in deps {
132 self.queue.push_back(ResolveRequest {
133 name: name.clone(),
134 version_req: version_req.clone(),
135 use_default_features: true,
136 });
137 }
138 }
139
140 pub fn provide_index(&mut self, name: &str, entries: Vec<IndexEntry>) {
142 self.index_cache.insert(name.to_string(), entries);
143 }
144
145 pub fn pending_crates(&self) -> Vec<String> {
149 let mut needed: BTreeSet<String> = BTreeSet::new();
150 for req in &self.queue {
151 let dep_name = req.name.clone();
152 if !self.index_cache.contains_key(&dep_name) {
153 needed.insert(dep_name);
154 }
155 }
156 needed.into_iter().collect()
157 }
158
159 pub fn has_work(&self) -> bool {
161 !self.queue.is_empty()
162 }
163
164 pub fn advance(&mut self) -> Result<()> {
168 let current_queue: Vec<ResolveRequest> = self.queue.drain(..).collect();
169
170 for req in current_queue {
171 let entries = match self.index_cache.get(&req.name) {
172 Some(e) => e,
173 None => {
174 tracing::warn!("no index data for {} (skipping)", req.name);
175 continue;
176 }
177 };
178
179 let matched = match find_best_match(entries, &req.name, &req.version_req) {
180 Ok(m) => m,
181 Err(e) => {
182 tracing::warn!("{}", e);
183 continue;
184 }
185 };
186
187 let key = (matched.name.clone(), matched.version.clone());
188 if self.visited.contains(&key) {
189 continue;
190 }
191 self.visited.insert(key);
192
193 let resolved_crate = ResolvedCrate {
194 name: matched.name.clone(),
195 version: matched.version.clone(),
196 };
197
198 if self.resolved.is_empty() || self.roots.iter().any(|r| r.name == req.name) {
200 if !self.roots.iter().any(|r| r.name == req.name) {
201 } else {
203 }
205 }
206
207 self.resolved.insert(resolved_crate.clone());
208
209 let enabled_features =
211 compute_enabled_features(&matched, req.use_default_features);
212 let activated_optional_deps =
213 compute_activated_deps(&matched, &enabled_features);
214
215 for dep in &matched.deps {
217 if dep.kind.as_deref() == Some("dev") || dep.kind.as_deref() == Some("build") {
218 continue;
219 }
220
221 if dep.optional && !activated_optional_deps.contains(&dep.name) {
222 continue;
223 }
224
225 let dep_name = dep.package.as_deref().unwrap_or(&dep.name);
226
227 self.queue.push_back(ResolveRequest {
228 name: dep_name.to_string(),
229 version_req: dep.req.clone(),
230 use_default_features: dep.default_features,
231 });
232 }
233 }
234
235 Ok(())
236 }
237
238 pub fn finish(self, direct_deps: &BTreeMap<String, String>) -> ResolvedGraph {
240 let roots: Vec<ResolvedCrate> = direct_deps
242 .keys()
243 .filter_map(|name| {
244 self.resolved
245 .iter()
246 .find(|c| c.name == *name)
247 .cloned()
248 })
249 .collect();
250
251 ResolvedGraph {
252 crates: self.resolved,
253 roots,
254 }
255 }
256}
257
258fn compute_enabled_features(entry: &IndexEntry, use_default: bool) -> HashSet<String> {
262 let mut enabled = HashSet::new();
263
264 let mut all_features: BTreeMap<String, Vec<String>> = entry.features.clone();
265 for (k, v) in &entry.features2 {
266 all_features
267 .entry(k.clone())
268 .or_default()
269 .extend(v.clone());
270 }
271
272 if use_default {
273 if let Some(defaults) = all_features.get("default") {
274 let mut stack: Vec<String> = defaults.clone();
275 while let Some(feat) = stack.pop() {
276 if enabled.insert(feat.clone()) {
277 if let Some(sub) = all_features.get(&feat) {
278 stack.extend(sub.clone());
279 }
280 }
281 }
282 }
283 }
284
285 enabled
286}
287
288fn compute_activated_deps(entry: &IndexEntry, enabled_features: &HashSet<String>) -> HashSet<String> {
291 let mut activated = HashSet::new();
292
293 let optional_dep_names: HashSet<&str> = entry
294 .deps
295 .iter()
296 .filter(|d| d.optional)
297 .map(|d| d.name.as_str())
298 .collect();
299
300 let mut all_features: BTreeMap<String, Vec<String>> = entry.features.clone();
301 for (k, v) in &entry.features2 {
302 all_features
303 .entry(k.clone())
304 .or_default()
305 .extend(v.clone());
306 }
307
308 for feat_name in enabled_features {
309 if optional_dep_names.contains(feat_name.as_str()) {
310 activated.insert(feat_name.clone());
311 }
312
313 if let Some(values) = all_features.get(feat_name) {
314 for value in values {
315 if let Some(dep_ref) = value.strip_prefix("dep:") {
316 activated.insert(dep_ref.to_string());
317 } else if optional_dep_names.contains(value.as_str()) {
318 activated.insert(value.clone());
319 } else if value.contains('/') {
320 let dep_part = value.split('/').next().unwrap();
321 if optional_dep_names.contains(dep_part) {
322 activated.insert(dep_part.to_string());
323 }
324 }
325 }
326 }
327 }
328
329 activated
330}
331
332fn find_best_match(entries: &[IndexEntry], name: &str, version_req: &str) -> Result<IndexEntry> {
334 let req = semver::VersionReq::parse(version_req)
335 .or_else(|_| semver::VersionReq::parse(&format!("={}", version_req)))
336 .map_err(|e| anyhow::anyhow!("invalid version req '{}' for {}: {}", version_req, name, e))?;
337
338 let mut candidates: Vec<&IndexEntry> = entries
339 .iter()
340 .filter(|e| !e.yanked)
341 .filter(|e| {
342 semver::Version::parse(&e.version)
343 .map(|v| req.matches(&v))
344 .unwrap_or(false)
345 })
346 .collect();
347
348 candidates.sort_by(|a, b| {
349 let va = semver::Version::parse(&a.version).unwrap();
350 let vb = semver::Version::parse(&b.version).unwrap();
351 vb.cmp(&va)
352 });
353
354 candidates
355 .into_iter()
356 .next()
357 .cloned()
358 .ok_or_else(|| anyhow::anyhow!("no matching version for {} {}", name, version_req))
359}