Skip to content

Commit 680a4db

Browse files
committed
move compatible to a type
This makes a O(n^2) loop in the hart of the resolver a O(n) loop, but n is small and hashing is not free. So the main reason to do this is to make the code clearer.
1 parent 63231f4 commit 680a4db

File tree

2 files changed

+60
-68
lines changed

2 files changed

+60
-68
lines changed

src/cargo/core/resolver/context.rs

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::collections::{HashMap, HashSet};
2+
use std::num::NonZeroU64;
23
use std::rc::Rc;
34

45
// "ensure" seems to require "bail" be in scope (macro hygiene issue?).
@@ -52,8 +53,36 @@ pub struct Context {
5253
pub warnings: RcList<String>,
5354
}
5455

55-
/// list all the activated versions of a particular crate name from a source
56-
pub type Activations = im_rc::HashMap<(InternedString, SourceId), Rc<Vec<Summary>>>;
56+
/// find the activated version of a crate based on the name, source, and semver compatibility
57+
pub type Activations = im_rc::HashMap<(InternedString, SourceId, SemverCompatibility), Summary>;
58+
59+
/// A type that represents when cargo treats two Versions as compatible.
60+
/// Versions `a` and `b` are compatible if their left-most nonzero digit is the
61+
/// same.
62+
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
63+
pub enum SemverCompatibility {
64+
Major(NonZeroU64),
65+
Minor(NonZeroU64),
66+
Patch(u64),
67+
}
68+
69+
impl From<&semver::Version> for SemverCompatibility {
70+
fn from(ver: &semver::Version) -> Self {
71+
if let Some(m) = NonZeroU64::new(ver.major) {
72+
return SemverCompatibility::Major(m);
73+
}
74+
if let Some(m) = NonZeroU64::new(ver.minor) {
75+
return SemverCompatibility::Minor(m);
76+
}
77+
SemverCompatibility::Patch(ver.patch)
78+
}
79+
}
80+
81+
impl PackageId {
82+
pub fn as_activations_key(&self) -> (InternedString, SourceId, SemverCompatibility) {
83+
(self.name(), self.source_id(), self.version().into())
84+
}
85+
}
5786

5887
impl Context {
5988
pub fn new(check_public_visible_dependencies: bool) -> Context {
@@ -78,22 +107,27 @@ impl Context {
78107
/// Returns `true` if this summary with the given method is already activated.
79108
pub fn flag_activated(&mut self, summary: &Summary, method: &Method<'_>) -> CargoResult<bool> {
80109
let id = summary.package_id();
81-
let prev = self
82-
.activations
83-
.entry((id.name(), id.source_id()))
84-
.or_insert_with(|| Rc::new(Vec::new()));
85-
if !prev.iter().any(|c| c == summary) {
86-
self.resolve_graph.push(GraphNode::Add(id));
87-
if let Some(link) = summary.links() {
88-
ensure!(
89-
self.links.insert(link, id).is_none(),
90-
"Attempting to resolve a dependency with more then one crate with the \
91-
links={}.\nThis will not build as is. Consider rebuilding the .lock file.",
92-
&*link
110+
match self.activations.entry(id.as_activations_key()) {
111+
im_rc::hashmap::Entry::Occupied(o) => {
112+
debug_assert_eq!(
113+
o.get(),
114+
summary,
115+
"cargo does not allow two semver compatible versions"
93116
);
94117
}
95-
Rc::make_mut(prev).push(summary.clone());
96-
return Ok(false);
118+
im_rc::hashmap::Entry::Vacant(v) => {
119+
self.resolve_graph.push(GraphNode::Add(id));
120+
if let Some(link) = summary.links() {
121+
ensure!(
122+
self.links.insert(link, id).is_none(),
123+
"Attempting to resolve a dependency with more then one crate with the \
124+
links={}.\nThis will not build as is. Consider rebuilding the .lock file.",
125+
&*link
126+
);
127+
}
128+
v.insert(summary.clone());
129+
return Ok(false);
130+
}
97131
}
98132
debug!("checking if {} is already activated", summary.package_id());
99133
let (features, use_default) = match *method {
@@ -149,17 +183,10 @@ impl Context {
149183
Ok(deps)
150184
}
151185

152-
pub fn prev_active(&self, dep: &Dependency) -> &[Summary] {
153-
self.activations
154-
.get(&(dep.package_name(), dep.source_id()))
155-
.map(|v| &v[..])
156-
.unwrap_or(&[])
157-
}
158-
159186
pub fn is_active(&self, id: PackageId) -> bool {
160187
self.activations
161-
.get(&(id.name(), id.source_id()))
162-
.map(|v| v.iter().any(|s| s.package_id() == id))
188+
.get(&id.as_activations_key())
189+
.map(|s| s.package_id() == id)
163190
.unwrap_or(false)
164191
}
165192

src/cargo/core/resolver/mod.rs

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ pub fn resolve(
137137
let cx = activate_deps_loop(cx, &mut registry, summaries, config)?;
138138

139139
let mut cksums = HashMap::new();
140-
for summary in cx.activations.values().flat_map(|v| v.iter()) {
140+
for summary in cx.activations.values() {
141141
let cksum = summary.checksum().map(|s| s.to_string());
142142
cksums.insert(summary.package_id(), cksum);
143143
}
@@ -237,13 +237,6 @@ fn activate_deps_loop(
237237
dep.package_name(),
238238
candidates.len()
239239
);
240-
trace!(
241-
"{}[{}]>{} {} prev activations",
242-
parent.name(),
243-
cur,
244-
dep.package_name(),
245-
cx.prev_active(&dep).len()
246-
);
247240

248241
let just_here_for_the_error_messages = just_here_for_the_error_messages
249242
&& past_conflicting_activations
@@ -759,16 +752,15 @@ impl RemainingCandidates {
759752
dep: &Dependency,
760753
parent: PackageId,
761754
) -> Option<(Candidate, bool)> {
762-
let prev_active = cx.prev_active(dep);
763-
764755
'main: for (_, b) in self.remaining.by_ref() {
756+
let b_id = b.summary.package_id();
765757
// The `links` key in the manifest dictates that there's only one
766758
// package in a dependency graph, globally, with that particular
767759
// `links` key. If this candidate links to something that's already
768760
// linked to by a different package then we've gotta skip this.
769761
if let Some(link) = b.summary.links() {
770762
if let Some(&a) = cx.links.get(&link) {
771-
if a != b.summary.package_id() {
763+
if a != b_id {
772764
conflicting_prev_active
773765
.entry(a)
774766
.or_insert_with(|| ConflictReason::Links(link));
@@ -785,10 +777,7 @@ impl RemainingCandidates {
785777
//
786778
// Here we throw out our candidate if it's *compatible*, yet not
787779
// equal, to all previously activated versions.
788-
if let Some(a) = prev_active
789-
.iter()
790-
.find(|a| compatible(a.version(), b.summary.version()))
791-
{
780+
if let Some(a) = cx.activations.get(&b_id.as_activations_key()) {
792781
if *a != b.summary {
793782
conflicting_prev_active
794783
.entry(a.package_id())
@@ -803,11 +792,11 @@ impl RemainingCandidates {
803792
// all of witch also need to be checked the same way.
804793
if let Some(public_dependency) = cx.public_dependency.as_ref() {
805794
let existing_public_deps: Vec<PackageId> = public_dependency
806-
.get(&b.summary.package_id())
795+
.get(&b_id)
807796
.iter()
808797
.flat_map(|x| x.values())
809798
.filter_map(|x| if x.1 { Some(&x.0) } else { None })
810-
.chain(&Some(b.summary.package_id()))
799+
.chain(&Some(b_id))
811800
.cloned()
812801
.collect();
813802
for t in existing_public_deps {
@@ -851,27 +840,6 @@ impl RemainingCandidates {
851840
}
852841
}
853842

854-
// Returns if `a` and `b` are compatible in the semver sense. This is a
855-
// commutative operation.
856-
//
857-
// Versions `a` and `b` are compatible if their left-most nonzero digit is the
858-
// same.
859-
fn compatible(a: &semver::Version, b: &semver::Version) -> bool {
860-
if a.major != b.major {
861-
return false;
862-
}
863-
if a.major != 0 {
864-
return true;
865-
}
866-
if a.minor != b.minor {
867-
return false;
868-
}
869-
if a.minor != 0 {
870-
return true;
871-
}
872-
a.patch == b.patch
873-
}
874-
875843
/// Looks through the states in `backtrack_stack` for dependencies with
876844
/// remaining candidates. For each one, also checks if rolling back
877845
/// could change the outcome of the failed resolution that caused backtracking
@@ -937,11 +905,8 @@ fn find_candidate(
937905
}
938906

939907
fn check_cycles(resolve: &Resolve, activations: &Activations) -> CargoResult<()> {
940-
let summaries: HashMap<PackageId, &Summary> = activations
941-
.values()
942-
.flat_map(|v| v.iter())
943-
.map(|s| (s.package_id(), s))
944-
.collect();
908+
let summaries: HashMap<PackageId, &Summary> =
909+
activations.values().map(|s| (s.package_id(), s)).collect();
945910

946911
// Sort packages to produce user friendly deterministic errors.
947912
let mut all_packages: Vec<_> = resolve.iter().collect();

0 commit comments

Comments
 (0)