2011-12-13 18:52:01 +01:00
# -*- coding: utf-8 -*-
# Copyright (C) 2012 Niels Thykier <niels@thykier.net>
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
2015-04-05 12:21:49 +02:00
from collections import defaultdict
2011-12-13 18:52:01 +01:00
from functools import partial
2018-03-31 07:04:35 +00:00
import logging
2015-04-05 12:21:49 +02:00
from itertools import chain , filterfalse
2011-12-13 18:52:01 +01:00
2019-11-10 23:08:47 +00:00
from britney2 . utils import iter_except , add_transitive_dependencies_flatten
2011-12-13 18:52:01 +01:00
2015-04-05 14:30:36 +02:00
2011-12-13 18:52:01 +01:00
class InstallabilityTester ( object ) :
2018-12-15 19:54:53 +00:00
def __init__ ( self , universe , suite_contents ) :
2011-12-13 18:52:01 +01:00
""" Create a new installability tester
2018-10-30 19:01:10 +00:00
universe is a BinaryPackageUniverse
2011-12-13 18:52:01 +01:00
2018-12-15 19:54:53 +00:00
suite_contents is a ( mutable ) set of package ids that determines
which of the packages in universe are currently in the suite .
2011-12-13 18:52:01 +01:00
2015-09-09 17:38:33 +02:00
Package id : ( pkg_name , pkg_version , pkg_arch )
2011-12-13 18:52:01 +01:00
- NB : arch : all packages are " re-mapped " to given architecture .
( simplifies caches and dependency checking )
"""
self . _universe = universe
2018-12-15 19:54:53 +00:00
# FIXME: Move this field to TargetSuite
self . _suite_contents = suite_contents
2015-04-05 14:30:36 +02:00
self . _stats = InstallabilityStats ( )
2018-03-31 07:04:35 +00:00
logger_name = " . " . join ( ( self . __class__ . __module__ , self . __class__ . __name__ ) )
self . logger = logging . getLogger ( logger_name )
2011-12-13 18:52:01 +01:00
# Cache of packages known to be broken - we deliberately do not
# include "broken" in it. See _optimize for more info.
self . _cache_broken = set ( )
# Cache of packages known to be installable
self . _cache_inst = set ( )
# Per "arch" cache of the "minimal" (possibly incomplete)
# pseudo-essential set. This includes all the packages that
# are essential and packages that will always follow.
#
# It may not be a complete essential set, since alternatives
2019-11-10 23:08:47 +00:00
# are not always resolved. Noticeably cases like "awk" may be
2011-12-13 18:52:01 +01:00
# left out (since it could be either gawk, mawk or
# original-awk) unless something in this sets depends strictly
# on one of them
self . _cache_ess = { }
2019-11-10 23:08:47 +00:00
essential_w_transitive_deps = set ( universe . essential_packages )
add_transitive_dependencies_flatten ( universe , essential_w_transitive_deps )
self . _cache_essential_transitive_dependencies = essential_w_transitive_deps
2018-12-15 22:32:04 +00:00
def compute_installability ( self ) :
""" Computes the installability of all the packages in the suite
2011-12-13 18:52:01 +01:00
This method computes the installability of all packages in
2018-12-15 22:32:04 +00:00
the suite and caches the result . This has the advantage of
2011-12-13 18:52:01 +01:00
making " is_installable " queries very fast for all packages
2018-12-15 22:32:04 +00:00
in the suite .
2011-12-13 18:52:01 +01:00
"""
2018-11-01 20:00:20 +00:00
universe = self . _universe
2011-12-13 18:52:01 +01:00
check_inst = self . _check_inst
cbroken = self . _cache_broken
cache_inst = self . _cache_inst
2018-12-15 22:32:04 +00:00
suite_contents = self . _suite_contents
tcopy = [ x for x in suite_contents ]
2015-04-25 17:22:43 +02:00
for t in filterfalse ( cache_inst . __contains__ , tcopy ) :
2011-12-13 18:52:01 +01:00
if t in cbroken :
continue
2014-07-22 20:39:48 +02:00
res = check_inst ( t )
2018-11-01 20:00:20 +00:00
if t in universe . equivalent_packages :
2018-12-15 22:32:04 +00:00
eqv = ( x for x in universe . packages_equivalent_to ( t ) if x in suite_contents )
2014-07-22 20:39:48 +02:00
if res :
cache_inst . update ( eqv )
else :
eqv_set = frozenset ( eqv )
2018-12-15 22:32:04 +00:00
suite_contents - = eqv_set
2014-07-22 20:39:48 +02:00
cbroken | = eqv_set
2015-04-05 14:30:36 +02:00
@property
def stats ( self ) :
return self . _stats
2011-12-13 18:52:01 +01:00
2018-12-15 22:32:04 +00:00
def any_of_these_are_in_the_suite ( self , pkgs ) :
""" Test if at least one package of a given set is in the suite
2015-09-09 20:30:09 +02:00
: param pkgs : A set of package ids ( as defined in the constructor )
2018-12-15 22:32:04 +00:00
: return : True if any of the packages in pkgs are currently in the suite
2015-09-09 20:30:09 +02:00
"""
2018-12-15 19:54:53 +00:00
return not self . _suite_contents . isdisjoint ( pkgs )
2015-09-09 20:30:09 +02:00
2018-12-15 22:32:04 +00:00
def is_pkg_in_the_suite ( self , pkg_id ) :
""" Test if the package of is in the suite
2018-08-13 21:05:04 +02:00
2018-10-31 07:35:51 +00:00
: param pkg_id : A package id ( as defined in the constructor )
2018-12-15 22:32:04 +00:00
: return : True if the pkg is currently in the suite
2018-08-13 21:05:04 +02:00
"""
2018-12-15 19:54:53 +00:00
return pkg_id in self . _suite_contents
2018-08-13 21:05:04 +02:00
2019-02-10 09:11:24 +00:00
def which_of_these_are_in_the_suite ( self , pkgs ) :
""" Iterate over all packages that are in the suite
: param pkgs : An iterable of package ids
: return : An iterable of package ids that are in the suite
"""
yield from ( x for x in pkgs if x in self . _suite_contents )
2018-12-15 19:54:53 +00:00
def add_binary ( self , pkg_id ) :
""" Add a binary package to the suite
2011-12-13 18:52:01 +01:00
If the package is not known , this method will throw an
2015-04-05 14:30:36 +02:00
KeyError .
2016-01-17 18:35:09 +00:00
: param pkg_id The id of the package
2011-12-13 18:52:01 +01:00
"""
2016-11-13 08:49:46 +00:00
if pkg_id not in self . _universe : # pragma: no cover
2015-09-09 20:44:13 +02:00
raise KeyError ( str ( pkg_id ) )
2011-12-13 18:52:01 +01:00
2018-11-01 07:12:36 +00:00
if pkg_id in self . _universe . broken_packages :
2018-12-15 19:54:53 +00:00
self . _suite_contents . add ( pkg_id )
elif pkg_id not in self . _suite_contents :
self . _suite_contents . add ( pkg_id )
2015-04-05 14:30:36 +02:00
if self . _cache_inst :
self . _stats . cache_drops + = 1
2011-12-13 18:52:01 +01:00
self . _cache_inst = set ( )
if self . _cache_broken :
# Re-add broken packages as some of them may now be installable
2018-12-15 19:54:53 +00:00
self . _suite_contents | = self . _cache_broken
2011-12-13 18:52:01 +01:00
self . _cache_broken = set ( )
2019-11-10 23:08:47 +00:00
if pkg_id in self . _cache_essential_transitive_dependencies and pkg_id . architecture in self . _cache_ess :
# Adds new possibly pseudo-essential => "pseudo-essential" set needs to be
2011-12-13 18:52:01 +01:00
# recomputed
2016-05-16 08:19:26 +00:00
del self . _cache_ess [ pkg_id . architecture ]
2011-12-13 18:52:01 +01:00
return True
2018-12-15 19:54:53 +00:00
def remove_binary ( self , pkg_id ) :
""" Remove a binary from the suite
2011-12-13 18:52:01 +01:00
2016-01-17 18:35:09 +00:00
: param pkg_id The id of the package
2011-12-13 18:52:01 +01:00
If the package is not known , this method will throw an
2016-01-17 18:35:09 +00:00
KeyError .
2011-12-13 18:52:01 +01:00
"""
2016-11-13 08:49:46 +00:00
if pkg_id not in self . _universe : # pragma: no cover
2015-09-09 20:44:13 +02:00
raise KeyError ( str ( pkg_id ) )
2011-12-13 18:52:01 +01:00
2015-09-09 20:44:13 +02:00
self . _cache_broken . discard ( pkg_id )
2011-12-13 18:52:01 +01:00
2018-12-15 19:54:53 +00:00
if pkg_id in self . _suite_contents :
self . _suite_contents . remove ( pkg_id )
2020-10-30 15:01:24 +00:00
if pkg_id . architecture in self . _cache_ess :
( start , ess_never , ess_choices ) = self . _cache_ess [ pkg_id . architecture ]
if pkg_id in start or any ( [ pkg_id in choices for choices in ess_choices ] ) :
# Removes a package from the "pseudo-essential set"
del self . _cache_ess [ pkg_id . architecture ]
2011-12-13 18:52:01 +01:00
2018-10-30 19:01:10 +00:00
if not self . _universe . reverse_dependencies_of ( pkg_id ) :
2011-12-13 18:52:01 +01:00
# no reverse relations - safe
return True
2018-11-01 07:12:36 +00:00
if pkg_id not in self . _universe . broken_packages and pkg_id in self . _cache_inst :
2011-12-13 18:52:01 +01:00
# It is in our cache (and not guaranteed to be broken) - throw out the cache
self . _cache_inst = set ( )
2015-04-05 14:30:36 +02:00
self . _stats . cache_drops + = 1
2011-12-13 18:52:01 +01:00
return True
2015-09-09 20:44:13 +02:00
def is_installable ( self , pkg_id ) :
2011-12-13 18:52:01 +01:00
""" Test if a package is installable in this package set
2018-12-15 22:32:04 +00:00
The package is assumed to be in the suite and only packages in
the suite can be used to satisfy relations .
2011-12-13 18:52:01 +01:00
2016-01-17 18:35:09 +00:00
: param pkg_id The id of the package
2011-12-13 18:52:01 +01:00
Returns True iff the package is installable .
Returns False otherwise .
"""
2015-04-05 14:30:36 +02:00
self . _stats . is_installable_calls + = 1
2011-12-13 18:52:01 +01:00
2016-11-13 08:49:46 +00:00
if pkg_id not in self . _universe : # pragma: no cover
2015-09-09 20:44:13 +02:00
raise KeyError ( str ( pkg_id ) )
2011-12-13 18:52:01 +01:00
2018-12-15 19:54:53 +00:00
if pkg_id not in self . _suite_contents or pkg_id in self . _universe . broken_packages :
2015-04-05 14:30:36 +02:00
self . _stats . cache_hits + = 1
2011-12-13 18:52:01 +01:00
return False
2015-09-09 20:44:13 +02:00
if pkg_id in self . _cache_inst :
2015-04-05 14:30:36 +02:00
self . _stats . cache_hits + = 1
2011-12-13 18:52:01 +01:00
return True
2015-04-05 14:30:36 +02:00
self . _stats . cache_misses + = 1
2015-09-09 20:44:13 +02:00
return self . _check_inst ( pkg_id )
2011-12-13 18:52:01 +01:00
def _check_inst ( self , t , musts = None , never = None , choices = None ) :
# See the explanation of musts, never and choices below.
2015-04-05 14:30:36 +02:00
stats = self . _stats
2011-12-13 18:52:01 +01:00
universe = self . _universe
2018-12-15 22:32:04 +00:00
suite_contents = self . _suite_contents
2011-12-13 18:52:01 +01:00
cbroken = self . _cache_broken
# Our installability verdict - start with "yes" and change if
# prove otherwise.
verdict = True
# set of packages that must be installed with this package
if musts is None :
musts = set ( )
musts . add ( t )
# set of packages we can *never* choose (e.g. due to conflicts)
if never is None :
never = set ( )
# set of relations were we have a choice, but where we have not
# committed ourselves yet. Hopefully some choices may be taken
# for us (if one of the alternatives appear in "musts")
if choices is None :
choices = set ( )
# The subset of musts we haven't checked yet.
2018-08-13 18:58:54 +00:00
check = [ t ]
2011-12-13 18:52:01 +01:00
if len ( musts ) == 1 :
2018-12-15 22:32:04 +00:00
# Include the essential packages in the suite as a starting point.
2016-05-16 08:19:26 +00:00
if t . architecture not in self . _cache_ess :
2011-12-13 18:52:01 +01:00
# The minimal essential set cache is not present -
# compute it now.
2017-02-11 09:36:11 +00:00
( start , ess_never , ess_choices ) = self . _get_min_pseudo_ess_set ( t . architecture )
2011-12-13 18:52:01 +01:00
else :
2017-02-11 09:36:11 +00:00
( start , ess_never , ess_choices ) = self . _cache_ess [ t . architecture ]
2011-12-13 18:52:01 +01:00
if t in ess_never :
# t conflicts with something in the essential set or the essential
# set conflicts with t - either way, t is f***ed
cbroken . add ( t )
2018-12-15 22:32:04 +00:00
suite_contents . remove ( t )
2015-04-05 14:30:36 +02:00
stats . conflicts_essential + = 1
2011-12-13 18:52:01 +01:00
return False
musts . update ( start )
never . update ( ess_never )
2017-02-11 09:36:11 +00:00
choices . update ( ess_choices )
2011-12-13 18:52:01 +01:00
# curry check_loop
2018-12-15 22:32:04 +00:00
check_loop = partial ( self . _check_loop , universe , suite_contents ,
2018-11-01 20:00:20 +00:00
stats , musts , never , cbroken )
2011-12-13 18:52:01 +01:00
# Useful things to remember:
#
# * musts and never are disjointed at all times
# - if not, t cannot be installable. Either t, or one of
# its dependencies conflict with t or one of its (other)
# dependencies.
#
# * choices should generally be avoided as much as possible.
# - picking a bad choice requires backtracking
# - sometimes musts/never will eventually "solve" the choice.
#
# * check never includes choices (these are always in choices)
#
2015-04-05 14:30:36 +02:00
# * A package is installable if never and musts are disjointed
2011-12-13 18:52:01 +01:00
# and both check and choices are empty.
2016-01-17 15:14:14 +00:00
# - exception: resolve_choices may determine the installability
2011-12-13 18:52:01 +01:00
# of t via recursion (calls _check_inst). In this case
# check and choices are not (always) empty.
2016-03-26 14:46:44 +00:00
def _prune_choices ( rebuild , len = len ) :
2011-12-13 18:52:01 +01:00
""" Picks a choice from choices and updates rebuild.
Prunes the choices and updates " rebuild " to reflect the
pruned choices .
Returns True if t is installable ( determined via recursion ) .
Returns False if a choice was picked and added to check .
Returns None if t is uninstallable ( no choice can be picked ) .
NB : If this returns False , choices should be replaced by
rebuild .
"""
2015-04-05 23:31:13 +02:00
# We already satisfied/chosen at least one of the literals
2011-12-13 18:52:01 +01:00
# in the choice, so the choice is gone
2015-04-25 17:22:43 +02:00
for choice in filter ( musts . isdisjoint , choices ) :
2011-12-13 18:52:01 +01:00
# cbroken is needed here because (in theory) it could
# have changed since the choice was discovered and it
2018-12-15 22:32:04 +00:00
# is smaller than suite_contents (so presumably faster)
2011-12-13 18:52:01 +01:00
remain = choice - never - cbroken
if len ( remain ) == 1 :
# the choice was reduced to one package we haven't checked - check that
2018-08-13 18:58:54 +00:00
check . extend ( remain )
2011-12-13 18:52:01 +01:00
musts . update ( remain )
2015-04-05 14:30:36 +02:00
stats . choice_presolved + = 1
2011-12-13 18:52:01 +01:00
continue
2014-02-27 18:12:54 +00:00
if not remain :
# all alternatives would violate the conflicts or are uninstallable
# => package is not installable
2015-04-05 14:30:36 +02:00
stats . choice_presolved + = 1
2016-01-17 14:33:43 +00:00
return False
2014-02-27 18:12:54 +00:00
2011-12-13 18:52:01 +01:00
# The choice is still deferred
rebuild . add ( frozenset ( remain ) )
2016-01-17 14:33:43 +00:00
return True
2014-07-24 19:54:33 +02:00
2016-01-17 14:33:43 +00:00
# END _prune_choices
2011-12-13 18:52:01 +01:00
while check :
2015-04-05 23:31:13 +02:00
if not check_loop ( choices , check ) :
2011-12-13 18:52:01 +01:00
verdict = False
break
if choices :
rebuild = set ( )
2016-01-17 14:33:43 +00:00
if not _prune_choices ( rebuild ) :
2011-12-13 18:52:01 +01:00
verdict = False
break
2016-01-17 14:33:43 +00:00
2016-01-17 15:14:14 +00:00
if not check and rebuild :
2016-01-17 14:33:43 +00:00
# We have to "guess" now, which is always fun, but not cheap. We
# stop guessing:
# - once we run out of choices to make (obviously), OR
# - if one of the choices exhaust all but one option
2016-01-17 15:14:14 +00:00
if self . resolve_choices ( check , musts , never , rebuild ) :
2016-01-17 14:33:43 +00:00
# The recursive call have already updated the
# cache so there is not point in doing it again.
return True
2011-12-13 18:52:01 +01:00
choices = rebuild
if verdict :
# if t is installable, then so are all packages in musts
self . _cache_inst . update ( musts )
2015-04-05 14:30:36 +02:00
stats . solved_installable + = 1
else :
stats . solved_uninstallable + = 1
2011-12-13 18:52:01 +01:00
return verdict
2016-01-17 15:14:14 +00:00
def resolve_choices ( self , check , musts , never , choices ) :
2016-01-17 14:33:43 +00:00
universe = self . _universe
2018-12-15 22:32:04 +00:00
suite_contents = self . _suite_contents
2016-01-17 14:33:43 +00:00
stats = self . _stats
cbroken = self . _cache_broken
2016-01-17 15:14:14 +00:00
while choices :
choice_options = choices . pop ( )
choice = iter ( choice_options )
last = next ( choice ) # pick one to go last
solved = False
for p in choice :
musts_copy = musts . copy ( )
never_tmp = set ( )
choices_tmp = set ( )
2018-08-13 18:58:54 +00:00
check_tmp = [ p ]
2018-08-15 17:13:36 +00:00
# _check_loop assumes that "musts" is up to date
musts_copy . add ( p )
2018-12-15 22:32:04 +00:00
if not self . _check_loop ( universe , suite_contents ,
2016-01-17 15:14:14 +00:00
stats , musts_copy , never_tmp ,
cbroken , choices_tmp ,
check_tmp ) :
# p cannot be chosen/is broken (unlikely, but ...)
continue
2016-01-17 14:33:43 +00:00
2016-01-17 15:14:14 +00:00
# Test if we can pick p without any consequences.
# - when we can, we avoid a backtrack point.
if never_tmp < = never and choices_tmp < = choices :
# we can pick p without picking up new conflicts
# or unresolved choices. Therefore we commit to
# using p.
musts . update ( musts_copy )
stats . choice_resolved_without_restore_point + = 1
solved = True
break
2016-01-17 14:33:43 +00:00
2016-01-17 15:14:14 +00:00
if not musts . isdisjoint ( never_tmp ) :
# If we pick p, we will definitely end up making
# t uninstallable, so p is a no-go.
continue
2016-01-17 14:33:43 +00:00
2016-01-17 15:14:14 +00:00
stats . backtrace_restore_point_created + = 1
# We are not sure that p is safe, setup a backtrack
# point and recurse.
never_tmp | = never
choices_tmp | = choices
if self . _check_inst ( p , musts_copy , never_tmp ,
choices_tmp ) :
# Success, p was a valid choice and made it all
# installable
return True
# If we get here, we failed to find something that
# would satisfy choice (without breaking the
# installability of t). This means p cannot be used
# to satisfy the dependencies, so pretend to conflict
# with it - hopefully it will reduce future choices.
never . add ( p )
stats . backtrace_restore_point_used + = 1
if not solved :
# Optimization for the last case; avoid the recursive call
# and just assume the last will lead to a solution. If it
# doesn't there is no solution and if it does, we don't
# have to back-track anyway.
2018-08-13 18:58:54 +00:00
check . append ( last )
2016-01-17 15:14:14 +00:00
musts . add ( last )
stats . backtrace_last_option + = 1
return False
2016-01-17 14:33:43 +00:00
2018-12-15 22:32:04 +00:00
def _check_loop ( self , universe , suite_contents , stats , musts , never ,
2015-04-05 23:31:13 +02:00
cbroken , choices , check , len = len ,
2014-07-19 20:05:23 +02:00
frozenset = frozenset ) :
2011-12-13 18:52:01 +01:00
""" Finds all guaranteed dependencies via " check " .
If it returns False , t is not installable . If it returns True
then " check " is exhausted . If " choices " are empty and this
returns True , then t is installable .
"""
# Local variables for faster access...
2015-04-25 17:22:43 +02:00
not_satisfied = partial ( filter , musts . isdisjoint )
2011-12-13 18:52:01 +01:00
# While we have guaranteed dependencies (in check), examine all
# of them.
2018-08-13 18:58:54 +00:00
for cur in iter_except ( check . pop , IndexError ) :
2018-10-30 19:01:10 +00:00
relations = universe . relations_of ( cur )
2011-12-13 18:52:01 +01:00
2018-10-30 19:01:10 +00:00
if relations . negative_dependencies :
2011-12-13 18:52:01 +01:00
# Conflicts?
if cur in never :
# cur adds a (reverse) conflict, so check if cur
# is in never.
#
# - there is a window where two conflicting
# packages can be in check. Example "A" depends
# on "B" and "C". If "B" conflicts with "C",
# then both "B" and "C" could end in "check".
return False
# We must install cur for the package to be installable,
# so "obviously" we can never choose any of its conflicts
2018-12-15 22:32:04 +00:00
never . update ( relations . negative_dependencies & suite_contents )
2011-12-13 18:52:01 +01:00
2015-04-05 23:31:13 +02:00
# depgroup can be satisfied by picking something that is
2011-12-13 18:52:01 +01:00
# already in musts - lets pick that (again). :)
2018-10-30 19:01:10 +00:00
for depgroup in not_satisfied ( relations . dependencies ) :
2011-12-13 18:52:01 +01:00
# Of all the packages listed in the relation remove those that
# are either:
2018-12-15 22:32:04 +00:00
# - not in the suite
2011-12-13 18:52:01 +01:00
# - known to be broken (by cache)
# - in never
2018-12-15 22:32:04 +00:00
candidates = ( depgroup & suite_contents ) - never
2011-12-13 18:52:01 +01:00
2016-01-17 12:27:15 +00:00
if not candidates :
2011-12-13 18:52:01 +01:00
# We got no candidates to satisfy it - this
# package cannot be installed with the current
2018-12-15 22:32:04 +00:00
# (version of the) suite
2011-12-13 18:52:01 +01:00
if cur not in cbroken and depgroup . isdisjoint ( never ) :
# cur's dependency cannot be satisfied even if never was empty.
# This means that cur itself is broken (as well).
cbroken . add ( cur )
2018-12-15 22:32:04 +00:00
suite_contents . remove ( cur )
2011-12-13 18:52:01 +01:00
return False
2014-07-19 20:05:23 +02:00
if len ( candidates ) == 1 :
2011-12-13 18:52:01 +01:00
# only one possible solution to this choice and we
# haven't seen it before
2018-08-13 18:58:54 +00:00
check . extend ( candidates )
2011-12-13 18:52:01 +01:00
musts . update ( candidates )
else :
2018-11-01 20:00:20 +00:00
possible_eqv = set ( x for x in candidates if x in universe . equivalent_packages )
2014-07-19 20:05:23 +02:00
if len ( possible_eqv ) > 1 :
2014-07-22 20:39:48 +02:00
# Exploit equivalency to reduce the number of
2014-07-19 20:05:23 +02:00
# candidates if possible. Basically, this
# code maps "similar" candidates into a single
# candidate that will give a identical result
# to any other candidate it eliminates.
#
# See InstallabilityTesterBuilder's
# _build_eqv_packages_table method for more
# information on how this works.
new_cand = set ( x for x in candidates if x not in possible_eqv )
2015-04-05 14:30:36 +02:00
stats . eqv_table_times_used + = 1
2014-07-19 20:05:23 +02:00
for chosen in iter_except ( possible_eqv . pop , KeyError ) :
new_cand . add ( chosen )
2018-11-01 20:00:20 +00:00
possible_eqv - = universe . packages_equivalent_to ( chosen )
2015-04-05 14:30:36 +02:00
stats . eqv_table_total_number_of_alternatives_eliminated + = len ( candidates ) - len ( new_cand )
2014-07-19 20:05:23 +02:00
if len ( new_cand ) == 1 :
2018-08-13 18:58:54 +00:00
check . extend ( new_cand )
2014-07-19 20:05:23 +02:00
musts . update ( new_cand )
2015-04-05 14:30:36 +02:00
stats . eqv_table_reduced_to_one + = 1
2014-07-19 20:05:23 +02:00
continue
2015-04-05 14:30:36 +02:00
elif len ( candidates ) == len ( new_cand ) :
stats . eqv_table_reduced_by_zero + = 1
2016-01-17 12:27:15 +00:00
2014-07-19 20:05:23 +02:00
candidates = frozenset ( new_cand )
2016-01-17 12:27:15 +00:00
else :
# Candidates have to be a frozenset to be added to choices
candidates = frozenset ( candidates )
2011-12-13 18:52:01 +01:00
# defer this choice till later
choices . add ( candidates )
return True
def _get_min_pseudo_ess_set ( self , arch ) :
if arch not in self . _cache_ess :
# The minimal essential set cache is not present -
# compute it now.
2018-12-15 22:32:04 +00:00
suite_contents = self . _suite_contents
2011-12-13 18:52:01 +01:00
cbroken = self . _cache_broken
universe = self . _universe
2015-04-05 14:30:36 +02:00
stats = self . _stats
2011-12-13 18:52:01 +01:00
2018-12-15 22:32:04 +00:00
ess_base = [ x for x in self . _universe . essential_packages if x . architecture == arch and x in suite_contents ]
2011-12-13 18:52:01 +01:00
start = set ( ess_base )
ess_never = set ( )
ess_choices = set ( )
2016-09-24 09:32:57 +00:00
not_satisfied = partial ( filter , start . isdisjoint )
2011-12-13 18:52:01 +01:00
while ess_base :
2018-12-15 22:32:04 +00:00
self . _check_loop ( universe , suite_contents , stats ,
2015-04-05 23:31:13 +02:00
start , ess_never , cbroken ,
ess_choices , ess_base )
2011-12-13 18:52:01 +01:00
if ess_choices :
# Try to break choices where possible
nchoice = set ( )
2016-09-24 09:32:57 +00:00
for choice in not_satisfied ( ess_choices ) :
2011-12-13 18:52:01 +01:00
b = False
for c in choice :
2018-10-30 19:01:10 +00:00
relations = universe . relations_of ( c )
if relations . negative_dependencies < = ess_never and \
not any ( not_satisfied ( relations . dependencies ) ) :
2018-08-13 18:58:54 +00:00
ess_base . append ( c )
2011-12-13 18:52:01 +01:00
b = True
break
if not b :
nchoice . add ( choice )
ess_choices = nchoice
else :
break
for x in start :
2018-10-30 19:01:10 +00:00
ess_never . update ( universe . negative_dependencies_of ( x ) )
2017-02-11 09:36:11 +00:00
self . _cache_ess [ arch ] = ( frozenset ( start ) , frozenset ( ess_never ) , frozenset ( ess_choices ) )
2011-12-13 18:52:01 +01:00
return self . _cache_ess [ arch ]
2015-04-05 12:21:49 +02:00
def compute_stats ( self ) :
universe = self . _universe
graph_stats = defaultdict ( ArchStats )
seen_eqv = defaultdict ( set )
for pkg in universe :
( pkg_name , pkg_version , pkg_arch ) = pkg
2018-10-30 19:01:10 +00:00
relations = universe . relations_of ( pkg )
2015-04-05 12:21:49 +02:00
arch_stats = graph_stats [ pkg_arch ]
arch_stats . nodes + = 1
2018-11-01 20:00:20 +00:00
if pkg in universe . equivalent_packages and pkg not in seen_eqv [ pkg_arch ] :
eqv = [ e for e in universe . packages_equivalent_to ( pkg ) if e . architecture == pkg_arch ]
2015-04-05 12:21:49 +02:00
arch_stats . eqv_nodes + = len ( eqv )
2018-10-30 19:01:10 +00:00
arch_stats . add_dep_edges ( relations . dependencies )
arch_stats . add_con_edges ( relations . negative_dependencies )
2015-04-05 12:21:49 +02:00
for stat in graph_stats . values ( ) :
stat . compute_all ( )
return graph_stats
2015-04-05 14:30:36 +02:00
class InstallabilityStats ( object ) :
def __init__ ( self ) :
self . cache_hits = 0
self . cache_misses = 0
self . cache_drops = 0
self . backtrace_restore_point_created = 0
self . backtrace_restore_point_used = 0
self . backtrace_last_option = 0
self . choice_presolved = 0
self . choice_resolved_without_restore_point = 0
self . is_installable_calls = 0
self . solved_installable = 0
self . solved_uninstallable = 0
self . conflicts_essential = 0
self . eqv_table_times_used = 0
self . eqv_table_reduced_to_one = 0
self . eqv_table_reduced_by_zero = 0
self . eqv_table_total_number_of_alternatives_eliminated = 0
def stats ( self ) :
formats = [
" Requests - is_installable: {is_installable_calls} " ,
" Cache - hits: {cache_hits} , misses: {cache_misses} , drops: {cache_drops} " ,
2018-08-12 18:46:13 +00:00
" Choices - pre-solved: {choice_presolved} , No RP: {choice_resolved_without_restore_point} " ,
2020-01-30 16:41:16 +00:00
" Backtrace - RP created: {backtrace_restore_point_created} , RP used: {backtrace_restore_point_used} , reached last option: {backtrace_last_option} " , # nopep8
" Solved - installable: {solved_installable} , uninstallable: {solved_uninstallable} , conflicts essential: {conflicts_essential} " , # nopep8
" Eqv - times used: {eqv_table_times_used} , perfect reductions: {eqv_table_reduced_to_one} , failed reductions: {eqv_table_reduced_by_zero} , total no. of alternatives pruned: {eqv_table_total_number_of_alternatives_eliminated} " , # nopep8
2015-04-05 14:30:36 +02:00
]
return [ x . format ( * * self . __dict__ ) for x in formats ]
2015-04-05 12:21:49 +02:00
class ArchStats ( object ) :
def __init__ ( self ) :
self . nodes = 0
self . eqv_nodes = 0
self . dep_edges = [ ]
self . con_edges = [ ]
self . stats = defaultdict ( lambda : defaultdict ( int ) )
def stat ( self , statname ) :
return self . stats [ statname ]
def stat_summary ( self ) :
text = [ ]
for statname in [ ' nodes ' , ' dependency-clauses ' , ' dependency-clause-alternatives ' , ' negative-dependency-clauses ' ] :
stat = self . stats [ statname ]
if statname != ' nodes ' :
format_str = " %s , max: %d , min: %d , median: %d , average: %f ( %d / %d ) "
values = [ statname , stat [ ' max ' ] , stat [ ' min ' ] , stat [ ' median ' ] , stat [ ' average ' ] , stat [ ' sum ' ] , stat [ ' size ' ] ]
if ' average-per-node ' in stat :
format_str + = " , average-per-node: %f "
values . append ( stat [ ' average-per-node ' ] )
else :
format_str = " nodes: %d , eqv-nodes: %d "
values = ( self . nodes , self . eqv_nodes )
text . append ( format_str % tuple ( values ) )
return text
def add_dep_edges ( self , edges ) :
self . dep_edges . append ( edges )
def add_con_edges ( self , edges ) :
self . con_edges . append ( edges )
def _list_stats ( self , stat_name , sorted_list , average_per_node = False ) :
if sorted_list :
stats = self . stats [ stat_name ]
stats [ ' max ' ] = sorted_list [ - 1 ]
stats [ ' min ' ] = sorted_list [ 0 ]
stats [ ' sum ' ] = sum ( sorted_list )
stats [ ' size ' ] = len ( sorted_list )
stats [ ' average ' ] = float ( stats [ ' sum ' ] ) / len ( sorted_list )
stats [ ' median ' ] = sorted_list [ len ( sorted_list ) / / 2 ]
if average_per_node :
stats [ ' average-per-node ' ] = float ( stats [ ' sum ' ] ) / self . nodes
def compute_all ( self ) :
dep_edges = self . dep_edges
con_edges = self . con_edges
sorted_no_dep_edges = sorted ( len ( x ) for x in dep_edges )
sorted_size_dep_edges = sorted ( len ( x ) for x in chain . from_iterable ( dep_edges ) )
sorted_no_con_edges = sorted ( len ( x ) for x in con_edges )
self . _list_stats ( ' dependency-clauses ' , sorted_no_dep_edges )
self . _list_stats ( ' dependency-clause-alternatives ' , sorted_size_dep_edges , average_per_node = True )
self . _list_stats ( ' negative-dependency-clauses ' , sorted_no_con_edges )