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}