@@ -23,15 +23,15 @@ pub use super::resolve::Resolve;
2323// possible.
2424#[ derive( Clone ) ]
2525pub struct Context {
26+ pub age : ContextAge ,
2627 pub activations : Activations ,
2728 /// list the features that are activated for each package
2829 pub resolve_features : im_rc:: HashMap < PackageId , FeaturesSet > ,
2930 /// get the package that will be linking to a native library by its links attribute
3031 pub links : im_rc:: HashMap < InternedString , PackageId > ,
3132 /// for each package the list of names it can see,
3233 /// then for each name the exact version that name represents and weather the name is public.
33- pub public_dependency :
34- Option < im_rc:: HashMap < PackageId , im_rc:: HashMap < InternedString , ( PackageId , bool ) > > > ,
34+ pub public_dependency : Option < PublicDependency > ,
3535
3636 /// a way to look up for a package in activations what packages required it
3737 /// and all of the exact deps that it fulfilled.
@@ -49,13 +49,13 @@ pub type ContextAge = usize;
4949/// By storing this in a hash map we ensure that there is only one
5050/// semver compatible version of each crate.
5151/// This all so stores the `ContextAge`.
52- pub type Activations =
53- im_rc:: HashMap < ( InternedString , SourceId , SemverCompatibility ) , ( Summary , ContextAge ) > ;
52+ pub type ActivationsKey = ( InternedString , SourceId , SemverCompatibility ) ;
53+ pub type Activations = im_rc:: HashMap < ActivationsKey , ( Summary , ContextAge ) > ;
5454
5555/// A type that represents when cargo treats two Versions as compatible.
5656/// Versions `a` and `b` are compatible if their left-most nonzero digit is the
5757/// same.
58- #[ derive( Clone , Copy , Eq , PartialEq , Hash , Debug ) ]
58+ #[ derive( Clone , Copy , Eq , PartialEq , Hash , Debug , PartialOrd , Ord ) ]
5959pub enum SemverCompatibility {
6060 Major ( NonZeroU64 ) ,
6161 Minor ( NonZeroU64 ) ,
@@ -75,18 +75,19 @@ impl From<&semver::Version> for SemverCompatibility {
7575}
7676
7777impl PackageId {
78- pub fn as_activations_key ( self ) -> ( InternedString , SourceId , SemverCompatibility ) {
78+ pub fn as_activations_key ( self ) -> ActivationsKey {
7979 ( self . name ( ) , self . source_id ( ) , self . version ( ) . into ( ) )
8080 }
8181}
8282
8383impl Context {
8484 pub fn new ( check_public_visible_dependencies : bool ) -> Context {
8585 Context {
86+ age : 0 ,
8687 resolve_features : im_rc:: HashMap :: new ( ) ,
8788 links : im_rc:: HashMap :: new ( ) ,
8889 public_dependency : if check_public_visible_dependencies {
89- Some ( im_rc :: HashMap :: new ( ) )
90+ Some ( PublicDependency :: new ( ) )
9091 } else {
9192 None
9293 } ,
@@ -109,7 +110,7 @@ impl Context {
109110 parent : Option < ( & Summary , & Dependency ) > ,
110111 ) -> ActivateResult < bool > {
111112 let id = summary. package_id ( ) ;
112- let age: ContextAge = self . age ( ) ;
113+ let age: ContextAge = self . age ;
113114 match self . activations . entry ( id. as_activations_key ( ) ) {
114115 im_rc:: hashmap:: Entry :: Occupied ( o) => {
115116 debug_assert_eq ! (
@@ -181,20 +182,49 @@ impl Context {
181182 } )
182183 }
183184
184- /// Returns the `ContextAge` of this `Context`.
185- /// For now we use (len of activations) as the age.
186- /// See the `ContextAge` docs for more details.
187- pub fn age ( & self ) -> ContextAge {
188- self . activations . len ( )
189- }
190-
191185 /// If the package is active returns the `ContextAge` when it was added
192186 pub fn is_active ( & self , id : PackageId ) -> Option < ContextAge > {
193187 self . activations
194188 . get ( & id. as_activations_key ( ) )
195189 . and_then ( |( s, l) | if s. package_id ( ) == id { Some ( * l) } else { None } )
196190 }
197191
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+
198228 /// Checks whether all of `parent` and the keys of `conflicting activations`
199229 /// are still active.
200230 /// If so returns the `ContextAge` when the newest one was added.
@@ -204,12 +234,12 @@ impl Context {
204234 conflicting_activations : & ConflictMap ,
205235 ) -> Option < usize > {
206236 let mut max = 0 ;
207- for & id in conflicting_activations . keys ( ) . chain ( parent. as_ref ( ) ) {
208- if let Some ( age ) = self . is_active ( id ) {
209- max = std :: cmp :: max ( max , age ) ;
210- } else {
211- return None ;
212- }
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 ) ? ) ;
213243 }
214244 Some ( max)
215245 }
@@ -240,3 +270,165 @@ impl Context {
240270 graph
241271 }
242272}
273+
274+ impl Graph < PackageId , Rc < Vec < 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 ) ]
282+ pub 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+
293+ impl 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 , Rc < Vec < 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 , Rc < Vec < 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: dont 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+ }
0 commit comments