cargo/core/resolver/context.rs
1use std::collections::HashMap;
2use std::num::NonZeroU64;
3
4use anyhow::format_err;
5use log::debug;
6
7use crate::core::interning::InternedString;
8use crate::core::{Dependency, PackageId, SourceId, Summary};
9use crate::util::Graph;
10
11use super::dep_cache::RegistryQueryer;
12use super::errors::ActivateResult;
13use super::types::{ConflictMap, ConflictReason, FeaturesSet, ResolveOpts};
14
15pub use super::encode::Metadata;
16pub use super::encode::{EncodableDependency, EncodablePackageId, EncodableResolve};
17pub use super::resolve::Resolve;
18
19// A `Context` is basically a bunch of local resolution information which is
20// kept around for all `BacktrackFrame` instances. As a result, this runs the
21// risk of being cloned *a lot* so we want to make this as cheap to clone as
22// possible.
23#[derive(Clone)]
24pub struct Context {
25 pub age: ContextAge,
26 pub activations: Activations,
27 /// list the features that are activated for each package
28 pub resolve_features: im_rc::HashMap<PackageId, FeaturesSet>,
29 /// get the package that will be linking to a native library by its links attribute
30 pub links: im_rc::HashMap<InternedString, PackageId>,
31 /// for each package the list of names it can see,
32 /// then for each name the exact version that name represents and weather the name is public.
33 pub public_dependency: Option<PublicDependency>,
34
35 /// a way to look up for a package in activations what packages required it
36 /// and all of the exact deps that it fulfilled.
37 pub parents: Graph<PackageId, im_rc::HashSet<Dependency>>,
38}
39
40/// When backtracking it can be useful to know how far back to go.
41/// The `ContextAge` of a `Context` is a monotonically increasing counter of the number
42/// of decisions made to get to this state.
43/// Several structures store the `ContextAge` when it was added,
44/// to be used in `find_candidate` for backtracking.
45pub type ContextAge = usize;
46
47/// Find the activated version of a crate based on the name, source, and semver compatibility.
48/// By storing this in a hash map we ensure that there is only one
49/// semver compatible version of each crate.
50/// This all so stores the `ContextAge`.
51pub type ActivationsKey = (InternedString, SourceId, SemverCompatibility);
52pub type Activations = im_rc::HashMap<ActivationsKey, (Summary, ContextAge)>;
53
54/// A type that represents when cargo treats two Versions as compatible.
55/// Versions `a` and `b` are compatible if their left-most nonzero digit is the
56/// same.
57#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
58pub enum SemverCompatibility {
59 Major(NonZeroU64),
60 Minor(NonZeroU64),
61 Patch(u64),
62}
63
64impl From<&semver::Version> for SemverCompatibility {
65 fn from(ver: &semver::Version) -> Self {
66 if let Some(m) = NonZeroU64::new(ver.major) {
67 return SemverCompatibility::Major(m);
68 }
69 if let Some(m) = NonZeroU64::new(ver.minor) {
70 return SemverCompatibility::Minor(m);
71 }
72 SemverCompatibility::Patch(ver.patch)
73 }
74}
75
76impl PackageId {
77 pub fn as_activations_key(self) -> ActivationsKey {
78 (self.name(), self.source_id(), self.version().into())
79 }
80}
81
82impl Context {
83 pub fn new(check_public_visible_dependencies: bool) -> Context {
84 Context {
85 age: 0,
86 resolve_features: im_rc::HashMap::new(),
87 links: im_rc::HashMap::new(),
88 public_dependency: if check_public_visible_dependencies {
89 Some(PublicDependency::new())
90 } else {
91 None
92 },
93 parents: Graph::new(),
94 activations: im_rc::HashMap::new(),
95 }
96 }
97
98 /// Activate this summary by inserting it into our list of known activations.
99 ///
100 /// The `parent` passed in here is the parent summary/dependency edge which
101 /// cased `summary` to get activated. This may not be present for the root
102 /// crate, for example.
103 ///
104 /// Returns `true` if this summary with the given features is already activated.
105 pub fn flag_activated(
106 &mut self,
107 summary: &Summary,
108 opts: &ResolveOpts,
109 parent: Option<(&Summary, &Dependency)>,
110 ) -> ActivateResult<bool> {
111 let id = summary.package_id();
112 let age: ContextAge = self.age;
113 match self.activations.entry(id.as_activations_key()) {
114 im_rc::hashmap::Entry::Occupied(o) => {
115 debug_assert_eq!(
116 &o.get().0,
117 summary,
118 "cargo does not allow two semver compatible versions"
119 );
120 }
121 im_rc::hashmap::Entry::Vacant(v) => {
122 if let Some(link) = summary.links() {
123 if self.links.insert(link, id).is_some() {
124 return Err(format_err!(
125 "Attempting to resolve a dependency with more then \
126 one crate with links={}.\nThis will not build as \
127 is. Consider rebuilding the .lock file.",
128 &*link
129 )
130 .into());
131 }
132 }
133 v.insert((summary.clone(), age));
134
135 // If we've got a parent dependency which activated us, *and*
136 // the dependency has a different source id listed than the
137 // `summary` itself, then things get interesting. This basically
138 // means that a `[patch]` was used to augment `dep.source_id()`
139 // with `summary`.
140 //
141 // In this scenario we want to consider the activation key, as
142 // viewed from the perspective of `dep.source_id()`, as being
143 // fulfilled. This means that we need to add a second entry in
144 // the activations map for the source that was patched, in
145 // addition to the source of the actual `summary` itself.
146 //
147 // Without this it would be possible to have both 1.0.0 and
148 // 1.1.0 "from crates.io" in a dependency graph if one of those
149 // versions came from a `[patch]` source.
150 if let Some((_, dep)) = parent {
151 if dep.source_id() != id.source_id() {
152 let key = (id.name(), dep.source_id(), id.version().into());
153 let prev = self.activations.insert(key, (summary.clone(), age));
154 if let Some((previous_summary, _)) = prev {
155 return Err(
156 (previous_summary.package_id(), ConflictReason::Semver).into()
157 );
158 }
159 }
160 }
161
162 return Ok(false);
163 }
164 }
165 debug!("checking if {} is already activated", summary.package_id());
166 if opts.features.all_features {
167 return Ok(false);
168 }
169
170 let has_default_feature = summary.features().contains_key("default");
171 Ok(match self.resolve_features.get(&id) {
172 Some(prev) => {
173 opts.features.features.is_subset(prev)
174 && (!opts.features.uses_default_features
175 || prev.contains("default")
176 || !has_default_feature)
177 }
178 None => {
179 opts.features.features.is_empty()
180 && (!opts.features.uses_default_features || !has_default_feature)
181 }
182 })
183 }
184
185 /// If the package is active returns the `ContextAge` when it was added
186 pub fn is_active(&self, id: PackageId) -> Option<ContextAge> {
187 self.activations
188 .get(&id.as_activations_key())
189 .and_then(|(s, l)| if s.package_id() == id { Some(*l) } else { None })
190 }
191
192 /// If the conflict reason on the package still applies returns the `ContextAge` when it was added
193 pub fn still_applies(&self, id: PackageId, reason: &ConflictReason) -> Option<ContextAge> {
194 self.is_active(id).and_then(|mut max| {
195 match reason {
196 ConflictReason::PublicDependency(name) => {
197 if &id == name {
198 return Some(max);
199 }
200 max = std::cmp::max(max, self.is_active(*name)?);
201 max = std::cmp::max(
202 max,
203 self.public_dependency
204 .as_ref()
205 .unwrap()
206 .can_see_item(*name, id)?,
207 );
208 }
209 ConflictReason::PubliclyExports(name) => {
210 if &id == name {
211 return Some(max);
212 }
213 max = std::cmp::max(max, self.is_active(*name)?);
214 max = std::cmp::max(
215 max,
216 self.public_dependency
217 .as_ref()
218 .unwrap()
219 .publicly_exports_item(*name, id)?,
220 );
221 }
222 _ => {}
223 }
224 Some(max)
225 })
226 }
227
228 /// Checks whether all of `parent` and the keys of `conflicting activations`
229 /// are still active.
230 /// If so returns the `ContextAge` when the newest one was added.
231 pub fn is_conflicting(
232 &self,
233 parent: Option<PackageId>,
234 conflicting_activations: &ConflictMap,
235 ) -> Option<usize> {
236 let mut max = 0;
237 if let Some(parent) = parent {
238 max = std::cmp::max(max, self.is_active(parent)?);
239 }
240
241 for (id, reason) in conflicting_activations.iter() {
242 max = std::cmp::max(max, self.still_applies(*id, reason)?);
243 }
244 Some(max)
245 }
246
247 pub fn resolve_replacements(
248 &self,
249 registry: &RegistryQueryer<'_>,
250 ) -> HashMap<PackageId, PackageId> {
251 self.activations
252 .values()
253 .filter_map(|(s, _)| registry.used_replacement_for(s.package_id()))
254 .collect()
255 }
256
257 pub fn graph(&self) -> Graph<PackageId, std::collections::HashSet<Dependency>> {
258 let mut graph: Graph<PackageId, std::collections::HashSet<Dependency>> = Graph::new();
259 self.activations
260 .values()
261 .for_each(|(r, _)| graph.add(r.package_id()));
262 for i in self.parents.iter() {
263 graph.add(*i);
264 for (o, e) in self.parents.edges(i) {
265 let old_link = graph.link(*o, *i);
266 assert!(old_link.is_empty());
267 *old_link = e.iter().cloned().collect();
268 }
269 }
270 graph
271 }
272}
273
274impl Graph<PackageId, im_rc::HashSet<Dependency>> {
275 pub fn parents_of(&self, p: PackageId) -> impl Iterator<Item = (PackageId, bool)> + '_ {
276 self.edges(&p)
277 .map(|(grand, d)| (*grand, d.iter().any(|x| x.is_public())))
278 }
279}
280
281#[derive(Clone, Debug, Default)]
282pub struct PublicDependency {
283 /// For each active package the set of all the names it can see,
284 /// for each name the exact package that name resolves to,
285 /// the `ContextAge` when it was first visible,
286 /// and the `ContextAge` when it was first exported.
287 inner: im_rc::HashMap<
288 PackageId,
289 im_rc::HashMap<InternedString, (PackageId, ContextAge, Option<ContextAge>)>,
290 >,
291}
292
293impl PublicDependency {
294 fn new() -> Self {
295 PublicDependency {
296 inner: im_rc::HashMap::new(),
297 }
298 }
299 fn publicly_exports(&self, candidate_pid: PackageId) -> Vec<PackageId> {
300 self.inner
301 .get(&candidate_pid) // if we have seen it before
302 .iter()
303 .flat_map(|x| x.values()) // all the things we have stored
304 .filter(|x| x.2.is_some()) // as publicly exported
305 .map(|x| x.0)
306 .chain(Some(candidate_pid)) // but even if not we know that everything exports itself
307 .collect()
308 }
309 fn publicly_exports_item(
310 &self,
311 candidate_pid: PackageId,
312 target: PackageId,
313 ) -> Option<ContextAge> {
314 debug_assert_ne!(candidate_pid, target);
315 let out = self
316 .inner
317 .get(&candidate_pid)
318 .and_then(|names| names.get(&target.name()))
319 .filter(|(p, _, _)| *p == target)
320 .and_then(|(_, _, age)| *age);
321 debug_assert_eq!(
322 out.is_some(),
323 self.publicly_exports(candidate_pid).contains(&target)
324 );
325 out
326 }
327 pub fn can_see_item(&self, candidate_pid: PackageId, target: PackageId) -> Option<ContextAge> {
328 self.inner
329 .get(&candidate_pid)
330 .and_then(|names| names.get(&target.name()))
331 .filter(|(p, _, _)| *p == target)
332 .map(|(_, age, _)| *age)
333 }
334 pub fn add_edge(
335 &mut self,
336 candidate_pid: PackageId,
337 parent_pid: PackageId,
338 is_public: bool,
339 age: ContextAge,
340 parents: &Graph<PackageId, im_rc::HashSet<Dependency>>,
341 ) {
342 // one tricky part is that `candidate_pid` may already be active and
343 // have public dependencies of its own. So we not only need to mark
344 // `candidate_pid` as visible to its parents but also all of its existing
345 // publicly exported dependencies.
346 for c in self.publicly_exports(candidate_pid) {
347 // for each (transitive) parent that can newly see `t`
348 let mut stack = vec![(parent_pid, is_public)];
349 while let Some((p, public)) = stack.pop() {
350 match self.inner.entry(p).or_default().entry(c.name()) {
351 im_rc::hashmap::Entry::Occupied(mut o) => {
352 // the (transitive) parent can already see something by `c`s name, it had better be `c`.
353 assert_eq!(o.get().0, c);
354 if o.get().2.is_some() {
355 // The previous time the parent saw `c`, it was a public dependency.
356 // So all of its parents already know about `c`
357 // and we can save some time by stopping now.
358 continue;
359 }
360 if public {
361 // Mark that `c` has now bean seen publicly
362 let old_age = o.get().1;
363 o.insert((c, old_age, if public { Some(age) } else { None }));
364 }
365 }
366 im_rc::hashmap::Entry::Vacant(v) => {
367 // The (transitive) parent does not have anything by `c`s name,
368 // so we add `c`.
369 v.insert((c, age, if public { Some(age) } else { None }));
370 }
371 }
372 // if `candidate_pid` was a private dependency of `p` then `p` parents can't see `c` thru `p`
373 if public {
374 // if it was public, then we add all of `p`s parents to be checked
375 stack.extend(parents.parents_of(p));
376 }
377 }
378 }
379 }
380 pub fn can_add_edge(
381 &self,
382 b_id: PackageId,
383 parent: PackageId,
384 is_public: bool,
385 parents: &Graph<PackageId, im_rc::HashSet<Dependency>>,
386 ) -> Result<
387 (),
388 (
389 ((PackageId, ConflictReason), (PackageId, ConflictReason)),
390 Option<(PackageId, ConflictReason)>,
391 ),
392 > {
393 // one tricky part is that `candidate_pid` may already be active and
394 // have public dependencies of its own. So we not only need to check
395 // `b_id` as visible to its parents but also all of its existing
396 // publicly exported dependencies.
397 for t in self.publicly_exports(b_id) {
398 // for each (transitive) parent that can newly see `t`
399 let mut stack = vec![(parent, is_public)];
400 while let Some((p, public)) = stack.pop() {
401 // TODO: don't look at the same thing more then once
402 if let Some(o) = self.inner.get(&p).and_then(|x| x.get(&t.name())) {
403 if o.0 != t {
404 // the (transitive) parent can already see a different version by `t`s name.
405 // So, adding `b` will cause `p` to have a public dependency conflict on `t`.
406 return Err((
407 (o.0, ConflictReason::PublicDependency(p)), // p can see the other version and
408 (parent, ConflictReason::PublicDependency(p)), // p can see us
409 ))
410 .map_err(|e| {
411 if t == b_id {
412 (e, None)
413 } else {
414 (e, Some((t, ConflictReason::PubliclyExports(b_id))))
415 }
416 });
417 }
418 if o.2.is_some() {
419 // The previous time the parent saw `t`, it was a public dependency.
420 // So all of its parents already know about `t`
421 // and we can save some time by stopping now.
422 continue;
423 }
424 }
425 // if `b` was a private dependency of `p` then `p` parents can't see `t` thru `p`
426 if public {
427 // if it was public, then we add all of `p`s parents to be checked
428 stack.extend(parents.parents_of(p));
429 }
430 }
431 }
432 Ok(())
433 }
434}