]>
Commit | Line | Data |
---|---|---|
623863e5 E |
1 | //! There are 2 sources of facts for the resolver: |
2 | //! | |
79714ba9 E |
3 | //! - The `Registry` tells us for a `Dependency` what versions are available to fulfil it. |
4 | //! - The `Summary` tells us for a version (and features) what dependencies need to be fulfilled for it to be activated. | |
623863e5 E |
5 | //! |
6 | //! These constitute immutable facts, the soled ground truth that all other inference depends on. | |
7 | //! Theoretically this could all be enumerated ahead of time, but we want to be lazy and only | |
8 | //! look up things we need to. The compromise is to cache the results as they are computed. | |
9 | //! | |
79714ba9 | 10 | //! This module impl that cache in all the gory details |
623863e5 | 11 | |
1eca786d | 12 | use crate::core::resolver::context::Context; |
7d22a309 | 13 | use crate::core::resolver::errors::describe_path_in_context; |
07162dba | 14 | use crate::core::resolver::types::{ConflictReason, DepInfo, FeaturesSet}; |
85854b18 | 15 | use crate::core::resolver::{ |
76a079cb DM |
16 | ActivateError, ActivateResult, CliFeatures, RequestedFeatures, ResolveOpts, VersionOrdering, |
17 | VersionPreferences, | |
85854b18 | 18 | }; |
949f49a5 | 19 | use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry, Summary}; |
ebca5190 | 20 | use crate::util::errors::CargoResult; |
7f73a6c7 | 21 | use crate::util::interning::InternedString; |
ebca5190 WL |
22 | |
23 | use anyhow::Context as _; | |
07162dba | 24 | use log::debug; |
07162dba AC |
25 | use std::collections::{BTreeSet, HashMap, HashSet}; |
26 | use std::rc::Rc; | |
95de3a1d E |
27 | |
28 | pub struct RegistryQueryer<'a> { | |
29 | pub registry: &'a mut (dyn Registry + 'a), | |
30 | replacements: &'a [(PackageIdSpec, Dependency)], | |
76a079cb | 31 | version_prefs: &'a VersionPreferences, |
79714ba9 E |
32 | /// If set the list of dependency candidates will be sorted by minimal |
33 | /// versions first. That allows `cargo update -Z minimal-versions` which will | |
34 | /// specify minimum dependency versions to be used. | |
95de3a1d | 35 | minimal_versions: bool, |
79714ba9 | 36 | /// a cache of `Candidate`s that fulfil a `Dependency` |
c1b22c5b | 37 | registry_cache: HashMap<Dependency, Rc<Vec<Summary>>>, |
79714ba9 E |
38 | /// a cache of `Dependency`s that are required for a `Summary` |
39 | summary_cache: HashMap< | |
e26ef017 | 40 | (Option<PackageId>, Summary, ResolveOpts), |
79714ba9 E |
41 | Rc<(HashSet<InternedString>, Rc<Vec<DepInfo>>)>, |
42 | >, | |
43 | /// all the cases we ended up using a supplied replacement | |
c86b8345 | 44 | used_replacements: HashMap<PackageId, Summary>, |
95de3a1d E |
45 | } |
46 | ||
47 | impl<'a> RegistryQueryer<'a> { | |
48 | pub fn new( | |
49 | registry: &'a mut dyn Registry, | |
50 | replacements: &'a [(PackageIdSpec, Dependency)], | |
76a079cb | 51 | version_prefs: &'a VersionPreferences, |
95de3a1d E |
52 | minimal_versions: bool, |
53 | ) -> Self { | |
54 | RegistryQueryer { | |
55 | registry, | |
56 | replacements, | |
76a079cb | 57 | version_prefs, |
95de3a1d | 58 | minimal_versions, |
79714ba9 E |
59 | registry_cache: HashMap::new(), |
60 | summary_cache: HashMap::new(), | |
95de3a1d E |
61 | used_replacements: HashMap::new(), |
62 | } | |
63 | } | |
64 | ||
65 | pub fn used_replacement_for(&self, p: PackageId) -> Option<(PackageId, PackageId)> { | |
c86b8345 E |
66 | self.used_replacements.get(&p).map(|r| (p, r.package_id())) |
67 | } | |
68 | ||
69 | pub fn replacement_summary(&self, p: PackageId) -> Option<&Summary> { | |
70 | self.used_replacements.get(&p) | |
95de3a1d E |
71 | } |
72 | ||
73 | /// Queries the `registry` to return a list of candidates for `dep`. | |
74 | /// | |
75 | /// This method is the location where overrides are taken into account. If | |
76 | /// any candidates are returned which match an override then the override is | |
77 | /// applied by performing a second query for what the override should | |
78 | /// return. | |
c1b22c5b | 79 | pub fn query(&mut self, dep: &Dependency) -> CargoResult<Rc<Vec<Summary>>> { |
79714ba9 | 80 | if let Some(out) = self.registry_cache.get(dep).cloned() { |
95de3a1d E |
81 | return Ok(out); |
82 | } | |
83 | ||
84 | let mut ret = Vec::new(); | |
85 | self.registry.query( | |
86 | dep, | |
87 | &mut |s| { | |
c1b22c5b | 88 | ret.push(s); |
95de3a1d E |
89 | }, |
90 | false, | |
91 | )?; | |
c1b22c5b | 92 | for summary in ret.iter_mut() { |
95de3a1d E |
93 | let mut potential_matches = self |
94 | .replacements | |
95 | .iter() | |
96 | .filter(|&&(ref spec, _)| spec.matches(summary.package_id())); | |
97 | ||
98 | let &(ref spec, ref dep) = match potential_matches.next() { | |
99 | None => continue, | |
100 | Some(replacement) => replacement, | |
101 | }; | |
102 | debug!( | |
103 | "found an override for {} {}", | |
104 | dep.package_name(), | |
105 | dep.version_req() | |
106 | ); | |
107 | ||
108 | let mut summaries = self.registry.query_vec(dep, false)?.into_iter(); | |
109 | let s = summaries.next().ok_or_else(|| { | |
3a18c89a | 110 | anyhow::format_err!( |
95de3a1d E |
111 | "no matching package for override `{}` found\n\ |
112 | location searched: {}\n\ | |
113 | version required: {}", | |
114 | spec, | |
115 | dep.source_id(), | |
116 | dep.version_req() | |
117 | ) | |
118 | })?; | |
119 | let summaries = summaries.collect::<Vec<_>>(); | |
120 | if !summaries.is_empty() { | |
121 | let bullets = summaries | |
122 | .iter() | |
123 | .map(|s| format!(" * {}", s.package_id())) | |
124 | .collect::<Vec<_>>(); | |
3a18c89a | 125 | anyhow::bail!( |
95de3a1d E |
126 | "the replacement specification `{}` matched \ |
127 | multiple packages:\n * {}\n{}", | |
128 | spec, | |
129 | s.package_id(), | |
130 | bullets.join("\n") | |
131 | ); | |
132 | } | |
133 | ||
134 | // The dependency should be hard-coded to have the same name and an | |
135 | // exact version requirement, so both of these assertions should | |
136 | // never fail. | |
137 | assert_eq!(s.version(), summary.version()); | |
138 | assert_eq!(s.name(), summary.name()); | |
139 | ||
140 | let replace = if s.source_id() == summary.source_id() { | |
141 | debug!("Preventing\n{:?}\nfrom replacing\n{:?}", summary, s); | |
142 | None | |
143 | } else { | |
144 | Some(s) | |
145 | }; | |
146 | let matched_spec = spec.clone(); | |
147 | ||
148 | // Make sure no duplicates | |
149 | if let Some(&(ref spec, _)) = potential_matches.next() { | |
3a18c89a | 150 | anyhow::bail!( |
95de3a1d E |
151 | "overlapping replacement specifications found:\n\n \ |
152 | * {}\n * {}\n\nboth specifications match: {}", | |
153 | matched_spec, | |
154 | spec, | |
155 | summary.package_id() | |
156 | ); | |
157 | } | |
158 | ||
159 | for dep in summary.dependencies() { | |
160 | debug!("\t{} => {}", dep.package_name(), dep.version_req()); | |
161 | } | |
c86b8345 E |
162 | if let Some(r) = replace { |
163 | self.used_replacements.insert(summary.package_id(), r); | |
95de3a1d | 164 | } |
95de3a1d E |
165 | } |
166 | ||
bd4a353e | 167 | // When we attempt versions for a package we'll want to do so in a sorted fashion to pick |
76a079cb DM |
168 | // the "best candidates" first. VersionPreferences implements this notion. |
169 | self.version_prefs.sort_summaries( | |
170 | &mut ret, | |
171 | if self.minimal_versions { | |
172 | VersionOrdering::MinimumVersionsFirst | |
173 | } else { | |
174 | VersionOrdering::MaximumVersionsFirst | |
175 | }, | |
176 | ); | |
95de3a1d E |
177 | |
178 | let out = Rc::new(ret); | |
179 | ||
79714ba9 | 180 | self.registry_cache.insert(dep.clone(), out.clone()); |
95de3a1d E |
181 | |
182 | Ok(out) | |
183 | } | |
5ae13e32 | 184 | |
623863e5 | 185 | /// Find out what dependencies will be added by activating `candidate`, |
e26ef017 | 186 | /// with features described in `opts`. Then look up in the `registry` |
623863e5 E |
187 | /// the candidates that will fulfil each of these dependencies, as it is the |
188 | /// next obvious question. | |
5ae13e32 E |
189 | pub fn build_deps( |
190 | &mut self, | |
1eca786d | 191 | cx: &Context, |
5ae13e32 E |
192 | parent: Option<PackageId>, |
193 | candidate: &Summary, | |
e26ef017 | 194 | opts: &ResolveOpts, |
5ae13e32 | 195 | ) -> ActivateResult<Rc<(HashSet<InternedString>, Rc<Vec<DepInfo>>)>> { |
623863e5 E |
196 | // if we have calculated a result before, then we can just return it, |
197 | // as it is a "pure" query of its arguments. | |
5ae13e32 | 198 | if let Some(out) = self |
79714ba9 | 199 | .summary_cache |
e26ef017 | 200 | .get(&(parent, candidate.clone(), opts.clone())) |
5ae13e32 E |
201 | .cloned() |
202 | { | |
203 | return Ok(out); | |
204 | } | |
205 | // First, figure out our set of dependencies based on the requested set | |
206 | // of features. This also calculates what features we're going to enable | |
207 | // for our own dependencies. | |
e26ef017 | 208 | let (used_features, deps) = resolve_features(parent, candidate, opts)?; |
5ae13e32 E |
209 | |
210 | // Next, transform all dependencies into a list of possible candidates | |
211 | // which can satisfy that dependency. | |
212 | let mut deps = deps | |
213 | .into_iter() | |
214 | .map(|(dep, features)| { | |
ebca5190 | 215 | let candidates = self.query(&dep).with_context(|| { |
9099b491 | 216 | format!( |
1eca786d | 217 | "failed to get `{}` as a dependency of {}", |
a07fec1b | 218 | dep.package_name(), |
7d22a309 | 219 | describe_path_in_context(cx, &candidate.package_id()), |
a07fec1b EH |
220 | ) |
221 | })?; | |
623863e5 | 222 | Ok((dep, candidates, features)) |
5ae13e32 E |
223 | }) |
224 | .collect::<CargoResult<Vec<DepInfo>>>()?; | |
225 | ||
226 | // Attempt to resolve dependencies with fewer candidates before trying | |
227 | // dependencies with more candidates. This way if the dependency with | |
228 | // only one candidate can't be resolved we don't have to do a bunch of | |
229 | // work before we figure that out. | |
230 | deps.sort_by_key(|&(_, ref a, _)| a.len()); | |
231 | ||
232 | let out = Rc::new((used_features, Rc::new(deps))); | |
233 | ||
623863e5 | 234 | // If we succeed we add the result to the cache so we can use it again next time. |
34f2d471 | 235 | // We don't cache the failure cases as they don't impl Clone. |
79714ba9 | 236 | self.summary_cache |
e26ef017 | 237 | .insert((parent, candidate.clone(), opts.clone()), out.clone()); |
5ae13e32 E |
238 | |
239 | Ok(out) | |
240 | } | |
c36301c6 E |
241 | } |
242 | ||
623863e5 E |
243 | /// Returns the features we ended up using and |
244 | /// all dependencies and the features we want from each of them. | |
949f49a5 E |
245 | pub fn resolve_features<'b>( |
246 | parent: Option<PackageId>, | |
247 | s: &'b Summary, | |
e26ef017 | 248 | opts: &'b ResolveOpts, |
623863e5 | 249 | ) -> ActivateResult<(HashSet<InternedString>, Vec<(Dependency, FeaturesSet)>)> { |
949f49a5 E |
250 | // First, filter by dev-dependencies. |
251 | let deps = s.dependencies(); | |
e26ef017 | 252 | let deps = deps.iter().filter(|d| d.is_transitive() || opts.dev_deps); |
949f49a5 | 253 | |
bcfdf9fb | 254 | let reqs = build_requirements(parent, s, opts)?; |
949f49a5 | 255 | let mut ret = Vec::new(); |
bcfdf9fb EH |
256 | let default_dep = BTreeSet::new(); |
257 | let mut valid_dep_names = HashSet::new(); | |
949f49a5 E |
258 | |
259 | // Next, collect all actually enabled dependencies and their features. | |
260 | for dep in deps { | |
261 | // Skip optional dependencies, but not those enabled through a | |
262 | // feature | |
263 | if dep.is_optional() && !reqs.deps.contains_key(&dep.name_in_toml()) { | |
264 | continue; | |
265 | } | |
bcfdf9fb | 266 | valid_dep_names.insert(dep.name_in_toml()); |
949f49a5 E |
267 | // So we want this dependency. Move the features we want from |
268 | // `feature_deps` to `ret` and register ourselves as using this | |
269 | // name. | |
bcfdf9fb EH |
270 | let mut base = reqs |
271 | .deps | |
272 | .get(&dep.name_in_toml()) | |
273 | .unwrap_or(&default_dep) | |
274 | .clone(); | |
949f49a5 | 275 | base.extend(dep.features().iter()); |
623863e5 | 276 | ret.push((dep.clone(), Rc::new(base))); |
949f49a5 E |
277 | } |
278 | ||
bcfdf9fb | 279 | // This is a special case for command-line `--features |
f4ac82a0 | 280 | // dep_name/feat_name` where `dep_name` does not exist. All other |
bcfdf9fb EH |
281 | // validation is done either in `build_requirements` or |
282 | // `build_feature_map`. | |
2eee644c EH |
283 | if parent.is_none() { |
284 | for dep_name in reqs.deps.keys() { | |
285 | if !valid_dep_names.contains(dep_name) { | |
286 | let e = RequirementError::MissingDependency(*dep_name); | |
287 | return Err(e.into_activate_error(parent, s)); | |
288 | } | |
bcfdf9fb | 289 | } |
949f49a5 E |
290 | } |
291 | ||
bcfdf9fb | 292 | Ok((reqs.into_features(), ret)) |
949f49a5 E |
293 | } |
294 | ||
e26ef017 | 295 | /// Takes requested features for a single package from the input `ResolveOpts` and |
949f49a5 E |
296 | /// recurses to find all requested features, dependencies and requested |
297 | /// dependency features in a `Requirements` object, returning it to the resolver. | |
298 | fn build_requirements<'a, 'b: 'a>( | |
bcfdf9fb | 299 | parent: Option<PackageId>, |
949f49a5 | 300 | s: &'a Summary, |
e26ef017 | 301 | opts: &'b ResolveOpts, |
bcfdf9fb | 302 | ) -> ActivateResult<Requirements<'a>> { |
949f49a5 E |
303 | let mut reqs = Requirements::new(s); |
304 | ||
85854b18 EH |
305 | let handle_default = |uses_default_features, reqs: &mut Requirements<'_>| { |
306 | if uses_default_features && s.features().contains_key("default") { | |
307 | if let Err(e) = reqs.require_feature(InternedString::new("default")) { | |
bcfdf9fb EH |
308 | return Err(e.into_activate_error(parent, s)); |
309 | } | |
e26ef017 | 310 | } |
85854b18 EH |
311 | Ok(()) |
312 | }; | |
313 | ||
314 | match &opts.features { | |
315 | RequestedFeatures::CliFeatures(CliFeatures { | |
316 | features, | |
317 | all_features, | |
318 | uses_default_features, | |
319 | }) => { | |
320 | if *all_features { | |
321 | for key in s.features().keys() { | |
322 | if let Err(e) = reqs.require_feature(*key) { | |
323 | return Err(e.into_activate_error(parent, s)); | |
324 | } | |
325 | } | |
326 | } else { | |
327 | for fv in features.iter() { | |
1b7fa21f | 328 | if let Err(e) = reqs.require_value(fv) { |
85854b18 EH |
329 | return Err(e.into_activate_error(parent, s)); |
330 | } | |
331 | } | |
332 | handle_default(*uses_default_features, &mut reqs)?; | |
bcfdf9fb | 333 | } |
949f49a5 | 334 | } |
85854b18 EH |
335 | RequestedFeatures::DepFeatures { |
336 | features, | |
337 | uses_default_features, | |
338 | } => { | |
339 | for feature in features.iter() { | |
340 | if let Err(e) = reqs.require_feature(*feature) { | |
341 | return Err(e.into_activate_error(parent, s)); | |
342 | } | |
343 | } | |
344 | handle_default(*uses_default_features, &mut reqs)?; | |
bcfdf9fb | 345 | } |
949f49a5 | 346 | } |
e26ef017 | 347 | |
949f49a5 E |
348 | Ok(reqs) |
349 | } | |
350 | ||
bcfdf9fb EH |
351 | /// Set of feature and dependency requirements for a package. |
352 | #[derive(Debug)] | |
949f49a5 E |
353 | struct Requirements<'a> { |
354 | summary: &'a Summary, | |
bcfdf9fb EH |
355 | /// The deps map is a mapping of dependency name to list of features enabled. |
356 | /// | |
357 | /// The resolver will activate all of these dependencies, with the given | |
358 | /// features enabled. | |
359 | deps: HashMap<InternedString, BTreeSet<InternedString>>, | |
360 | /// The set of features enabled on this package which is later used when | |
361 | /// compiling to instruct the code what features were enabled. | |
362 | features: HashSet<InternedString>, | |
363 | } | |
364 | ||
365 | /// An error for a requirement. | |
366 | /// | |
367 | /// This will later be converted to an `ActivateError` depending on whether or | |
368 | /// not this is a dependency or a root package. | |
369 | enum RequirementError { | |
370 | /// The package does not have the requested feature. | |
371 | MissingFeature(InternedString), | |
372 | /// The package does not have the requested dependency. | |
373 | MissingDependency(InternedString), | |
374 | /// A feature has a direct cycle to itself. | |
375 | /// | |
376 | /// Note that cycles through multiple features are allowed (but perhaps | |
377 | /// they shouldn't be?). | |
378 | Cycle(InternedString), | |
949f49a5 E |
379 | } |
380 | ||
381 | impl Requirements<'_> { | |
382 | fn new(summary: &Summary) -> Requirements<'_> { | |
383 | Requirements { | |
384 | summary, | |
385 | deps: HashMap::new(), | |
bcfdf9fb | 386 | features: HashSet::new(), |
949f49a5 E |
387 | } |
388 | } | |
389 | ||
bcfdf9fb EH |
390 | fn into_features(self) -> HashSet<InternedString> { |
391 | self.features | |
949f49a5 E |
392 | } |
393 | ||
f4ac82a0 | 394 | fn require_dep_feature( |
bcfdf9fb EH |
395 | &mut self, |
396 | package: InternedString, | |
397 | feat: InternedString, | |
9034e488 | 398 | weak: bool, |
bcfdf9fb | 399 | ) -> Result<(), RequirementError> { |
dbc2c2b5 AC |
400 | // If `package` is indeed an optional dependency then we activate the |
401 | // feature named `package`, but otherwise if `package` is a required | |
402 | // dependency then there's no feature associated with it. | |
9034e488 | 403 | if !weak |
bcfdf9fb EH |
404 | && self |
405 | .summary | |
406 | .dependencies() | |
407 | .iter() | |
408 | .any(|dep| dep.name_in_toml() == package && dep.is_optional()) | |
dbc2c2b5 | 409 | { |
bcfdf9fb | 410 | self.require_feature(package)?; |
949f49a5 | 411 | } |
bcfdf9fb EH |
412 | self.deps.entry(package).or_default().insert(feat); |
413 | Ok(()) | |
949f49a5 E |
414 | } |
415 | ||
416 | fn require_dependency(&mut self, pkg: InternedString) { | |
bcfdf9fb | 417 | self.deps.entry(pkg).or_default(); |
949f49a5 E |
418 | } |
419 | ||
bcfdf9fb EH |
420 | fn require_feature(&mut self, feat: InternedString) -> Result<(), RequirementError> { |
421 | if !self.features.insert(feat) { | |
422 | // Already seen this feature. | |
949f49a5 E |
423 | return Ok(()); |
424 | } | |
bcfdf9fb EH |
425 | |
426 | let fvs = match self.summary.features().get(&feat) { | |
427 | Some(fvs) => fvs, | |
428 | None => return Err(RequirementError::MissingFeature(feat)), | |
429 | }; | |
430 | ||
431 | for fv in fvs { | |
432 | if let FeatureValue::Feature(dep_feat) = fv { | |
433 | if *dep_feat == feat { | |
434 | return Err(RequirementError::Cycle(feat)); | |
435 | } | |
949f49a5 E |
436 | } |
437 | self.require_value(fv)?; | |
438 | } | |
439 | Ok(()) | |
440 | } | |
441 | ||
bcfdf9fb | 442 | fn require_value(&mut self, fv: &FeatureValue) -> Result<(), RequirementError> { |
949f49a5 E |
443 | match fv { |
444 | FeatureValue::Feature(feat) => self.require_feature(*feat)?, | |
f4ac82a0 EH |
445 | FeatureValue::Dep { dep_name } => self.require_dependency(*dep_name), |
446 | FeatureValue::DepFeature { | |
bcfdf9fb EH |
447 | dep_name, |
448 | dep_feature, | |
9ffcf690 EH |
449 | // Weak features are always activated in the dependency |
450 | // resolver. They will be narrowed inside the new feature | |
451 | // resolver. | |
9034e488 EH |
452 | weak, |
453 | } => self.require_dep_feature(*dep_name, *dep_feature, *weak)?, | |
949f49a5 E |
454 | }; |
455 | Ok(()) | |
456 | } | |
457 | } | |
bcfdf9fb EH |
458 | |
459 | impl RequirementError { | |
460 | fn into_activate_error(self, parent: Option<PackageId>, summary: &Summary) -> ActivateError { | |
461 | match self { | |
462 | RequirementError::MissingFeature(feat) => { | |
463 | let deps: Vec<_> = summary | |
464 | .dependencies() | |
465 | .iter() | |
466 | .filter(|dep| dep.name_in_toml() == feat) | |
467 | .collect(); | |
468 | if deps.is_empty() { | |
469 | return match parent { | |
470 | None => ActivateError::Fatal(anyhow::format_err!( | |
471 | "Package `{}` does not have the feature `{}`", | |
472 | summary.package_id(), | |
473 | feat | |
474 | )), | |
475 | Some(p) => ActivateError::Conflict( | |
476 | p, | |
477 | ConflictReason::MissingFeatures(feat.to_string()), | |
478 | ), | |
479 | }; | |
480 | } | |
481 | if deps.iter().any(|dep| dep.is_optional()) { | |
482 | match parent { | |
483 | None => ActivateError::Fatal(anyhow::format_err!( | |
484 | "Package `{}` does not have feature `{}`. It has an optional dependency \ | |
f4ac82a0 | 485 | with that name, but that dependency uses the \"dep:\" \ |
bcfdf9fb EH |
486 | syntax in the features table, so it does not have an implicit feature with that name.", |
487 | summary.package_id(), | |
488 | feat | |
489 | )), | |
490 | Some(p) => ActivateError::Conflict( | |
491 | p, | |
492 | ConflictReason::NonImplicitDependencyAsFeature(feat), | |
493 | ), | |
494 | } | |
495 | } else { | |
496 | match parent { | |
497 | None => ActivateError::Fatal(anyhow::format_err!( | |
498 | "Package `{}` does not have feature `{}`. It has a required dependency \ | |
499 | with that name, but only optional dependencies can be used as features.", | |
500 | summary.package_id(), | |
501 | feat | |
502 | )), | |
503 | Some(p) => ActivateError::Conflict( | |
504 | p, | |
505 | ConflictReason::RequiredDependencyAsFeature(feat), | |
506 | ), | |
507 | } | |
508 | } | |
509 | } | |
510 | RequirementError::MissingDependency(dep_name) => { | |
511 | match parent { | |
512 | None => ActivateError::Fatal(anyhow::format_err!( | |
513 | "package `{}` does not have a dependency named `{}`", | |
514 | summary.package_id(), | |
515 | dep_name | |
516 | )), | |
517 | // This code path currently isn't used, since `foo/bar` | |
f4ac82a0 | 518 | // and `dep:` syntax is not allowed in a dependency. |
bcfdf9fb EH |
519 | Some(p) => ActivateError::Conflict( |
520 | p, | |
521 | ConflictReason::MissingFeatures(dep_name.to_string()), | |
522 | ), | |
523 | } | |
524 | } | |
525 | RequirementError::Cycle(feat) => ActivateError::Fatal(anyhow::format_err!( | |
526 | "cyclic feature dependency: feature `{}` depends on itself", | |
527 | feat | |
528 | )), | |
529 | } | |
530 | } | |
531 | } |