def compare(interface, b, a, iface_restrictions, arch): """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""" a_stab = a.get_stability() b_stab = b.get_stability() # Usable ones come first r = cmp(is_unusable(b, iface_restrictions, arch), is_unusable(a, iface_restrictions, arch)) if r: return r machine = machine_groups.get(os.uname()[-1], 0) r = cmp( machine_groups.get(a.machine, 0) == machine, machine_groups.get(b.machine, 0) == machine) if r: return r # Preferred versions come first r = cmp(a_stab == model.preferred, b_stab == model.preferred) if r: return r if self.network_use != model.network_full: r = cmp(get_cached(a), get_cached(b)) if r: return r # Stability stab_policy = interface.stability_policy if not stab_policy: if self.help_with_testing: stab_policy = model.testing else: stab_policy = model.stable 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 # 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(b.os, None), arch.os_ranks.get(a.os, None)) if r: return r # Get best machine r = cmp(arch.machine_ranks.get(b.machine, None), arch.machine_ranks.get(a.machine, None)) if r: return r # Slightly prefer cached versions if self.network_use == model.network_full: r = cmp(get_cached(a), get_cached(b)) if r: return r return cmp(a.id, b.id)
def get_unusable_reason(impl, restrictions, arch): """ @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.""" machine = impl.machine if machine and self._machine_group is not None: if machine_groups.get(machine, 0) != self._machine_group: return _( "Incompatible with another selection from a different architecture group" ) for r in restrictions: if not r.meets_restriction(impl): return _( "Incompatible with another selected implementation") stability = impl.get_stability() if stability <= model.buggy: return stability.name 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") # 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 machine not in arch.machine_ranks: if machine == 'src': return _("Source code") return _("Unsupported machine type") return None
def add_iface(uri, arch): """Name implementations from feed and assert that only one can be selected.""" if uri in ifaces_processed: return ifaces_processed.add(uri) iface = iface_cache.get_interface(uri) impls = [] for f in usable_feeds(iface, arch): self.feeds_used.add(f) debug(_("Processing feed %s"), f) try: feed = iface_cache.get_feed(f) if feed is None: continue #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for: # info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f}) if feed.implementations: impls.extend(feed.implementations.values()) distro_feed_url = feed.get_distro_feed() if distro_feed_url: self.feeds_used.add(distro_feed_url) distro_feed = iface_cache.get_feed(distro_feed_url) if distro_feed.implementations: impls.extend(distro_feed.implementations.values()) except MissingLocalFeed as ex: warn( _("Missing local feed; if it's no longer required, remove it with:" ) + '\n0install remove-feed ' + iface.uri + ' ' + f, { 'feed': f, 'interface': iface, 'exception': ex }) except Exception as ex: warn( _("Failed to load feed %(feed)s for %(interface)s: %(exception)s" ), { 'feed': f, 'interface': iface, 'exception': ex }) #raise impls.sort(key=lambda impl: self.get_rating(iface, impl, arch), reverse=True) impls_for_iface[iface] = filtered_impls = [] my_extra_restrictions = self.extra_restrictions.get(iface, []) if self.record_details: self.details[iface] = [ (impl, get_unusable_reason(impl, my_extra_restrictions, arch)) for impl in impls ] var_names = [] for impl in impls: if is_unusable(impl, my_extra_restrictions, arch): continue filtered_impls.append(impl) if impl in impl_to_var: # TODO If the same impl comes from original feed and # the feed with "feed" tag continue v = problem.add_variable(ImplInfo(iface, impl, arch)) impl_to_var[impl] = v var_names.append(v) if impl.machine and impl.machine != 'src': impls_for_machine_group[machine_groups.get( impl.machine, 0)].append(v) process_dependencies(v, impl, arch) if closest_match: dummy_impl = _DummyImpl() dummy_var = problem.add_variable( ImplInfo(iface, dummy_impl, arch, dummy=True)) var_names.append(dummy_var) impl_to_var[dummy_impl] = dummy_var filtered_impls.append(dummy_impl) # Only one implementation of this interface can be selected if uri == root_interface: if var_names: clause = problem.at_most_one(var_names) problem.add_clause(var_names) # at least one else: problem.impossible() clause = False elif var_names: clause = problem.at_most_one(var_names) else: # Don't need to add to group_clause_for because we should # never get a possible selection involving this. return assert clause is not True assert clause is not None if clause is not False: group_clause_for[uri] = clause
def solve(self, root_interface, root_arch, command_name = 'run', closest_match = False): # closest_match is used internally. It adds a lowest-ranked # by valid implementation to every interface, so we can always # select something. Useful for diagnostics. # TODO: We need some way to figure out which feeds to include. # Currently, we include any feed referenced from anywhere but # this is probably too much. We could insert a dummy optimial # implementation in stale/uncached feeds and see whether it # selects that. iface_cache = self.config.iface_cache problem = sat.SATProblem() impl_to_var = {} # Impl -> sat var self.feeds_used = set() self.requires = {} self.ready = False self.details = self.record_details and {} self.selections = None self._failure_reason = None ifaces_processed = set() impls_for_machine_group = {0 : []} # Machine group (e.g. "64") to [impl] in that group for machine_group in machine_groups.values(): impls_for_machine_group[machine_group] = [] impls_for_iface = {} # Iface -> [impl] group_clause_for = {} # Iface URI -> AtMostOneClause | bool group_clause_for_command = {} # (Iface URI, command name) -> AtMostOneClause | bool # Return the dependencies of impl that we should consider. # Skips dependencies if the use flag isn't what we need. # (note: impl may also be a model.Command) def deps_in_use(impl, arch): for dep in impl.requires: use = dep.metadata.get("use", None) if use not in arch.use: continue yield dep # Add a clause so that if requiring_impl_var is True then an implementation # matching 'dependency' must also be selected. # Must have already done add_iface on dependency.interface. def find_dependency_candidates(requiring_impl_var, dependency): dep_iface = iface_cache.get_interface(dependency.interface) dep_union = [sat.neg(requiring_impl_var)] # Either requiring_impl_var is False, or ... for candidate in impls_for_iface[dep_iface]: for r in dependency.restrictions: if candidate.__class__ is not _DummyImpl and not r.meets_restriction(candidate): #warn("%s rejected due to %s", candidate.get_version(), r) if candidate.version is not None: break else: c_var = impl_to_var.get(candidate, None) if c_var is not None: dep_union.append(c_var) # else we filtered that version out, so ignore it assert dep_union problem.add_clause(dep_union) def is_unusable(impl, restrictions, arch): """@return: whether this implementation is unusable. @rtype: bool""" return get_unusable_reason(impl, restrictions, arch) != None def get_unusable_reason(impl, restrictions, arch): """ @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 feed 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 <= model.buggy: return stability.name if (self.config.network_use == model.network_offline or not impl.download_sources) and not _get_cached(self.config.stores, impl): if not impl.download_sources: return _("No retrieval methods") return _("Not cached and we are off-line") if impl.os not in arch.os_ranks: return _("Unsupported OS") if impl.machine not in arch.machine_ranks: if impl.machine == 'src': return _("Source code") return _("Unsupported machine type") return None def usable_feeds(iface, arch): """Return all feeds for iface that support arch. @rtype: generator(ZeroInstallFeed)""" yield iface.uri for f in iface_cache.get_feed_imports(iface): # Note: when searching for src, None is not in machine_ranks if f.os in arch.os_ranks and \ (f.machine is None or f.machine in arch.machine_ranks): yield f.uri else: debug(_("Skipping '%(feed)s'; unsupported architecture %(os)s-%(machine)s"), {'feed': f, 'os': f.os, 'machine': f.machine}) def add_iface(uri, arch): """Name implementations from feed and assert that only one can be selected.""" if uri in ifaces_processed: return ifaces_processed.add(uri) iface = iface_cache.get_interface(uri) impls = [] for f in usable_feeds(iface, arch): self.feeds_used.add(f) debug(_("Processing feed %s"), f) try: feed = iface_cache.get_feed(f) if feed is None: continue #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for: # info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f}) if feed.implementations: impls.extend(feed.implementations.values()) distro_feed_url = feed.get_distro_feed() if distro_feed_url: self.feeds_used.add(distro_feed_url) distro_feed = iface_cache.get_feed(distro_feed_url) if distro_feed.implementations: impls.extend(distro_feed.implementations.values()) except Exception, ex: warn(_("Failed to load feed %(feed)s for %(interface)s: %(exception)s"), {'feed': f, 'interface': iface, 'exception': ex}) #raise impls.sort(lambda a, b: self.compare(iface, a, b, arch)) impls_for_iface[iface] = filtered_impls = [] my_extra_restrictions = self.extra_restrictions.get(iface, []) if self.record_details: self.details[iface] = [(impl, get_unusable_reason(impl, my_extra_restrictions, arch)) for impl in impls] rank = 1 var_names = [] for impl in impls: if is_unusable(impl, my_extra_restrictions, arch): continue filtered_impls.append(impl) assert impl not in impl_to_var v = problem.add_variable(ImplInfo(iface, impl, arch)) impl_to_var[impl] = v rank += 1 var_names.append(v) if impl.machine and impl.machine != 'src': impls_for_machine_group[machine_groups.get(impl.machine, 0)].append(v) for d in deps_in_use(impl, arch): debug(_("Considering dependency %s"), d) add_iface(d.interface, arch.child_arch) # Must choose one version of d if impl is selected find_dependency_candidates(v, d) if closest_match: dummy_impl = _DummyImpl() dummy_var = problem.add_variable(ImplInfo(iface, dummy_impl, arch, dummy = True)) var_names.append(dummy_var) impl_to_var[dummy_impl] = dummy_var filtered_impls.append(dummy_impl) # Only one implementation of this interface can be selected if uri == root_interface: if var_names: clause = problem.at_most_one(var_names) problem.add_clause(var_names) # at least one else: problem.impossible() clause = False elif var_names: clause = problem.at_most_one(var_names) else: # Don't need to add to group_clause_for because we should # never get a possible selection involving this. return assert clause is not True assert clause is not None if clause is not False: group_clause_for[uri] = clause
def add_iface(uri, arch): """Name implementations from feed and assert that only one can be selected.""" if uri in ifaces_processed: return ifaces_processed.add(uri) iface = iface_cache.get_interface(uri) impls = [] for f in usable_feeds(iface, arch): self.feeds_used.add(f) debug(_("Processing feed %s"), f) try: feed = iface_cache.get_feed(f) if feed is None: continue #if feed.name and iface.uri != feed.url and iface.uri not in feed.feed_for: # info(_("Missing <feed-for> for '%(uri)s' in '%(feed)s'"), {'uri': iface.uri, 'feed': f}) if feed.implementations: impls.extend(feed.implementations.values()) distro_feed_url = feed.get_distro_feed() if distro_feed_url: self.feeds_used.add(distro_feed_url) distro_feed = iface_cache.get_feed(distro_feed_url) if distro_feed.implementations: impls.extend(distro_feed.implementations.values()) except MissingLocalFeed as ex: warn(_("Missing local feed; if it's no longer required, remove it with:") + '\n0install remove-feed ' + iface.uri + ' ' + f, {'feed': f, 'interface': iface, 'exception': ex}) except Exception as ex: warn(_("Failed to load feed %(feed)s for %(interface)s: %(exception)s"), {'feed': f, 'interface': iface, 'exception': ex}) #raise impls.sort(lambda a, b: self.compare(iface, a, b, arch)) impls_for_iface[iface] = filtered_impls = [] my_extra_restrictions = self.extra_restrictions.get(iface, []) if self.record_details: self.details[iface] = [(impl, get_unusable_reason(impl, my_extra_restrictions, arch)) for impl in impls] rank = 1 var_names = [] for impl in impls: if is_unusable(impl, my_extra_restrictions, arch): continue filtered_impls.append(impl) assert impl not in impl_to_var v = problem.add_variable(ImplInfo(iface, impl, arch)) impl_to_var[impl] = v rank += 1 var_names.append(v) if impl.machine and impl.machine != 'src': impls_for_machine_group[machine_groups.get(impl.machine, 0)].append(v) for d in deps_in_use(impl, arch): debug(_("Considering dependency %s"), d) add_iface(d.interface, arch.child_arch) # Must choose one version of d if impl is selected find_dependency_candidates(v, d) if closest_match: dummy_impl = _DummyImpl() dummy_var = problem.add_variable(ImplInfo(iface, dummy_impl, arch, dummy = True)) var_names.append(dummy_var) impl_to_var[dummy_impl] = dummy_var filtered_impls.append(dummy_impl) # Only one implementation of this interface can be selected if uri == root_interface: if var_names: clause = problem.at_most_one(var_names) problem.add_clause(var_names) # at least one else: problem.impossible() clause = False elif var_names: clause = problem.at_most_one(var_names) else: # Don't need to add to group_clause_for because we should # never get a possible selection involving this. return assert clause is not True assert clause is not None if clause is not False: group_clause_for[uri] = clause
def process(dep, arch): iface = self.iface_cache.get_interface(dep.interface) iface_restrictions = dep.restrictions + self.extra_restrictions.get( iface, []) if iface in self.selections: impl = self.selections[iface] if impl is not None: return not get_unusable_reason(impl, iface_restrictions, arch) else: warn( "Interface %s requested twice but first one was failed", iface) return True self.selections[iface] = None # Avoid cycles impls = get_implementations(iface, arch) # sort in reverse mode to use pop() impls.sort( lambda a, b: compare(iface, b, a, iface_restrictions, arch)) if self.record_details: self.details[iface] = [] best = None while impls and best is None: self.requires[iface] = selected_requires = [] impl = impls.pop() if dep.is_optional() and not get_cached(impl): continue unusable = get_unusable_reason(impl, iface_restrictions, arch) if unusable: info( _("Best implementation of %(interface)s is %(best)s, but unusable (%(unusable)s)" ), { 'interface': iface, 'best': impl, 'unusable': unusable }) if self.record_details: self.details[iface].append((impl, unusable)) continue best = impl machine_group_set = False if self._machine_group is None and best.machine and best.machine != 'src': self._machine_group = machine_groups.get(best.machine, 0) debug(_("Now restricted to architecture group %s"), self._machine_group) machine_group_set = True prev_selections = set(self.selections.keys()) try: for d in impl.requires: debug(_("Considering dependency %s"), d) use = d.metadata.get("use", None) if use not in arch.use: info("Skipping dependency; use='%s' not in %s", use, arch.use) continue if process(d, arch.child_arch): selected_requires.append(d) elif not d.is_optional(): best = None break finally: if best is None and impls: for i in set(self.selections.keys()) - prev_selections: del self.selections[i] if machine_group_set: self._machine_group = None if self.record_details: if best is None: self.details[iface].append( (impl, _("Incompatible with dependency restrictions"))) else: self.details[iface].append((impl, None)) if self.record_details: for impl in impls: unusable = get_unusable_reason(impl, iface_restrictions, arch) if unusable: self.details[iface].append((impl, unusable)) else: self.details[iface].append((impl, _('Not processed'))) if best is not None: debug( _("Will use implementation %(implementation)s (version %(version)s)" ), { 'implementation': best, 'version': best.get_version() }) self.selections[iface] = best elif dep.is_optional(): info(_("Skip optional but not ready %(interface)s dependency"), {'interface': iface}) del self.selections[iface] else: debug(_("No implementation chould be chosen yet")) return best is not None