示例#1
0
文件: tester.py 项目: ajtowns/britney
    def _check_loop(self, universe, testing, eqv_table, musts, never,
                    choices, cbroken, check, len=len,
                    frozenset=frozenset):
        """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...
        not_satisfied = partial(ifilter, musts.isdisjoint)

        # While we have guaranteed dependencies (in check), examine all
        # of them.
        for cur in iter_except(check.pop, KeyError):
            (deps, cons) = universe[cur]

            if cons:
                # 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
                never.update(cons & testing)

            # depgroup can be satisifed by picking something that is
            # already in musts - lets pick that (again).  :)
            for depgroup in not_satisfied(deps):

                # Of all the packages listed in the relation remove those that
                # are either:
                #  - not in testing
                #  - known to be broken (by cache)
                #  - in never
                candidates = frozenset((depgroup & testing) - never)

                if len(candidates) == 0:
                    # We got no candidates to satisfy it - this
                    # package cannot be installed with the current
                    # testing
                    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)
                        testing.remove(cur)
                    return False
                if len(candidates) == 1:
                    # only one possible solution to this choice and we
                    # haven't seen it before
                    check.update(candidates)
                    musts.update(candidates)
                else:
                    possible_eqv = set(x for x in candidates if x in eqv_table)
                    if len(possible_eqv) > 1:
                        # Exploit equivalency to reduce the number of
                        # 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)
                        for chosen in iter_except(possible_eqv.pop, KeyError):
                            new_cand.add(chosen)
                            possible_eqv -= eqv_table[chosen]
                        if len(new_cand) == 1:
                            check.update(new_cand)
                            musts.update(new_cand)
                            continue
                        candidates = frozenset(new_cand)
                    # defer this choice till later
                    choices.add(candidates)
        return True
示例#2
0
    def solve_groups(self, groups):
        sat_in_testing = self._testing.isdisjoint
        universe = self._universe
        revuniverse = self._revuniverse
        result = []
        emitted = set()
        check = set()
        order = {}
        ptable = {}
        key2item = {}
        going_out = set()
        going_in = set()
        debug_solver = 0

        try:
            debug_solver = int(os.environ.get('BRITNEY_DEBUG', '0'))
        except:
            pass

        # Build the tables
        for (item, adds, rms) in groups:
            key = str(item)
            key2item[key] = item
            order[key] = {'before': set(), 'after': set()}
            going_in.update(adds)
            going_out.update(rms)
            for a in adds:
                ptable[a] = key
            for r in rms:
                ptable[r] = key

        if debug_solver > 1:
            self._dump_groups(groups)

        # This large loop will add ordering constrains on each "item"
        # that migrates based on various rules.
        for (item, adds, rms) in groups:
            key = str(item)
            oldcons = set()
            newcons = set()
            for r in rms:
                oldcons.update(universe[r][1])
            for a in adds:
                newcons.update(universe[a][1])
            current = newcons & oldcons
            oldcons -= current
            newcons -= current
            if oldcons:
                # Some of the old binaries have "conflicts" that will
                # be removed.
                for o in ifilter_only(ptable, oldcons):
                    # "key" removes a conflict with one of
                    # "other"'s binaries, so it is probably a good
                    # idea to migrate "key" before "other"
                    other = ptable[o]
                    if other == key:
                        # "Self-conflicts" => ignore
                        continue
                    if debug_solver and other not in order[key]['before']:
                        print("N: Conflict induced order: %s before %s" % (key, other))
                    order[key]['before'].add(other)
                    order[other]['after'].add(key)

            for r in ifilter_only(revuniverse, rms):
                # The binaries have reverse dependencies in testing;
                # check if we can/should migrate them first.
                for rdep in revuniverse[r][0]:
                    for depgroup in universe[rdep][0]:
                        rigid = depgroup - going_out
                        if not sat_in_testing(rigid):
                            # (partly) satisfied by testing, assume it is okay
                            continue
                        if rdep in ptable:
                            other = ptable[rdep]
                            if other == key:
                                # "Self-dependency" => ignore
                                continue
                            if debug_solver and other not in order[key]['after']:
                                print("N: Removal induced order: %s before %s" % (key, other))
                            order[key]['after'].add(other)
                            order[other]['before'].add(key)

            for a in adds:
                # Check if this item should migrate before others
                # (e.g. because they depend on a new [version of a]
                # binary provided by this item).
                for depgroup in universe[a][0]:
                    rigid = depgroup - going_out
                    if not sat_in_testing(rigid):
                        # (partly) satisfied by testing, assume it is okay
                        continue
                    # okay - we got three cases now.
                    # - "swap" (replace existing binary with a newer version)
                    # - "addition" (add new binary without removing any)
                    # - "removal" (remove binary without providing a new)
                    #
                    # The problem is that only the two latter requires
                    # an ordering.  A "swap" (in itself) should not
                    # affect us.
                    other_adds = set()
                    other_rms = set()
                    for d in ifilter_only(ptable, depgroup):
                        if d in going_in:
                            # "other" provides something "key" needs,
                            # schedule accordingly.
                            other = ptable[d]
                            other_adds.add(other)
                        else:
                            # "other" removes something "key" needs,
                            # schedule accordingly.
                            other = ptable[d]
                            other_rms.add(other)

                    for other in (other_adds - other_rms):
                        if debug_solver and other != key and other not in order[key]['after']:
                            print("N: Dependency induced order (add): %s before %s" % (key, other))
                        order[key]['after'].add(other)
                        order[other]['before'].add(key)

                    for other in (other_rms - other_adds):
                        if debug_solver and other != key and other not in order[key]['before']:
                            print("N: Dependency induced order (remove): %s before %s" % (key, other))
                        order[key]['before'].add(other)
                        order[other]['after'].add(key)

        ### MILESTONE: Partial-order constrains computed ###

        # At this point, we have computed all the partial-order
        # constrains needed.  Some of these may have created strongly
        # connected components (SSC) [of size 2 or greater], which
        # represents a group of items that (we believe) must migrate
        # together.
        #
        # Each one of those components will become an "easy" hint.

        comps = self._compute_scc(order, ptable)
        merged = {}
        scc = {}
        # Now that we got the SSCs (in comps), we select on item from
        # each SSC to represent the group and become an ID for that
        # SSC.
        #  * ssc[ssc_id] => All the items in that SSC
        #  * merged[item] => The ID of the SSC to which the item belongs.
        #
        # We also "repair" the ordering, so we know in which order the
        # hints should be emitted.
        for com in comps:
            scc_id = com[0]
            scc[scc_id] = com
            merged[scc_id] = scc_id
            if len(com) > 1:
                so_before = order[scc_id]['before']
                so_after = order[scc_id]['after']
                for n in com:
                    if n == scc_id:
                        continue
                    so_before.update(order[n]['before'])
                    so_after.update(order[n]['after'])
                    merged[n] = scc_id
                    del order[n]
                if debug_solver:
                    print("N: SCC: %s -- %s" % (scc_id, str(sorted(com))))

        for com in comps:
            node = com[0]
            nbefore = set(merged[b] for b in order[node]['before'])
            nafter = set(merged[b] for b in order[node]['after'])

            # Drop self-relations (usually caused by the merging)
            nbefore.discard(node)
            nafter.discard(node)
            order[node]['before'] = nbefore
            order[node]['after'] = nafter


        if debug_solver:
            print("N: -- PARTIAL ORDER --")

        for com in sorted(order):
            if debug_solver and order[com]['before']:
                print("N: %s <= %s" % (com, str(sorted(order[com]['before']))))
            if not order[com]['after']:
                # This component can be scheduled immediately, add it
                # to "check"
                check.add(com)
            elif debug_solver:
                print("N: %s >= %s" % (com, str(sorted(order[com]['after']))))

        if debug_solver:
            print("N: -- END PARTIAL ORDER --")
            print("N: -- LINEARIZED ORDER --")

        for cur in iter_except(check.pop, KeyError):
            if order[cur]['after'] <= emitted:
                # This item is ready to be emitted right now
                if debug_solver:
                    print("N: %s -- %s" % (cur, sorted(scc[cur])))
                emitted.add(cur)
                result.append([key2item[x] for x in scc[cur]])
                if order[cur]['before']:
                    # There are components that come after this one.
                    # Add it to "check":
                    # - if it is ready, it will be emitted.
                    # - else, it will be dropped and re-added later.
                    check.update(order[cur]['before'] - emitted)

        if debug_solver:
            print("N: -- END LINEARIZED ORDER --")

        return result
示例#3
0
    def build(self):
        """Compile the installability tester

        This method will compile an installability tester from the
        information given and (where possible) try to optimise a
        few things.
        """
        package_table = self._package_table
        reverse_package_table = self._reverse_package_table
        intern_set = self._intern_set
        safe_set = set()
        broken = self._broken
        not_broken = ifilter_except(broken)
        check = set(broken)

        def safe_set_satisfies(t):
            """Check if t's dependencies can be satisfied by the safe set"""
            if not package_table[t][0]:
                # If it has no dependencies at all, then it is safe.  :)
                return True
            for depgroup in package_table[t][0]:
                if not any(dep for dep in depgroup if dep in safe_set):
                    return False
            return True


        # Merge reverse conflicts with conflicts - this saves some
        # operations in _check_loop since we only have to check one
        # set (instead of two) and we remove a few duplicates here
        # and there.
        #
        # At the same time, intern the rdep sets
        for pkg in reverse_package_table:
            if pkg not in package_table:
                raise RuntimeError("%s/%s/%s referenced but not added!" % pkg)
            deps, con = package_table[pkg]
            rdeps, rcon, rdep_relations = reverse_package_table[pkg]
            if rcon:
                if not con:
                    con = intern_set(rcon)
                else:
                    con = intern_set(con | rcon)
                package_table[pkg] = (deps, con)
            reverse_package_table[pkg] = (intern_set(rdeps), con,
                                          intern_set(rdep_relations))

        # Check if we can expand broken.
        for t in not_broken(iter_except(check.pop, KeyError)):
            # This package is not known to be broken... but it might be now
            isb = False
            for depgroup in package_table[t][0]:
                if not any(not_broken(depgroup)):
                    # A single clause is unsatisfiable, the
                    # package can never be installed - add it to
                    # broken.
                    isb = True
                    break

            if not isb:
                continue

            broken.add(t)

            if t not in reverse_package_table:
                continue
            check.update(reverse_package_table[t][0] - broken)

        if broken:
            # Since a broken package will never be installable, nothing that depends on it
            # will ever be installable.  Thus, there is no point in keeping relations on
            # the broken package.
            seen = set()
            empty_set = frozenset()
            null_data = (frozenset([empty_set]), empty_set)
            for b in (x for x in broken if x in reverse_package_table):
                for rdep in (r for r in not_broken(reverse_package_table[b][0])
                             if r not in seen):
                    ndep = intern_set((x - broken) for x in package_table[rdep][0])
                    package_table[rdep] = (ndep, package_table[rdep][1] - broken)
                    seen.add(rdep)

            # Since they won't affect the installability of any other package, we might as
            # as well null their data.  This memory for these packages, but likely there
            # will only be a handful of these "at best" (fsvo of "best")
            for b in broken:
                package_table[b] = null_data
                if b in reverse_package_table:
                    del reverse_package_table[b]

        # Now find an initial safe set (if any)
        check = set()
        for pkg in package_table:

            if package_table[pkg][1]:
                # has (reverse) conflicts - not safe
                continue
            if not safe_set_satisfies(pkg):
                continue
            safe_set.add(pkg)
            if pkg in reverse_package_table:
                # add all rdeps (except those already in the safe_set)
                check.update(reverse_package_table[pkg][0] - safe_set)

        # Check if we can expand the initial safe set
        for pkg in iter_except(check.pop, KeyError):
            if package_table[pkg][1]:
                # has (reverse) conflicts - not safe
                continue
            if safe_set_satisfies(pkg):
                safe_set.add(pkg)
                if pkg in reverse_package_table:
                    # add all rdeps (except those already in the safe_set)
                    check.update(reverse_package_table[pkg][0] - safe_set)

        eqv_table = self._build_eqv_packages_table(package_table,
                                       reverse_package_table)

        return InstallabilitySolver(package_table,
                                    reverse_package_table,
                                    self._testing, self._broken,
                                    self._essentials, safe_set,
                                    eqv_table)