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
Exemple #3
0
        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
Exemple #4
0
	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