1
- use std:: collections:: { BTreeMap , BTreeSet , HashMap , HashSet } ;
1
+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
2
2
3
3
use super :: types:: ConflictReason ;
4
4
use core:: resolver:: Context ;
5
5
use core:: { Dependency , PackageId } ;
6
6
7
+ enum ConflictStore {
8
+ Con ( BTreeMap < PackageId , ConflictReason > ) ,
9
+ Map ( HashMap < PackageId , ConflictStore > ) ,
10
+ }
11
+
12
+ impl ConflictStore {
13
+ /// Finds any known set of conflicts, if any,
14
+ /// which are activated in `cx` and pass the `filter` specified?
15
+ pub fn find_conflicting < F > (
16
+ & self ,
17
+ cx : & Context ,
18
+ filter : & F ,
19
+ ) -> Option < & BTreeMap < PackageId , ConflictReason > >
20
+ where
21
+ for < ' r > F : Fn ( & ' r & BTreeMap < PackageId , ConflictReason > ) -> bool ,
22
+ {
23
+ match self {
24
+ ConflictStore :: Con ( c) => {
25
+ if filter ( & c) {
26
+ Some ( c)
27
+ } else {
28
+ None
29
+ }
30
+ }
31
+ ConflictStore :: Map ( m) => {
32
+ for ( pid, store) in m {
33
+ if cx. is_active ( pid) {
34
+ if let Some ( o) = store. find_conflicting ( cx, filter) {
35
+ return Some ( o) ;
36
+ }
37
+ }
38
+ }
39
+ None
40
+ }
41
+ }
42
+ }
43
+ }
44
+
7
45
pub ( super ) struct ConflictCache {
8
46
// `con_from_dep` is a cache of the reasons for each time we
9
47
// backtrack. For example after several backtracks we may have:
10
48
//
11
- // con_from_dep[`foo = "^1.0.2"`] = vec![
12
- // map!{`foo=1.0.1`: Semver},
13
- // map!{`foo=1.0.0`: Semver},
14
- // ] ;
49
+ // con_from_dep[`foo = "^1.0.2"`] = map!{
50
+ // `foo=1.0.1`: map!{`foo=1.0.1`: Semver},
51
+ // `foo=1.0.0`: map!{`foo=1.0.0`: Semver},
52
+ // } ;
15
53
//
16
54
// This can be read as "we cannot find a candidate for dep `foo = "^1.0.2"`
17
55
// if either `foo=1.0.1` OR `foo=1.0.0` are activated".
18
56
//
19
57
// Another example after several backtracks we may have:
20
58
//
21
- // con_from_dep[`foo = ">=0.8.2, <=0.9.3"`] = vec![
22
- // map!{`foo=0.8.1`: Semver, `foo=0.9.4`: Semver},
23
- // ];
59
+ // con_from_dep[`foo = ">=0.8.2, <=0.9.3"`] = map!{
60
+ // `foo=0.8.1`: map!{
61
+ // `foo=0.9.4`: map!{`foo=0.8.1`: Semver, `foo=0.9.4`: Semver},
62
+ // }
63
+ // };
24
64
//
25
65
// This can be read as "we cannot find a candidate for dep `foo = ">=0.8.2,
26
66
// <=0.9.3"` if both `foo=0.8.1` AND `foo=0.9.4` are activated".
27
67
//
28
68
// This is used to make sure we don't queue work we know will fail. See the
29
69
// discussion in https://github.com/rust-lang/cargo/pull/5168 for why this
30
- // is so important, and there can probably be a better data structure here
31
- // but for now this works well enough!
70
+ // is so important. The nested HashMaps act as a kind of btree, that lets us
71
+ // look up a witch entry's are still active without
72
+ // linearly scanning through the full list.
32
73
//
33
74
// Also, as a final note, this map is *not* ever removed from. This remains
34
75
// as a global cache which we never delete from. Any entry in this map is
35
76
// unconditionally true regardless of our resolution history of how we got
36
77
// here.
37
- con_from_dep : HashMap < Dependency , BTreeSet < BTreeMap < PackageId , ConflictReason > > > ,
78
+ con_from_dep : HashMap < Dependency , ConflictStore > ,
38
79
// `dep_from_pid` is an inverse-index of `con_from_dep`.
39
80
// For every `PackageId` this lists the `Dependency`s that mention it in `dep_from_pid`.
40
81
dep_from_pid : HashMap < PackageId , HashSet < Dependency > > ,
@@ -56,14 +97,9 @@ impl ConflictCache {
56
97
filter : F ,
57
98
) -> Option < & BTreeMap < PackageId , ConflictReason > >
58
99
where
59
- for < ' r > F : FnMut ( & ' r & BTreeMap < PackageId , ConflictReason > ) -> bool ,
100
+ for < ' r > F : Fn ( & ' r & BTreeMap < PackageId , ConflictReason > ) -> bool ,
60
101
{
61
- self . con_from_dep
62
- . get ( dep) ?
63
- . iter ( )
64
- . rev ( ) // more general cases are normally found letter. So start the search there.
65
- . filter ( filter)
66
- . find ( |conflicting| cx. is_conflicting ( None , conflicting) )
102
+ self . con_from_dep . get ( dep) ?. find_conflicting ( cx, & filter)
67
103
}
68
104
pub fn conflicting (
69
105
& self ,
@@ -77,25 +113,43 @@ impl ConflictCache {
77
113
/// `dep` is known to be unresolvable if
78
114
/// all the `PackageId` entries are activated
79
115
pub fn insert ( & mut self , dep : & Dependency , con : & BTreeMap < PackageId , ConflictReason > ) {
80
- let past = self
116
+ let mut past = self
81
117
. con_from_dep
82
118
. entry ( dep. clone ( ) )
83
- . or_insert_with ( BTreeSet :: new) ;
84
- if !past. contains ( con) {
85
- trace ! (
86
- "{} = \" {}\" adding a skip {:?}" ,
87
- dep. package_name( ) ,
88
- dep. version_req( ) ,
89
- con
90
- ) ;
91
- past. insert ( con. clone ( ) ) ;
92
- for c in con. keys ( ) {
93
- self . dep_from_pid
94
- . entry ( c. clone ( ) )
95
- . or_insert_with ( HashSet :: new)
96
- . insert ( dep. clone ( ) ) ;
119
+ . or_insert_with ( || ConflictStore :: Map ( HashMap :: new ( ) ) ) ;
120
+
121
+ for pid in con. keys ( ) {
122
+ match past {
123
+ ConflictStore :: Con ( _) => {
124
+ // We already have a subset of this in the ConflictStore
125
+ return ;
126
+ }
127
+ ConflictStore :: Map ( p) => {
128
+ past = p
129
+ . entry ( pid. clone ( ) )
130
+ . or_insert_with ( || ConflictStore :: Map ( HashMap :: new ( ) ) ) ;
131
+ }
97
132
}
98
133
}
134
+
135
+ if let ConflictStore :: Con ( _) = past {
136
+ // We already have this in the ConflictStore
137
+ return ;
138
+ }
139
+ * past = ConflictStore :: Con ( con. clone ( ) ) ;
140
+ trace ! (
141
+ "{} = \" {}\" adding a skip {:?}" ,
142
+ dep. package_name( ) ,
143
+ dep. version_req( ) ,
144
+ con
145
+ ) ;
146
+
147
+ for c in con. keys ( ) {
148
+ self . dep_from_pid
149
+ . entry ( c. clone ( ) )
150
+ . or_insert_with ( HashSet :: new)
151
+ . insert ( dep. clone ( ) ) ;
152
+ }
99
153
}
100
154
pub fn dependencies_conflicting_with ( & self , pid : & PackageId ) -> Option < & HashSet < Dependency > > {
101
155
self . dep_from_pid . get ( pid)
0 commit comments