From e66896bac2979566c0af4a48d89d2eb9481d553b Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Sun, 13 Jan 2008 12:04:31 +0000 Subject: [PATCH] Replaced get_ranked_selections with record_details on Solver. The GUI now shows the reason why an implementation couldn't be used in a new column, not in a tooltip. Removed old Policy.walk_interfaces(). --- tests/testautopolicy.py | 40 +---- tests/testlaunch.py | 4 + tests/testsolver.py | 39 ++++- zeroinstall/0launch-gui/ZeroInstall-GUI.xml | 4 +- zeroinstall/0launch-gui/gui.py | 1 + zeroinstall/0launch-gui/impl_list.py | 19 +-- zeroinstall/0launch-gui/properties.py | 3 +- zeroinstall/injector/namespaces.py | 2 +- zeroinstall/injector/policy.py | 255 ++++++---------------------- zeroinstall/injector/solver.py | 168 ++++++++++-------- 10 files changed, 194 insertions(+), 341 deletions(-) diff --git a/tests/testautopolicy.py b/tests/testautopolicy.py index e41b7be..269bae4 100755 --- a/tests/testautopolicy.py +++ b/tests/testautopolicy.py @@ -251,44 +251,6 @@ class TestAutoPolicy(BaseTest): download_only = False) logger.setLevel(logging.WARN) - def testRanking(self): - self.cache_iface('http://bar', -""" - - Bar - Bar - Bar - - -""") - self.cache_iface(foo_iface_uri, -""" - - Foo - Foo - Foo - - - - -""" % foo_iface_uri) - policy = autopolicy.AutoPolicy(foo_iface_uri, - download_only = False) - policy.network_use = model.network_full - policy.freshness = 0 - impls = policy.get_ranked_implementations( - policy.get_interface(policy.root)) - assert len(impls) == 4 - - logger.setLevel(logging.ERROR) - policy.network_use = model.network_offline # Changes sort order tests - policy.recalculate() # Triggers feed-for warning - logger.setLevel(logging.WARN) - def testNoLocal(self): self.cache_iface(foo_iface_uri, """ @@ -352,7 +314,7 @@ class TestAutoPolicy(BaseTest): download_only = False) policy.network_use = model.network_offline policy.recalculate() - assert not policy.ready + assert not policy.ready, policy.implementation try: policy.download_and_execute([]) assert False diff --git a/tests/testlaunch.py b/tests/testlaunch.py index 63c301f..eb123ea 100755 --- a/tests/testlaunch.py +++ b/tests/testlaunch.py @@ -36,10 +36,14 @@ class TestLaunch(BaseTest): try: imp.load_source('launch', '../0launch') print "Finished" + except NameError: + raise except SystemExit: pass except TypeError: raise + except AttributeError: + raise except AssertionError: raise except Exception, ex: diff --git a/tests/testsolver.py b/tests/testsolver.py index a8a7b3a..d58565d 100755 --- a/tests/testsolver.py +++ b/tests/testsolver.py @@ -4,6 +4,7 @@ import sys, tempfile, os, shutil import unittest sys.path.insert(0, '..') +from zeroinstall.zerostore import Stores from zeroinstall.injector import solver, reader, arch, model from zeroinstall.injector.iface_cache import iface_cache @@ -13,7 +14,7 @@ logger = logging.getLogger() class TestSolver(BaseTest): def testSimple(self): - s = solver.DefaultSolver(model.network_full) + s = solver.DefaultSolver(model.network_full, iface_cache, Stores()) foo = iface_cache.get_interface('http://foo/Binary.xml') reader.update(foo, 'Binary.xml') @@ -23,19 +24,41 @@ class TestSolver(BaseTest): reader.update(compiler, 'Compiler.xml') binary_arch = arch.Architecture({None: 1}, {None: 1}) - ready, selections, feeds_used = s.solve('http://foo/Binary.xml', iface_cache, binary_arch) + ready = s.solve('http://foo/Binary.xml', binary_arch) assert ready - assert feeds_used == set([foo.uri]), feeds_used - assert selections.selections[foo.uri].id == 'sha1=123' + assert s.feeds_used == set([foo.uri]), s.feeds_used + assert s.selections[foo].id == 'sha1=123' # Now ask for source instead - ready, selections, feeds_used = s.solve('http://foo/Binary.xml', iface_cache, + ready = s.solve('http://foo/Binary.xml', arch.SourceArchitecture(binary_arch)) assert ready - assert feeds_used == set([foo.uri, foo_src.uri, compiler.uri]), feeds_used - assert selections.selections[foo.uri].id == 'sha1=234' # The source - assert selections.selections[compiler.uri].id == 'sha1=345' # A binary needed to compile it + assert s.feeds_used == set([foo.uri, foo_src.uri, compiler.uri]), s.feeds_used + assert s.selections[foo].id == 'sha1=234' # The source + assert s.selections[compiler].id == 'sha1=345' # A binary needed to compile it + + assert not s.details + + def testDetails(self): + s = solver.DefaultSolver(model.network_full, iface_cache, Stores()) + + foo = iface_cache.get_interface('http://foo/Binary.xml') + reader.update(foo, 'Binary.xml') + foo_src = iface_cache.get_interface('http://foo/Source.xml') + reader.update(foo_src, 'Source.xml') + compiler = iface_cache.get_interface('http://foo/Compiler.xml') + reader.update(compiler, 'Compiler.xml') + + binary_arch = arch.Architecture({None: 1}, {None: 1}) + s.record_details = True + ready = s.solve('http://foo/Binary.xml', arch.SourceArchitecture(binary_arch)) + assert ready + + assert len(s.details) == 2 + assert s.details[foo] == [(foo_src._main_feed.implementations['sha1=234'], None), (foo._main_feed.implementations['sha1=123'], 'Unsupported machine type')] + assert s.details[compiler] == [(compiler._main_feed.implementations['sha1=345'], None)] + suite = unittest.makeSuite(TestSolver) if __name__ == '__main__': diff --git a/zeroinstall/0launch-gui/ZeroInstall-GUI.xml b/zeroinstall/0launch-gui/ZeroInstall-GUI.xml index 7ada7d1..93a8023 100644 --- a/zeroinstall/0launch-gui/ZeroInstall-GUI.xml +++ b/zeroinstall/0launch-gui/ZeroInstall-GUI.xml @@ -7,9 +7,9 @@ The Zero Install GUI displays a window showing which versions of various components will be used when running a program, and allows you to alter various policy settings to affect the selection process. You can use this to mark particular versions of libraries as preferred, bugggy, etc. http://0install.net/injector.html - + - + diff --git a/zeroinstall/0launch-gui/gui.py b/zeroinstall/0launch-gui/gui.py index 291901a..ad24ebc 100644 --- a/zeroinstall/0launch-gui/gui.py +++ b/zeroinstall/0launch-gui/gui.py @@ -140,6 +140,7 @@ class GUIPolicy(Policy): def __init__(self, interface, download_only, refresh, src = False, restrictions = None): Policy.__init__(self, interface, GUIHandler(self), src = src) + self.solver.record_details = True global policy assert policy is None policy = self diff --git a/zeroinstall/0launch-gui/impl_list.py b/zeroinstall/0launch-gui/impl_list.py index 4132c55..ac43d2b 100644 --- a/zeroinstall/0launch-gui/impl_list.py +++ b/zeroinstall/0launch-gui/impl_list.py @@ -26,6 +26,7 @@ VERSION = 3 CACHED = 4 UNUSABLE = 5 RELEASED = 6 +NOTES = 7 class ImplTips(TreeTips): def __init__(self, interface): @@ -34,10 +35,6 @@ class ImplTips(TreeTips): def get_tooltip_text(self, impl): restrictions = policy.restrictions.get(self.interface, []) - unusable = policy.get_unusable_reason(impl, restrictions) - if unusable: - return unusable - if impl.id.startswith('/'): return _("Local: %s") % impl.id if impl.id.startswith('package:'): @@ -62,7 +59,7 @@ class ImplementationList: self.model = gtk.ListStore(object, str, str, str, gobject.TYPE_BOOLEAN, gobject.TYPE_BOOLEAN, - str) + str, str) self.tree_view = widgets.get_widget('versions_list') self.tree_view.set_model(self.model) @@ -77,7 +74,8 @@ class ImplementationList: gtk.TreeViewColumn('Released', text, text = RELEASED, strikethrough = UNUSABLE), stability, gtk.TreeViewColumn('C', toggle, active = CACHED), - gtk.TreeViewColumn('Arch', text, text = ARCH)): + gtk.TreeViewColumn('Arch', text, text = ARCH), + gtk.TreeViewColumn('Notes', text, text = NOTES)): self.tree_view.append_column(column) tips = ImplTips(interface) @@ -133,8 +131,7 @@ class ImplementationList: def set_items(self, items): self.model.clear() - restrictions = policy.restrictions.get(self.interface, None) - for item in items: + for item, unusable in items: new = self.model.append() self.model[new][ITEM] = item self.model[new][VERSION] = item.get_version() @@ -146,10 +143,8 @@ class ImplementationList: self.model[new][STABILITY] = item.upstream_stability or \ model.testing self.model[new][ARCH] = item.arch or 'any' - if restrictions is not None: - self.model[new][UNUSABLE] = policy.is_unusable(item, restrictions) - else: - self.model[new][UNUSABLE] = policy.is_unusable(item) + self.model[new][UNUSABLE] = bool(unusable) + self.model[new][NOTES] = unusable def clear(self): self.model.clear() diff --git a/zeroinstall/0launch-gui/properties.py b/zeroinstall/0launch-gui/properties.py index f913df6..23ded78 100644 --- a/zeroinstall/0launch-gui/properties.py +++ b/zeroinstall/0launch-gui/properties.py @@ -328,8 +328,7 @@ class Properties: self.compile_button.set_sensitive(have_source_for(self.interface)) def update_list(self): - impls = policy.get_ranked_implementations(self.interface) - self.use_list.set_items(impls) + self.use_list.set_items(policy.solver.details[self.interface]) def add_remote_feed(parent, interface): d = gtk.MessageDialog(parent, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL, diff --git a/zeroinstall/injector/namespaces.py b/zeroinstall/injector/namespaces.py index c5db299..0b440f0 100644 --- a/zeroinstall/injector/namespaces.py +++ b/zeroinstall/injector/namespaces.py @@ -17,4 +17,4 @@ XMLNS_TRUST = 'http://zero-install.sourceforge.net/2007/injector/trust' config_site = '0install.net' config_prog = 'injector' -injector_gui_uri = 'http://0install.net/2007/interfaces/ZeroInstall-GUI.xml' +injector_gui_uri = 'http://0install.net/2008/interfaces/ZeroInstall-GUI.xml' diff --git a/zeroinstall/injector/policy.py b/zeroinstall/injector/policy.py index efd7979..721166b 100644 --- a/zeroinstall/injector/policy.py +++ b/zeroinstall/injector/policy.py @@ -107,9 +107,17 @@ class Policy(object): @type stale_feeds: set """ __slots__ = ['root', 'implementation', 'watchers', - 'help_with_testing', 'network_use', 'freshness', 'ready', 'handler', '_warned_offline', - 'restrictions', 'src', 'root_restrictions', 'stale_feeds'] + 'restrictions', 'src', 'stale_feeds', 'solver'] + + help_with_testing = property(lambda self: self.solver.help_with_testing, + lambda self, value: setattr(self.solver, 'help_with_testing', value)) + + network_use = property(lambda self: self.solver.network_use, + lambda self, value: setattr(self.solver, 'network_use', value)) + + root_restrictions = property(lambda self: self.solver.root_restrictions, + lambda self, value: setattr(self.solver, 'root_restrictions', value)) def __init__(self, root, handler = None, src = False): """ @@ -120,18 +128,14 @@ class Policy(object): @type src: bool """ self.watchers = [] - self.help_with_testing = False - self.network_use = network_full self.freshness = 60 * 60 * 24 * 30 self.ready = False self.src = src # Root impl must be a "src" machine type self.restrictions = {} self.stale_feeds = sets.Set() - # This is used in is_unusable() to check whether the impl is - # for the root interface when looking for source. It is also - # used to add restrictions to the root (e.g. --before and --not-before) - self.root_restrictions = [] + from zeroinstall.injector.solver import DefaultSolver + self.solver = DefaultSolver(network_full, iface_cache, iface_cache.stores, root_restrictions = []) # If we need to download something but can't because we are offline, # warn the user. But only the first time. @@ -148,11 +152,11 @@ class Policy(object): try: config = ConfigParser.ConfigParser() config.read(path) - self.help_with_testing = config.getboolean('global', + self.solver.help_with_testing = config.getboolean('global', 'help_with_testing') - self.network_use = config.get('global', 'network_use') + self.solver.network_use = config.get('global', 'network_use') self.freshness = int(config.get('global', 'freshness')) - assert self.network_use in network_levels + assert self.solver.network_use in network_levels except Exception, ex: warn("Error loading config: %s", ex) @@ -221,32 +225,16 @@ class Policy(object): self.stale_feeds = sets.Set() self.restrictions = {} self.implementation = {} - self.ready = True - debug("Recalculate! root = %s", self.root) - def process(dep): - iface = self.get_interface(dep.interface) - if iface in self.implementation: - debug("Interface requested twice; skipping second %s", iface) - if dep.restrictions: - warn("Interface requested twice; I've already chosen an implementation " - "of '%s' but there are more restrictions! Ignoring the second set.", iface) - return - self.implementation[iface] = None # Avoid cycles - - assert iface not in self.restrictions - self.restrictions[iface] = dep.restrictions - - impl = self._get_best_implementation(iface) - if impl: - debug("Will use implementation %s (version %s)", impl, impl.get_version()) - self.implementation[iface] = impl - for d in impl.requires: - debug("Considering dependency %s", d) - process(d) - else: - debug("No implementation chould be chosen yet"); - self.ready = False - process(InterfaceDependency(self.root, restrictions = self.root_restrictions)) + + host_arch = arch.get_host_architecture() + if self.src: + host_arch = arch.SourceArchitecture(host_arch) + self.ready = self.solver.solve(self.root, host_arch) + + for f in self.solver.feeds_used: + iface = self.get_interface(f) # May start a download + + self.implementation = self.solver.selections.copy() if fetch_stale_interfaces and self.network_use != network_offline: for stale in self.stale_feeds: @@ -255,95 +243,6 @@ class Policy(object): for w in self.watchers: w() - # Only to be called from recalculate, as it is quite slow. - # Use the results stored in self.implementation instead. - def _get_best_implementation(self, iface): - impls = iface.implementations.values() - for f in self.usable_feeds(iface): - debug("Processing feed %s", f) - try: - feed_iface = self.get_interface(f.uri) - if feed_iface.name and iface.uri not in feed_iface.feed_for: - warn("Missing for '%s' in '%s'", - iface.uri, f.uri) - if feed_iface.implementations: - impls.extend(feed_iface.implementations.values()) - except NeedDownload, ex: - raise ex - except Exception, ex: - warn("Failed to load feed %s for %s: %s", - f, iface, str(ex)) - - debug("get_best_implementation(%s), with feeds: %s", iface, iface.feeds) - - if not impls: - info("Interface %s has no implementations!", iface) - return None - best = impls[0] - for x in impls[1:]: - if self.compare(iface, x, best) < 0: - best = x - unusable = self.get_unusable_reason(best, self.restrictions.get(iface, [])) - if unusable: - info("Best implementation of %s is %s, but unusable (%s)", iface, best, unusable) - return None - return best - - def compare(self, interface, b, a): - """Compare a and b to see which would be chosen first. - @param interface: The interface we are trying to resolve, which may - not be the interface of a or b if they are from feeds. - @rtype: int""" - restrictions = self.restrictions.get(interface, []) - - a_stab = a.get_stability() - b_stab = b.get_stability() - - # Usable ones come first - r = cmp(self.is_unusable(b, restrictions), self.is_unusable(a, restrictions)) - if r: return r - - # Preferred versions come first - r = cmp(a_stab == preferred, b_stab == preferred) - if r: return r - - if self.network_use != network_full: - r = cmp(self.get_cached(a), self.get_cached(b)) - if r: return r - - # Stability - stab_policy = interface.stability_policy - if not stab_policy: - if self.help_with_testing: stab_policy = testing - else: stab_policy = stable - - if a_stab >= stab_policy: a_stab = preferred - if b_stab >= stab_policy: b_stab = preferred - - r = cmp(a_stab, b_stab) - if r: return r - - # Newer versions come before older ones - r = cmp(a.version, b.version) - if r: return r - - # Get best OS - r = cmp(arch.os_ranks.get(a.os, None), - arch.os_ranks.get(b.os, None)) - if r: return r - - # Get best machine - r = cmp(arch.machine_ranks.get(a.machine, None), - arch.machine_ranks.get(b.machine, None)) - if r: return r - - # Slightly prefer cached versions - if self.network_use == network_full: - r = cmp(self.get_cached(a), self.get_cached(b)) - if r: return r - - return cmp(a.id, b.id) - def usable_feeds(self, iface): """Generator for C{iface.feeds} that are valid for our architecture. @rtype: generator @@ -361,55 +260,6 @@ class Policy(object): debug("Skipping '%s'; unsupported architecture %s-%s", f, f.os, f.machine) - def get_ranked_implementations(self, iface): - """Get all implementations from all feeds, in order. - @type iface: Interface - @return: a sorted list of implementations. - @rtype: [model.Implementation]""" - impls = iface.implementations.values() - for f in self.usable_feeds(iface): - feed_iface = iface_cache.get_interface(f.uri) - if feed_iface.implementations: - impls.extend(feed_iface.implementations.values()) - impls.sort(lambda a, b: self.compare(iface, a, b)) - return impls - - def is_unusable(self, impl, restrictions = []): - """@return: whether this implementation is unusable. - @rtype: bool""" - return self.get_unusable_reason(impl, restrictions) != None - - def get_unusable_reason(self, impl, restrictions = []): - """ - @param impl: Implementation to test. - @type restrictions: [L{model.Restriction}] - @return: The reason why this impl is unusable, or None if it's OK. - @rtype: str - @note: The restrictions are for the interface being requested, not the interface - of the implementation; they may be different when feeds are being used.""" - for r in restrictions: - if not r.meets_restriction(impl): - return "Incompatible with another selected implementation" - stability = impl.get_stability() - if stability <= buggy: - return stability.name - if self.network_use == network_offline and not self.get_cached(impl): - return "Not cached and we are off-line" - if impl.os not in arch.os_ranks: - return "Unsupported OS" - # When looking for source code, we need to known if we're - # looking at an implementation of the root interface, even if - # it's from a feed, hence the sneaky restrictions identity check. - if self.src and restrictions is self.root_restrictions: - if impl.machine != 'src': - return "Not source code" - else: - if impl.machine not in arch.machine_ranks: - if impl.machine == 'src': - return "Source code" - return "Unsupported machine type" - return None - def get_interface(self, uri): """Get an interface from the L{iface_cache}. If it is missing start a new download. If it is present but stale, add it to L{stale_feeds}. This should only be called @@ -423,32 +273,33 @@ class Policy(object): # TODO: unless the pending version is very old return iface - if iface.last_modified is None: - if self.network_use != network_offline: - debug("Interface not cached and not off-line. Downloading...") - self.begin_iface_download(iface) - else: - if self._warned_offline: - debug("Nothing known about interface, but we are off-line.") + if not uri.startswith('/'): + if iface.last_modified is None: + if self.network_use != network_offline: + debug("Interface not cached and not off-line. Downloading...") + self.begin_iface_download(iface) else: - if iface.feeds: - info("Nothing known about interface '%s' and off-line. Trying feeds only.", uri) + if self._warned_offline: + debug("Nothing known about interface, but we are off-line.") else: - warn("Nothing known about interface '%s', but we are in off-line mode " - "(so not fetching).", uri) - self._warned_offline = True - elif not uri.startswith('/'): - now = time.time() - staleness = now - (iface.last_checked or 0) - debug("Staleness for %s is %.2f hours", iface, staleness / 3600.0) - - if self.freshness > 0 and staleness > self.freshness: - last_check_attempt = iface_cache.get_last_check_attempt(iface.uri) - if last_check_attempt and last_check_attempt > now - FAILED_CHECK_DELAY: - debug("Stale, but tried to check recently (%s) so not rechecking now.", time.ctime(last_check_attempt)) - else: - debug("Adding %s to stale set", iface) - self.stale_feeds.add(iface) + if iface.feeds: + info("Nothing known about interface '%s' and off-line. Trying feeds only.", uri) + else: + warn("Nothing known about interface '%s', but we are in off-line mode " + "(so not fetching).", uri) + self._warned_offline = True + else: + now = time.time() + staleness = now - (iface.last_checked or 0) + debug("Staleness for %s is %.2f hours", iface, staleness / 3600.0) + + if self.freshness > 0 and staleness > self.freshness: + last_check_attempt = iface_cache.get_last_check_attempt(iface.uri) + if last_check_attempt and last_check_attempt > now - FAILED_CHECK_DELAY: + debug("Stale, but tried to check recently (%s) so not rechecking now.", time.ctime(last_check_attempt)) + else: + debug("Adding %s to stale set", iface) + self.stale_feeds.add(iface) #else: debug("Local interface, so not checking staleness.") return iface @@ -582,10 +433,6 @@ class Policy(object): (interface.name, offline)) raise ex - def walk_interfaces(self): - """@deprecated: use L{implementation} instead""" - return iter(self.implementation) - def get_cached(self, impl): """Check whether an implementation is available locally. @type impl: model.Implementation @@ -614,7 +461,7 @@ class Policy(object): uncached = [] for iface in self.implementation: impl = self.implementation[iface] - assert impl + assert impl, self.implementation if not self.get_cached(impl): uncached.append((iface, impl)) return uncached diff --git a/zeroinstall/injector/solver.py b/zeroinstall/injector/solver.py index be290fb..e9e486f 100644 --- a/zeroinstall/injector/solver.py +++ b/zeroinstall/injector/solver.py @@ -4,10 +4,13 @@ Chooses a set of components to make a running program. This class is intended to replace L{policy.Policy}. """ +import os from logging import debug, warn, info -import selections -import model +from zeroinstall.zerostore import BadDigest, NotStored + +from zeroinstall.injector import selections +from zeroinstall.injector import model # Copyright (C) 2008, Thomas Leonard # See the README file for details, or visit http://0install.net. @@ -19,47 +22,69 @@ class Solver(object): 2. Call L{solve}. 3. If any of the returned feeds_used are stale or missing, you may like to start downloading them 4. If it is 'ready' then you can download and run the chosen versions. + @ivar selections: the chosen implementation of each interface + @type selections: {L{model.Interface}: Implementation} + @ivar feeds_used: the feeds which contributed to the choice in L{selections} + @type feeds_used: set(str) + @ivar record_details: whether to record information about unselected implementations + @type record_details: {L{Interface}: [(L{Implementation}, str)]} + @ivar details: extra information, if record_details mode was used + @type details: {str: [(Implementation, comment)]} """ - __slots__ = [] + __slots__ = ['selections', 'feeds_used', 'details', 'record_details'] def __init__(self): - pass + self.selections = self.feeds_used = self.details = None + self.record_details = False - def solve(self, root_interface, feed_cache, arch): + def solve(self, root_interface, arch): """Get the best implementation of root_interface and all of its dependencies. @param root_interface: the URI of the program to be solved @type root_interface: str - @param feed_cache: a cache of feeds containing information about available versions - @type feed_cache: L{iface_cache.IfaceCache} @param arch: the desired target architecture @type arch: L{arch.Architecture} - @return: ready, selections, feeds_used - @rtype: (bool, Selections, [str])""" + @return: whether we have a viable selection + @rtype: bool + @postcondition: self.selections and self.feeds_used are updated""" raise NotImplementedError("Abstract") class DefaultSolver(Solver): - def __init__(self, network_use, root_restrictions = None): + def __init__(self, network_use, iface_cache, stores, root_restrictions = None): + """ + @param network_use: how much use to make of the network + @type network_use: L{model.network_levels} + @param iface_cache: a cache of feeds containing information about available versions + @type iface_cache: L{iface_cache.IfaceCache} + @param stores: a cached of implementations (affects choice when offline or when minimising network use) + @type stores: L{zerostore.Stores} + @param root_restrictions: list of extra restrictions for the root interface + @type root_restrictions: [L{model.Restriction}] + """ + Solver.__init__(self) self.network_use = network_use + self.iface_cache = iface_cache + self.stores = stores self.help_with_testing = False self.root_restrictions = root_restrictions or [] - def solve(self, root_interface, feed_cache, arch): - ready = True - chosen = {} - feeds_used = set() + def solve(self, root_interface, arch): + self.selections = {} + self.feeds_used = set() + self.details = self.record_details and {} restrictions = {} debug("Solve! root = %s", root_interface) def process(dep, arch): - iface = feed_cache.get_interface(dep.interface) + ready = True + iface = self.iface_cache.get_interface(dep.interface) - if iface in chosen: + if iface in self.selections: debug("Interface requested twice; skipping second %s", iface) if dep.restrictions: warn("Interface requested twice; I've already chosen an implementation " "of '%s' but there are more restrictions! Ignoring the second set.", iface) return - chosen[iface.uri] = None # Avoid cycles + self.selections[iface] = None # Avoid cycles assert iface not in restrictions restrictions[iface] = dep.restrictions @@ -67,36 +92,50 @@ class DefaultSolver(Solver): impl = get_best_implementation(iface, arch) if impl: debug("Will use implementation %s (version %s)", impl, impl.get_version()) - chosen[iface.uri] = impl + self.selections[iface] = impl for d in impl.requires: debug("Considering dependency %s", d) - process(d, arch.child_arch) + if not process(d, arch.child_arch): + ready = False else: debug("No implementation chould be chosen yet"); ready = False - return arch + return ready def get_best_implementation(iface, arch): + debug("get_best_implementation(%s), with feeds: %s", iface, iface.feeds) + impls = [] for f in usable_feeds(iface, arch): - feeds_used.add(f.url) + self.feeds_used.add(f) debug("Processing feed %s", f) + try: - if f.implementations: - impls.extend(f.implementations.values()) + feed = self.iface_cache.get_interface(f)._main_feed + if not feed.last_modified: continue # DummyFeed + if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for: + warn("Missing for '%s' in '%s'", iface.uri, f) + + if feed.implementations: + impls.extend(feed.implementations.values()) except Exception, ex: warn("Failed to load feed %s for %s: %s", f, iface, str(ex)) - raise - - debug("get_best_implementation(%s), with feeds: %s", iface, iface.feeds) if not impls: info("Interface %s has no implementations!", iface) return None - best = impls[0] - for x in impls[1:]: - if compare(iface, x, best) < 0: - best = x + + if self.record_details: + # In details mode, rank all the implementations and then choose the best + impls.sort(lambda a, b: compare(iface, a, b)) + best = impls[0] + self.details[iface] = [(impl, get_unusable_reason(impl, restrictions.get(iface, []), arch)) for impl in impls] + else: + # Otherwise, just choose the best without sorting + best = impls[0] + for x in impls[1:]: + if compare(iface, x, best) < 0: + best = x unusable = get_unusable_reason(best, restrictions.get(iface, []), arch) if unusable: info("Best implementation of %s is %s, but unusable (%s)", iface, best, unusable) @@ -122,7 +161,7 @@ class DefaultSolver(Solver): if r: return r if self.network_use != model.network_full: - r = cmp(self.get_cached(a), self.get_cached(b)) + r = cmp(get_cached(a), get_cached(b)) if r: return r # Stability @@ -131,8 +170,8 @@ class DefaultSolver(Solver): if self.help_with_testing: stab_policy = model.testing else: stab_policy = model.stable - if a_stab >= stab_policy: a_stab = preferred - if b_stab >= stab_policy: b_stab = preferred + if a_stab >= stab_policy: a_stab = model.preferred + if b_stab >= stab_policy: b_stab = model.preferred r = cmp(a_stab, b_stab) if r: return r @@ -153,7 +192,7 @@ class DefaultSolver(Solver): # Slightly prefer cached versions if self.network_use == model.network_full: - r = cmp(self.get_cached(a), self.get_cached(b)) + r = cmp(get_cached(a), get_cached(b)) if r: return r return cmp(a.id, b.id) @@ -161,31 +200,15 @@ class DefaultSolver(Solver): def usable_feeds(iface, arch): """Return all feeds for iface that support arch. @rtype: generator(ZeroInstallFeed)""" - yield iface._main_feed + yield iface.uri for f in iface.feeds: if f.os in arch.os_ranks and f.machine in arch.machine_ranks: - feed_iface = feed_cache.get_interface(f.uri) - if feed_iface.name and iface.uri not in feed_iface.feed_for: - warn("Missing for '%s' in '%s'", iface.uri, f.uri) - yield feed_iface._main_feed + yield f.uri else: debug("Skipping '%s'; unsupported architecture %s-%s", f, f.os, f.machine) - def get_ranked_implementations(self, iface): - """Get all implementations from all feeds, in order. - @type iface: Interface - @return: a sorted list of implementations. - @rtype: [model.Implementation]""" - impls = iface.implementations.values() - for f in self.usable_feeds(iface): - feed_iface = iface_cache.get_interface(f.uri) - if feed_iface.implementations: - impls.extend(feed_iface.implementations.values()) - impls.sort(lambda a, b: compare(iface, a, b)) - return impls - def is_unusable(impl, restrictions, arch): """@return: whether this implementation is unusable. @rtype: bool""" @@ -205,7 +228,7 @@ class DefaultSolver(Solver): stability = impl.get_stability() if stability <= model.buggy: return stability.name - if self.network_use == model.network_offline and not self.get_cached(impl): + if self.network_use == model.network_offline and not get_cached(impl): return "Not cached and we are off-line" if impl.os not in arch.os_ranks: return "Unsupported OS" @@ -218,24 +241,23 @@ class DefaultSolver(Solver): return "Unsupported machine type" return None - process(model.InterfaceDependency(root_interface, restrictions = self.root_restrictions), arch) - - return (ready, selections.Selections(chosen), feeds_used) + def get_cached(impl): + """Check whether an implementation is available locally. + @type impl: model.Implementation + @rtype: bool + """ + if isinstance(impl, model.DistributionImplementation): + return impl.installed + if impl.id.startswith('/'): + return os.path.exists(impl.id) + else: + try: + path = self.stores.lookup(impl.id) + assert path + return True + except BadDigest: + return False + except NotStored: + return False - def get_cached(self, impl): - """Check whether an implementation is available locally. - @type impl: model.Implementation - @rtype: bool - """ - if isinstance(impl, model.DistributionImplementation): - return impl.installed - if impl.id.startswith('/'): - return os.path.exists(impl.id) - else: - try: - path = self.get_implementation_path(impl) - assert path - return True - except: - pass # OK - return False + return process(model.InterfaceDependency(root_interface, restrictions = self.root_restrictions), arch) -- 2.11.4.GIT