@ -18,6 +18,7 @@ from collections import defaultdict
import re
import re
from britney2 import DependencyType
from britney2 import DependencyType
from britney2 . excusedeps import DependencySpec , DependencyState , ImpossibleDependencyState
from britney2 . policies . policy import PolicyVerdict
from britney2 . policies . policy import PolicyVerdict
VERDICT2DESC = {
VERDICT2DESC = {
@ -40,6 +41,87 @@ VERDICT2DESC = {
}
}
class ExcuseDependency ( object ) :
""" Object to represent a specific dependecy of an excuse on a package
( source or binary ) or on other excuses """
def __init__ ( self , spec , depstates ) :
"""
: param : spec : DependencySpec
: param : depstates : list of DependencyState , each of which can satisfy
the dependency
"""
self . spec = spec
self . depstates = depstates
@property
def deptype ( self ) :
return self . spec . deptype
@property
def valid ( self ) :
if { d for d in self . depstates if d . valid } :
return True
else :
return False
@property
def deps ( self ) :
return { d . dep for d in self . depstates }
@property
def possible ( self ) :
if { d for d in self . depstates if d . possible } :
return True
else :
return False
@property
def first_dep ( self ) :
""" return the first valid dependency, if there is one, otherwise the
first possible one
return None if there are only impossible dependencies
"""
first = None
for d in self . depstates :
if d . valid :
return d . dep
elif d . possible and not first :
first = d . dep
return first
@property
def first_impossible_dep ( self ) :
""" return the first impossible dependency, if there is one """
first = None
for d in self . depstates :
if not d . possible :
return d . desc
return first
@property
def verdict ( self ) :
return min ( { d . verdict for d in self . depstates } )
def invalidate ( self , excuse , verdict ) :
""" invalidate the dependencies on a specific excuse
: param excuse : the excuse which is no longer valid
: param verdict : the PolicyVerdict causing the invalidation
"""
invalidated_alternative = False
valid_alternative_left = False
for ds in self . depstates :
if ds . dep == excuse :
ds . invalidate ( verdict )
invalidated_alternative = True
elif ds . valid :
valid_alternative_left = True
return valid_alternative_left
class Excuse ( object ) :
class Excuse ( object ) :
""" Excuse class
""" Excuse class
@ -75,8 +157,7 @@ class Excuse(object):
self . forced = False
self . forced = False
self . _policy_verdict = PolicyVerdict . REJECTED_PERMANENTLY
self . _policy_verdict = PolicyVerdict . REJECTED_PERMANENTLY
self . all_invalid_deps = { }
self . all_deps = [ ]
self . all_deps = { }
self . break_deps = [ ]
self . break_deps = [ ]
self . unsatisfiable_on_archs = [ ]
self . unsatisfiable_on_archs = [ ]
self . unsat_deps = defaultdict ( set )
self . unsat_deps = defaultdict ( set )
@ -91,15 +172,14 @@ class Excuse(object):
self . verdict_info = defaultdict ( list )
self . verdict_info = defaultdict ( list )
self . infoline = [ ]
self . infoline = [ ]
self . detailed_info = [ ]
self . detailed_info = [ ]
self . dep_info_rendered = False
# packages (source and binary) that will migrate to testing if the
# packages (source and binary) that will migrate to testing if the
# item from this excuse migrates
# item from this excuse migrates
self . packages = defaultdict ( set )
self . packages = defaultdict ( set )
# for each deptype, there is a set that contains
# list of ExcuseDependency, with dependencies on packages
# frozensets of PackageIds (of sources or binaries) that can satisfy
self . depends_packages = [ ]
# the dep
self . depends_packages = defaultdict ( set )
# contains all PackageIds in any over the sets above
# contains all PackageIds in any over the sets above
self . depends_packages_flattened = set ( )
self . depends_packages_flattened = set ( )
@ -144,13 +224,27 @@ class Excuse(object):
""" Set the section of the package """
""" Set the section of the package """
self . section = section
self . section = section
def add_dependency ( self , deptype , name , arch ) :
def add_dependency ( self , dep , spec ) :
""" Add a dependency of type deptype """
""" Add a dependency of type deptype
if name not in self . all_deps :
self . all_deps [ name ] = { }
: param dep : set with names of excuses , each of which satisfies the dep
if deptype not in self . all_deps [ name ] :
: param spec : DependencySpec
self . all_deps [ name ] [ deptype ] = [ ]
self . all_deps [ name ] [ deptype ] . append ( arch )
"""
assert dep != frozenset ( ) , " %s : Adding empty list of dependencies " % self . name
deps = [ ]
for d in dep :
if isinstance ( d , DependencyState ) :
deps . append ( d )
else :
deps . append ( DependencyState ( d ) )
ed = ExcuseDependency ( spec , deps )
self . all_deps . append ( ed )
if not ed . valid :
self . do_invalidate ( ed )
return ed . valid
def get_deps ( self ) :
def get_deps ( self ) :
# the autohinter uses the excuses data to query dependencies between
# the autohinter uses the excuses data to query dependencies between
@ -158,9 +252,12 @@ class Excuse(object):
# the data that was in the old deps set
# the data that was in the old deps set
""" Get the dependencies of type DEPENDS """
""" Get the dependencies of type DEPENDS """
deps = set ( )
deps = set ( )
for dep in self . all_deps :
for dep in [ d for d in self . all_deps if d . deptype == DependencyType . DEPENDS ] :
if DependencyType . DEPENDS in self . all_deps [ dep ] :
# add the first valid dependency
deps . add ( dep )
for d in dep . depstates :
if d . valid :
deps . add ( d . dep )
break
return deps
return deps
def add_break_dep ( self , name , arch ) :
def add_break_dep ( self , name , arch ) :
@ -177,9 +274,24 @@ class Excuse(object):
""" Add an unsatisfiable dependency """
""" Add an unsatisfiable dependency """
self . unsat_deps [ arch ] . add ( signature )
self . unsat_deps [ arch ] . add ( signature )
def do_invalidate ( self , dep ) :
"""
param : dep : ExcuseDependency
"""
self . addreason ( dep . deptype . get_reason ( ) )
if self . policy_verdict < dep . verdict :
self . policy_verdict = dep . verdict
def invalidate_dependency ( self , name , verdict ) :
def invalidate_dependency ( self , name , verdict ) :
""" Invalidate dependency """
""" Invalidate dependency """
self . all_invalid_deps [ name ] = verdict
invalidate = False
for dep in self . all_deps :
if not dep . invalidate ( name , verdict ) :
invalidate = True
self . do_invalidate ( dep )
return not invalidate
def setdaysold ( self , daysold , mindays ) :
def setdaysold ( self , daysold , mindays ) :
""" Set the number of days from the upload and the minimum number of days for the update """
""" Set the number of days from the upload and the minimum number of days for the update """
@ -224,9 +336,21 @@ class Excuse(object):
def add_package ( self , pkg_id ) :
def add_package ( self , pkg_id ) :
self . packages [ pkg_id . architecture ] . add ( pkg_id )
self . packages [ pkg_id . architecture ] . add ( pkg_id )
def add_package_depends ( self , deptype , depends ) :
def add_package_depends ( self , spec , depends ) :
""" depends is a set of PackageIds (source or binary) that can satisfy the dependency """
""" Add dependency on a package (source or binary)
self . depends_packages [ deptype ] . add ( frozenset ( depends ) )
: param spec : DependencySpec
: param depends : set of PackageIds ( source or binary ) , each of which can satisfy the dependency
"""
assert depends != frozenset ( ) , " %s : Adding empty list of package dependencies " % self . name
# we use DependencyState for consistency with excuse dependencies, but
# package dependencies are never invalidated, they are used to add
# excuse dependencies (in invalidate_excuses()), and these are
# (potentially) invalidated
ed = ExcuseDependency ( spec , [ DependencyState ( d ) for d in depends ] )
self . depends_packages . append ( ed )
self . depends_packages_flattened | = depends
self . depends_packages_flattened | = depends
def _format_verdict_summary ( self ) :
def _format_verdict_summary ( self ) :
@ -235,25 +359,32 @@ class Excuse(object):
return VERDICT2DESC [ verdict ]
return VERDICT2DESC [ verdict ]
return " UNKNOWN: Missing description for {0} - Please file a bug against Britney " . format ( verdict . name )
return " UNKNOWN: Missing description for {0} - Please file a bug against Britney " . format ( verdict . name )
def _render_dep_issues ( self , dep_issues , invalid_deps ) :
def _render_dep_issues ( self ) :
lastdep = " "
if self . dep_info_rendered :
res = [ ]
return
for x in sorted ( dep_issues , key = lambda x : x . split ( ' / ' ) [ 0 ] ) :
dep = x . split ( ' / ' ) [ 0 ]
dep_issues = defaultdict ( set )
if dep != lastdep :
for d in self . all_deps :
seen = { }
dep = d . first_dep
lastdep = dep
info = " "
for deptype in sorted ( dep_issues [ x ] , key = lambda y : str ( y ) ) :
if d . valid :
field = deptype
info = " %s : %s <a href= \" # %s \" > %s </a> " % ( d . deptype , self . name , dep , dep )
if deptype in seen :
elif not d . possible :
continue
desc = d . first_impossible_dep
seen [ deptype ] = True
info = " Impossible %s : %s -> %s " % ( d . deptype , self . name , desc )
if x in invalid_deps :
res . append ( " %s : %s <a href= \" # %s \" > %s </a> (not considered) " % ( field , self . name , dep , dep ) )
else :
else :
res . append ( " %s : %s <a href= \" # %s \" > %s </a> " % ( field , self . name , dep , dep ) )
info = " %s : %s <a href= \" # %s \" > %s </a> (not considered) " % ( d . deptype , self . name , dep , dep )
dep_issues [ d . verdict ] . add ( " Invalidated by %s " % d . deptype . get_description ( ) )
dep_issues [ d . verdict ] . add ( info )
return res
seen = set ( )
for v in sorted ( dep_issues . keys ( ) , reverse = True ) :
for i in sorted ( dep_issues [ v ] ) :
if i not in seen :
self . add_verdict_info ( v , i )
seen . add ( i )
self . dep_info_rendered = True
def html ( self ) :
def html ( self ) :
""" Render the excuse in HTML """
""" Render the excuse in HTML """
@ -276,6 +407,7 @@ class Excuse(object):
def _text ( self ) :
def _text ( self ) :
""" Render the excuse in text """
""" Render the excuse in text """
self . _render_dep_issues ( )
res = [ ]
res = [ ]
res . append (
res . append (
" Migration status for %s ( %s to %s ): %s " %
" Migration status for %s ( %s to %s ): %s " %
@ -285,10 +417,6 @@ class Excuse(object):
for v in sorted ( self . verdict_info . keys ( ) , reverse = True ) :
for v in sorted ( self . verdict_info . keys ( ) , reverse = True ) :
for x in self . verdict_info [ v ] :
for x in self . verdict_info [ v ] :
res . append ( " " + x + " " )
res . append ( " " + x + " " )
di = [ x for x in self . all_invalid_deps . keys ( ) if self . all_invalid_deps [ x ] == v ]
ad = { x : self . all_deps [ x ] for x in di }
for x in self . _render_dep_issues ( ad , di ) :
res . append ( " " + x + " " )
if self . infoline :
if self . infoline :
res . append ( " Additional info: " )
res . append ( " Additional info: " )
for x in self . infoline :
for x in self . infoline :
@ -326,16 +454,21 @@ class Excuse(object):
' on-architectures ' : sorted ( self . missing_builds ) ,
' on-architectures ' : sorted ( self . missing_builds ) ,
' on-unimportant-architectures ' : sorted ( self . missing_builds_ood_arch ) ,
' on-unimportant-architectures ' : sorted ( self . missing_builds_ood_arch ) ,
}
}
if self . all_invalid_deps :
if { d for d in self . all_deps if not d . valid and d . possible } :
excusedata [ ' invalidated-by-other-package ' ] = True
excusedata [ ' invalidated-by-other-package ' ] = True
if self . all_deps or self . all_invalid_deps . keys ( ) \
if self . all_deps \
or self . break_deps or self . unsat_deps :
or self . break_deps or self . unsat_deps :
excusedata [ ' dependencies ' ] = dep_data = { }
excusedata [ ' dependencies ' ] = dep_data = { }
migrate_after = sorted ( self . all_deps . keys ( ) - self . all_invalid_deps . keys ( ) )
migrate_after = sorted ( set ( d . first_dep for d in self . all_deps if d . valid ) )
break_deps = [ x for x , _ in self . break_deps if x not in self . all_deps ]
blocked_by = sorted ( set ( d . first_dep for d in self . all_deps
if not d . valid and d . possible ) )
break_deps = [ x for x , _ in self . break_deps if
x not in migrate - after and
x not in blocked - by ]
if self . all_invalid_deps . keys ( ) :
if blocked_by :
dep_data [ ' blocked-by ' ] = sorted ( self . all_invalid_deps . keys ( ) )
dep_data [ ' blocked-by ' ] = blocked_by
if migrate_after :
if migrate_after :
dep_data [ ' migrate-after ' ] = migrate_after
dep_data [ ' migrate-after ' ] = migrate_after
if break_deps :
if break_deps :