Пример #1
0
    def guess_bad_solve(self, specs):
        # TODO: Check features as well
        from conda.console import setup_verbose_handlers
        setup_verbose_handlers()

        def mysat(specs):
            dists = self.get_dists(specs)
            groups = build_groups(dists)
            m, v, w = self.build_vw(groups)
            clauses = set(self.gen_clauses(v, groups, specs))
            return sat(clauses)

        # Don't show the dots from solve2 in normal mode but do show the
        # dotlog messages with --debug
        dotlog.setLevel(logging.INFO)
        specs = [s for s in specs if not s.optional]
        hint = minimal_unsatisfiable_subset(specs, sat=mysat, log=True)
        if not hint:
            return ''
        hint = list(map(str, hint))
        if len(hint) == 1:
            # TODO: Generate a hint from the dependencies.
            ret = (("\nHint: '{0}' has unsatisfiable dependencies (see 'conda "
                "info {0}')").format(hint[0].split()[0]))
        else:
            ret = """
Hint: the following packages conflict with each other:
  - %s

Use 'conda info %s' etc. to see the dependencies for each package.""" % ('\n  - '.join(hint), hint[0].split()[0])
        return ret
Пример #2
0
    def guess_bad_solve(self, specs, features):
        # TODO: Check features as well
        from conda.console import setup_verbose_handlers
        setup_verbose_handlers()

        # Don't show the dots from solve2 in normal mode but do show the
        # dotlog messages with --debug
        dotlog.setLevel(logging.WARN)

        def sat(specs):
            try:
                self.solve2(specs, features, guess=False, unsat_only=True)
            except RuntimeError:
                return False
            return True

        hint = minimal_unsatisfiable_subset(specs, sat=sat, log=True)
        if not hint:
            return ''
        if len(hint) == 1:
            # TODO: Generate a hint from the dependencies.
            return ((
                "\nHint: '{0}' has unsatisfiable dependencies (see 'conda "
                "info {0}')").format(hint[0].split()[0]))
        return ("""
Hint: the following packages conflict with each other:
  - %s""" % '\n  - '.join(hint))
Пример #3
0
    def guess_bad_solve(self, specs):
        # TODO: Check features as well
        from conda.console import setup_verbose_handlers
        setup_verbose_handlers()

        def mysat(specs):
            dists = self.get_dists(specs)
            groups = build_groups(dists)
            m, v, w = self.build_vw(groups)
            clauses = set(self.gen_clauses(v, groups, specs))
            return sat(clauses)

        # Don't show the dots from solve2 in normal mode but do show the
        # dotlog messages with --debug
        dotlog.setLevel(logging.INFO)
        specs = [s for s in specs if not s.optional]
        hint = minimal_unsatisfiable_subset(specs, sat=mysat, log=True)
        if not hint:
            return ''
        hint = list(map(str, hint))
        if len(hint) == 1:
            # TODO: Generate a hint from the dependencies.
            ret = (("\nHint: '{0}' has unsatisfiable dependencies (see 'conda "
                "info {0}')").format(hint[0].split()[0]))
        else:
            ret = """
Hint: the following packages conflict with each other:
  - %s

Use 'conda info %s' etc. to see the dependencies for each package.""" % ('\n  - '.join(hint), hint[0].split()[0])
        return ret
Пример #4
0
    def guess_bad_solve(self, specs, features):
        # TODO: Check features as well
        from conda.console import setup_verbose_handlers
        setup_verbose_handlers()

        # Don't show the dots from solve2 in normal mode but do show the
        # dotlog messages with --debug
        dotlog.setLevel(logging.WARN)

        def sat(specs):
            try:
                self.solve2(specs, features, guess=False, unsat_only=True)
            except RuntimeError:
                return False
            return True

        hint = minimal_unsatisfiable_subset(specs, sat=sat, log=True)
        if not hint:
            return ''
        if len(hint) == 1:
            # TODO: Generate a hint from the dependencies.
            return (("\nHint: '{0}' has unsatisfiable dependencies (see 'conda "
                "info {0}')").format(hint[0].split()[0]))
        return ("""
Hint: the following packages conflict with each other:
  - %s""" % '\n  - '.join(hint))
Пример #5
0
    def minimal_unsatisfiable_subset(self, clauses, v, w):
        clauses = minimal_unsatisfiable_subset(clauses, log=True)

        pretty_clauses = []
        for clause in clauses:
            if clause[0] < 0 and len(clause) > 1:
                pretty_clauses.append('%s => %s' %
                    (self.clause_pkg_name(-clause[0], w), ' or '.join([self.clause_pkg_name(j, w) for j in clause[1:]])))
            else:
                pretty_clauses.append(' or '.join([self.clause_pkg_name(j, w) for j in clause]))
        return "The following set of clauses is unsatisfiable:\n\n%s" % '\n'.join(pretty_clauses)
Пример #6
0
    def minimal_unsatisfiable_subset(self, clauses, v, w):
        clauses = minimal_unsatisfiable_subset(clauses, log=True)

        pretty_clauses = []
        for clause in clauses:
            if clause[0] < 0 and len(clause) > 1:
                pretty_clauses.append('%s => %s' %
                    (self.clause_pkg_name(-clause[0], w), ' or '.join([self.clause_pkg_name(j, w) for j in clause[1:]])))
            else:
                pretty_clauses.append(' or '.join([self.clause_pkg_name(j, w) for j in clause]))
        return "The following set of clauses is unsatisfiable:\n\n%s" % '\n'.join(pretty_clauses)
Пример #7
0
def test_minimal_unsatisfiable_subset():
    assert raises(ValueError, lambda: minimal_unsatisfiable_subset([[1]]))

    clauses = [[-10], [1], [5], [2, 3], [3, 4], [5, 2], [-7], [2], [3],
               [-2, -3, 5], [7, 8, 9, 10], [-8], [-9]]
    res = minimal_unsatisfiable_subset(clauses)
    assert sorted(res) == [[-10], [-9], [-8], [-7], [7, 8, 9, 10]]
    assert not sat(res)

    clauses = [[1, 3], [2, 3], [-1], [4], [3], [-3]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(clauses)
        assert sorted(res) == [[-3], [3]]
        assert not sat(res)

    clauses = [[1], [-1], [2], [-2], [3, 4], [4]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(perm)
        assert sorted(res) in [[[-1], [1]], [[-2], [2]]]
        assert not sat(res)
Пример #8
0
def test_minimal_unsatisfiable_subset():
    assert raises(ValueError, lambda: minimal_unsatisfiable_subset([[1]]))

    clauses = [[-10], [1], [5], [2, 3], [3, 4], [5, 2], [-7], [2], [3], [-2,
        -3, 5], [7, 8, 9, 10], [-8], [-9]]
    res = minimal_unsatisfiable_subset(clauses)
    assert sorted(res) == [[-10], [-9], [-8], [-7], [7, 8, 9, 10]]
    assert not sat(res)


    clauses = [[1, 3], [2, 3], [-1], [4], [3], [-3]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(clauses)
        assert sorted(res) == [[-3], [3]]
        assert not sat(res)

    clauses = [[1], [-1], [2], [-2], [3, 4], [4]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(perm)
        assert sorted(res) in [[[-1], [1]], [[-2], [2]]]
        assert not sat(res)
Пример #9
0
def test_minimal_unsatisfiable_subset():
    def sat(val):
        return Clauses(max(abs(v) for v in chain(*val))).sat(val)
    assert raises(ValueError, lambda: minimal_unsatisfiable_subset([[1]], sat))

    clauses = [[-10], [1], [5], [2, 3], [3, 4], [5, 2], [-7], [2], [3],
        [-2, -3, 5], [7, 8, 9, 10], [-8], [-9]]
    res = minimal_unsatisfiable_subset(clauses, sat)
    assert sorted(res) == [[-10], [-9], [-8], [-7], [7, 8, 9, 10]]
    assert not sat(res)

    clauses = [[1, 3], [2, 3], [-1], [4], [3], [-3]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(clauses, sat)
        assert sorted(res) == [[-3], [3]]
        assert not sat(res)

    clauses = [[1], [-1], [2], [-2], [3, 4], [4]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(perm, sat)
        assert sorted(res) in [[[-1], [1]], [[-2], [2]]]
        assert not sat(res)
Пример #10
0
def test_minimal_unsatisfiable_subset():
    def sat(val):
        return Clauses(max(abs(v) for v in chain(*val))).sat(val)
    assert raises(ValueError, lambda: minimal_unsatisfiable_subset([[1]], sat))

    clauses = [[-10], [1], [5], [2, 3], [3, 4], [5, 2], [-7], [2], [3],
        [-2, -3, 5], [7, 8, 9, 10], [-8], [-9]]
    res = minimal_unsatisfiable_subset(clauses, sat)
    assert sorted(res) == [[-10], [-9], [-8], [-7], [7, 8, 9, 10]]
    assert not sat(res)

    clauses = [[1, 3], [2, 3], [-1], [4], [3], [-3]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(clauses, sat)
        assert sorted(res) == [[-3], [3]]
        assert not sat(res)

    clauses = [[1], [-1], [2], [-2], [3, 4], [4]]
    for perm in permutations(clauses):
        res = minimal_unsatisfiable_subset(perm, sat)
        assert sorted(res) in [[[-1], [1]], [[-2], [2]]]
        assert not sat(res)
Пример #11
0
    def get_dists(self, specs, sat_only=False):
        log.debug('Beginning the pruning process')

        specs = list(map(MatchSpec, specs))
        active = self.feats.copy()
        len0 = len(specs)
        bad_deps = []
        valid = {}
        unsat = []

        def filter_group(matches, top):
            # If no packages exist with this name, it's a fatal error
            match1 = next(x for x in matches)
            name = match1.name
            group = self.groups.get(name,[])
            if not group:
                bad_deps.append((matches,top))
                return False

            # If we are here, then this dependency is mandatory,
            # so add it to the master list. That way it is still
            # participates in the pruning even if one of its
            # parents is pruned away
            if all(name != ms.name for ms in specs):
                specs.append(MatchSpec(name, parent=str(top)))

            # Prune packages that don't match any of the patterns
            # or which may be missing dependencies
            nold = nnew = 0
            first = False
            notfound = set()
            for fn in group:
                sat = valid.get(fn, None)
                if sat is None:
                    first = sat = valid[fn] = True
                nold += sat
                if sat:
                    if name[-1] == '@':
                        sat = name[:-1] in self.track_features(fn)
                    else:
                        sat = self.match_any(matches, fn)
                if sat:
                    sat = all(any(valid.get(f2, True)
                                  for f2 in self.find_matches(ms))
                              for ms in self.ms_depends(fn) if not ms.optional)
                    if not sat:
                        notfound.update(ms for ms in self.ms_depends(fn) if ms.name not in self.groups)
                nnew += sat
                valid[fn] = sat

            reduced = nnew < nold
            if reduced:
                log.debug('%s: pruned from %d -> %d' % (name, nold, nnew))
                if nnew == 0:
                    if notfound:
                        bad_deps.append((notfound,matches))
                    unsat.extend(matches)
                    return True
            elif not first:
                return False

            # Perform the same filtering steps on any dependencies shared across
            # *all* packages in the group. Even if just one of the packages does
            # not have a particular dependency, it must be ignored in this pass.
            cdeps = defaultdict(list)
            for fn in group:
                if valid[fn]:
                    for m2 in self.ms_depends(fn):
                        cdeps[m2.name].append(m2)
            if top is None:
                top = match1
            cdeps = {mname:set(deps) for mname,deps in iteritems(cdeps) if len(deps)==nnew}
            if cdeps:
                top = top if top else match1
                if sum(filter_group(deps, top) for deps in itervalues(cdeps)):
                    reduced = True
            return reduced

        # Look through all of the non-optional specs (which at this point
        # should include the installed packages) for any features which *might*
        # be installed. Prune away any packages that depend on features other
        # than this subset.
        def prune_features():
            feats = set()
            for ms in specs:
                for fn in self.groups.get(ms.name, []):
                    if valid.get(fn, True):
                        feats.update(self.track_features(fn))
            pruned = False
            for feat in active - feats:
                active.remove(feat)
                for fn in self.groups[feat+'@']:
                    if valid.get(fn,True):
                        valid[fn] = False
                        pruned = True
            for name, group in iteritems(self.groups):
                nold =  npruned = 0
                for fn in group:
                    if valid.get(fn, True):
                        nold += 1
                        if self.features(fn) - feats:
                            valid[fn] = False
                            npruned += 1
                if npruned:
                    pruned = True
                    log.debug('%s: pruned from %d -> %d for missing features'%(name,nold,nold-npruned))
                    if npruned == nold:
                        for ms in specs:
                            if ms.name == name and not ms.optional:
                                bad_deps.append((ms,name+'@'))
            return pruned

        # Initial scan to add tracked features and rule out missing packages
        for feat in self.feats:
            valid[feat + '@'] = False
        for ms in specs:
            if ms.name[-1] == '@':
                feat = ms.name[:-1]
                self.add_feature(feat)
                valid[feat + '@'] = True
            elif not ms.optional:
                if not any(True for _ in self.find_matches(ms)):
                    bad_deps.append(ms)
        if bad_deps:
            raise NoPackagesFound(
                "No packages found in current %s channels matching: %s" %
                (config.subdir, ' '.join(map(str,bad_deps))), bad_deps)

        # Iterate in the filtering process until no more progress is made
        pruned = True
        while pruned:
            pruned = False
            for s in list(specs):
                if not s.optional:
                    pruned += filter_group([s], None)
                    if unsat and sat_only:
                        return False
            pruned += prune_features()
        log.debug('Potential feature set: %r'%(active,))

        # Touch all packages
        touched = {}
        def is_valid(fn, notfound=None):
            val = valid.get(fn)
            if val is None or (notfound and not val):
                valid[fn] = True # ensure cycles terminate
                val = valid[fn] = all(any(is_valid(f2)
                                          for f2 in self.find_matches(ms))
                                      for ms in self.ms_depends(fn))
            if notfound and not val:
                notfound.append(ms)
            return val
        def touch(fn, notfound=None):
            val = touched.get(fn)
            if val is None or (notfound is not None and not val):
                val = touched[fn] = is_valid(fn, notfound)
                if val:
                    for ms in self.ms_depends(fn):
                        for f2 in self.find_matches(ms):
                            touch(f2, notfound)
            return val
        for ms in specs[:len0]:
            notfound = []
            if sum(touch(fn, notfound) for fn in self.find_matches(ms)) == 0 and not ms.optional and notfound:
                bad_deps.extend((notfound,(ms)))

        if bad_deps:
            res = []
            specs = set()
            for spec, src in bad_deps:
                specs.update(spec)
                res.append('  - %s: %s' % ('|'.join(map(str,src)),', '.join(map(str,spec))))
            raise NoPackagesFound('\n'.join([
                    "Could not find some dependencies for one or more packages:"]
                    + res), specs)

        # Throw an error for missing packages or dependencies
        if sat_only and (unsat or bad_deps):
            return False

        # For weak dependency conflicts, generate a hint
        if unsat:
            def mysat(specs):
                self.get_dists(specs, sat_only=True)
            stderrlog.info('\nError: Unsatisfiable package specifications.\nGenerating hint: \n')
            hint = minimal_unsatisfiable_subset(specs, sat=mysat, log=True)
            hint = ['  - %s'%str(x) for x in set(chain(unsat, hint))]
            hint = (['The following specifications were found to be in conflict:'] + hint
                + ['Use "conda info <package>" to see the dependencies for each package.'])
            sys.exit('\n'.join(hint))

        dists = {fn:info for fn,info in iteritems(self.index) if touched.get(fn)}
        return dists, specs
Пример #12
0
    def get_dists(self, specs):
        log.debug('Retrieving packages for: %s' % specs)

        specs, optional, features = self.verify_specs(specs)
        filter = {}
        touched = {}
        snames = set()
        nspecs = set()
        unsat = set()

        def filter_group(matches, chains=None):
            # If we are here, then this dependency is mandatory,
            # so add it to the master list. That way it is still
            # participates in the pruning even if one of its
            # parents is pruned away
            if unsat:
                return False
            match1 = next(ms for ms in matches)
            name = match1.name
            first = name not in snames
            group = self.groups.get(name, [])

            # Prune packages that don't match any of the patterns
            # or which have unsatisfiable dependencies
            nold = 0
            bad_deps = []
            for fkey in group:
                if filter.setdefault(fkey, True):
                    nold += 1
                    sat = self.match_any(matches, fkey)
                    sat = sat and all(any(filter.get(f2, True) for f2 in self.find_matches(ms))
                                      for ms in self.ms_depends(fkey))
                    filter[fkey] = sat
                    if not sat:
                        bad_deps.append(fkey)

            # Build dependency chains if we detect unsatisfiability
            nnew = nold - len(bad_deps)
            reduced = nnew < nold
            if reduced:
                log.debug('%s: pruned from %d -> %d' % (name, nold, nnew))
            if nnew == 0:
                if name in snames:
                    snames.remove(name)
                bad_deps = [fkey for fkey in bad_deps if self.match_any(matches, fkey)]
                matches = [(ms,) for ms in matches]
                chains = [a + b for a in chains for b in matches] if chains else matches
                if bad_deps:
                    dep2 = set()
                    for fkey in bad_deps:
                        for ms in self.ms_depends(fkey):
                            if not any(filter.get(f2, True) for f2 in self.find_matches(ms)):
                                dep2.add(ms)
                    chains = [a + (b,) for a in chains for b in dep2]
                unsat.update(chains)
                return nnew != 0
            if not reduced and not first:
                return False

            # Perform the same filtering steps on any dependencies shared across
            # *all* packages in the group. Even if just one of the packages does
            # not have a particular dependency, it must be ignored in this pass.
            if first:
                snames.add(name)
                if match1 not in specs:
                    nspecs.add(MatchSpec(name))
            cdeps = defaultdict(list)
            for fkey in group:
                if filter[fkey]:
                    for m2 in self.ms_depends(fkey):
                        if m2.name[0] != '@' and not m2.optional:
                            cdeps[m2.name].append(m2)
            cdeps = {mname: set(deps) for mname, deps in iteritems(cdeps) if len(deps) >= nnew}
            if cdeps:
                matches = [(ms,) for ms in matches]
                if chains:
                    matches = [a + b for a in chains for b in matches]
                if sum(filter_group(deps, chains) for deps in itervalues(cdeps)):
                    reduced = True

            return reduced

        # Iterate in the filtering process until no more progress is made
        def full_prune(specs, optional, features):
            self.default_filter(features, filter)
            for ms in optional:
                for fkey in self.groups.get(ms.name, []):
                    if not self.match_fast(ms, fkey):
                        filter[fkey] = False
            feats = set(self.trackers.keys())
            snames.clear()
            specs = slist = list(specs)
            onames = set(s.name for s in specs)
            for iter in range(10):
                first = True
                while sum(filter_group([s]) for s in slist) and not unsat:
                    slist = specs + [MatchSpec(n) for n in snames - onames]
                    first = False
                if unsat:
                    return False
                if first and iter:
                    return True
                touched.clear()
                for fstr in features:
                    touched[fstr+'@'] = True
                for spec in chain(specs, optional):
                    self.touch(spec, touched, filter)
                nfeats = set()
                for fkey, val in iteritems(touched):
                    if val:
                        nfeats.update(self.track_features(fkey))
                if len(nfeats) >= len(feats):
                    return True
                pruned = False
                for feat in feats - nfeats:
                    feats.remove(feat)
                    for fkey in self.trackers[feat]:
                        if filter.get(fkey, True):
                            filter[fkey] = False
                            pruned = True
                if not pruned:
                    return True

        #
        # In the case of a conflict, look for the minimum satisfiable subset
        #

        if not full_prune(specs, optional, features):
            def minsat_prune(specs):
                return full_prune(specs, optional, features)

            save_unsat = set(s for s in unsat if s[0] in specs)
            stderrlog.info('...')
            hint = minimal_unsatisfiable_subset(specs, sat=minsat_prune, log=False)
            save_unsat.update((ms,) for ms in hint)
            raise Unsatisfiable(save_unsat)

        dists = {fkey: self.index[fkey] for fkey, val in iteritems(touched) if val}
        return dists, list(map(MatchSpec, snames - {ms.name for ms in specs}))
Пример #13
0
    def get_dists(self, specs):
        log.debug('Retrieving packages for: %s' % specs)

        specs, optional, features = self.verify_specs(specs)
        filter = {}
        touched = {}
        snames = set()
        nspecs = set()
        unsat = set()

        def filter_group(matches, chains=None):
            # If we are here, then this dependency is mandatory,
            # so add it to the master list. That way it is still
            # participates in the pruning even if one of its
            # parents is pruned away
            if unsat:
                return False
            match1 = next(ms for ms in matches)
            name = match1.name
            first = name not in snames
            group = self.groups.get(name, [])

            # Prune packages that don't match any of the patterns
            # or which have unsatisfiable dependencies
            nold = 0
            bad_deps = []
            for fkey in group:
                if filter.setdefault(fkey, True):
                    nold += 1
                    sat = self.match_any(matches, fkey)
                    sat = sat and all(any(filter.get(f2, True) for f2 in self.find_matches(ms))
                                      for ms in self.ms_depends(fkey))
                    filter[fkey] = sat
                    if not sat:
                        bad_deps.append(fkey)

            # Build dependency chains if we detect unsatisfiability
            nnew = nold - len(bad_deps)
            reduced = nnew < nold
            if reduced:
                log.debug('%s: pruned from %d -> %d' % (name, nold, nnew))
            if nnew == 0:
                if name in snames:
                    snames.remove(name)
                bad_deps = [fkey for fkey in bad_deps if self.match_any(matches, fkey)]
                matches = [(ms,) for ms in matches]
                chains = [a + b for a in chains for b in matches] if chains else matches
                if bad_deps:
                    dep2 = set()
                    for fkey in bad_deps:
                        for ms in self.ms_depends(fkey):
                            if not any(filter.get(f2, True) for f2 in self.find_matches(ms)):
                                dep2.add(ms)
                    chains = [a + (b,) for a in chains for b in dep2]
                unsat.update(chains)
                return nnew != 0
            if not reduced and not first:
                return False

            # Perform the same filtering steps on any dependencies shared across
            # *all* packages in the group. Even if just one of the packages does
            # not have a particular dependency, it must be ignored in this pass.
            if first:
                snames.add(name)
                if match1 not in specs:
                    nspecs.add(MatchSpec(name))
            cdeps = defaultdict(list)
            for fkey in group:
                if filter[fkey]:
                    for m2 in self.ms_depends(fkey):
                        if m2.name[0] != '@' and not m2.optional:
                            cdeps[m2.name].append(m2)
            cdeps = {mname: set(deps) for mname, deps in iteritems(cdeps) if len(deps) >= nnew}
            if cdeps:
                matches = [(ms,) for ms in matches]
                if chains:
                    matches = [a + b for a in chains for b in matches]
                if sum(filter_group(deps, chains) for deps in itervalues(cdeps)):
                    reduced = True

            return reduced

        # Iterate in the filtering process until no more progress is made
        def full_prune(specs, optional, features):
            self.default_filter(features, filter)
            for ms in optional:
                for fkey in self.groups.get(ms.name, []):
                    if not self.match_fast(ms, fkey):
                        filter[fkey] = False
            feats = set(self.trackers.keys())
            snames.clear()
            specs = slist = list(specs)
            onames = set(s.name for s in specs)
            for iter in range(10):
                first = True
                while sum(filter_group([s]) for s in slist) and not unsat:
                    slist = specs + [MatchSpec(n) for n in snames - onames]
                    first = False
                if unsat:
                    return False
                if first and iter:
                    return True
                touched.clear()
                for fstr in features:
                    touched[fstr+'@'] = True
                for spec in chain(specs, optional):
                    self.touch(spec, touched, filter)
                nfeats = set()
                for fkey, val in iteritems(touched):
                    if val:
                        nfeats.update(self.track_features(fkey))
                if len(nfeats) >= len(feats):
                    return True
                pruned = False
                for feat in feats - nfeats:
                    feats.remove(feat)
                    for fkey in self.trackers[feat]:
                        if filter.get(fkey, True):
                            filter[fkey] = False
                            pruned = True
                if not pruned:
                    return True

        #
        # In the case of a conflict, look for the minimum satisfiable subset
        #

        if not full_prune(specs, optional, features):
            def minsat_prune(specs):
                return full_prune(specs, optional, features)

            save_unsat = set(s for s in unsat if s[0] in specs)
            stderrlog.info('...')
            hint = minimal_unsatisfiable_subset(specs, sat=minsat_prune, log=False)
            save_unsat.update((ms,) for ms in hint)
            raise Unsatisfiable(save_unsat)

        dists = {fkey: self.index[fkey] for fkey, val in iteritems(touched) if val}
        return dists, list(map(MatchSpec, snames - {ms.name for ms in specs}))
Пример #14
0
    def get_dists(self, specs, sat_only=False):
        log.debug('Beginning the pruning process')

        specs = list(map(MatchSpec, specs))
        active = self.feats.copy()
        len0 = len(specs)
        bad_deps = []
        valid = {}
        unsat = []

        def filter_group(matches, top):
            # If no packages exist with this name, it's a fatal error
            match1 = next(x for x in matches)
            name = match1.name
            group = self.groups.get(name,[])
            if not group:
                bad_deps.append((matches,top))
                return False

            # If we are here, then this dependency is mandatory,
            # so add it to the master list. That way it is still
            # participates in the pruning even if one of its
            # parents is pruned away
            if all(name != ms.name for ms in specs):
                specs.append(MatchSpec(name, parent=str(top)))

            # Prune packages that don't match any of the patterns
            # or which may be missing dependencies
            nold = nnew = 0
            first = False
            notfound = set()
            for fn in group:
                sat = valid.get(fn, None)
                if sat is None:
                    first = sat = valid[fn] = True
                nold += sat
                if sat:
                    if name[-1] == '@':
                        sat = name[:-1] in self.track_features(fn)
                    else:
                        sat = self.match_any(matches, fn)
                if sat:
                    sat = all(any(valid.get(f2, True)
                                  for f2 in self.find_matches(ms))
                              for ms in self.ms_depends(fn) if not ms.optional)
                    if not sat:
                        notfound.update(ms for ms in self.ms_depends(fn) if ms.name not in self.groups)
                nnew += sat
                valid[fn] = sat

            reduced = nnew < nold
            if reduced:
                log.debug('%s: pruned from %d -> %d' % (name, nold, nnew))
                if nnew == 0:
                    if notfound:
                        bad_deps.append((notfound,matches))
                    unsat.extend(matches)
                    return True
            elif not first:
                return False

            # Perform the same filtering steps on any dependencies shared across
            # *all* packages in the group. Even if just one of the packages does
            # not have a particular dependency, it must be ignored in this pass.
            cdeps = defaultdict(list)
            for fn in group:
                if valid[fn]:
                    for m2 in self.ms_depends(fn):
                        cdeps[m2.name].append(m2)
            if top is None:
                top = match1
            cdeps = {mname:set(deps) for mname,deps in iteritems(cdeps) if len(deps)==nnew}
            if cdeps:
                top = top if top else match1
                if sum(filter_group(deps, top) for deps in itervalues(cdeps)):
                    reduced = True
            return reduced

        # Look through all of the non-optional specs (which at this point
        # should include the installed packages) for any features which *might*
        # be installed. Prune away any packages that depend on features other
        # than this subset.
        def prune_features():
            feats = set()
            for ms in specs:
                for fn in self.groups.get(ms.name, []):
                    if valid.get(fn, True):
                        feats.update(self.track_features(fn))
            pruned = False
            for feat in active - feats:
                active.remove(feat)
                for fn in self.groups[feat+'@']:
                    if valid.get(fn,True):
                        valid[fn] = False
                        pruned = True
            for name, group in iteritems(self.groups):
                nold =  npruned = 0
                for fn in group:
                    if valid.get(fn, True):
                        nold += 1
                        if self.features(fn) - feats:
                            valid[fn] = False
                            npruned += 1
                if npruned:
                    pruned = True
                    log.debug('%s: pruned from %d -> %d for missing features'%(name,nold,nold-npruned))
                    if npruned == nold:
                        for ms in specs:
                            if ms.name == name and not ms.optional:
                                bad_deps.append((ms,name+'@'))
            return pruned

        # Initial scan to add tracked features and rule out missing packages
        for feat in self.feats:
            valid[feat + '@'] = False
        for ms in specs:
            if ms.name[-1] == '@':
                feat = ms.name[:-1]
                self.add_feature(feat)
                valid[feat + '@'] = True
            elif not ms.optional:
                if not any(True for _ in self.find_matches(ms)):
                    bad_deps.append(ms)
        if bad_deps:
            raise NoPackagesFound(
                "No packages found in current %s channels matching: %s" %
                (config.subdir, ' '.join(map(str,bad_deps))), bad_deps)

        # Iterate in the filtering process until no more progress is made
        pruned = True
        while pruned:
            pruned = False
            for s in list(specs):
                if not s.optional:
                    pruned += filter_group([s], None)
                    if unsat and sat_only:
                        return False
            pruned += prune_features()
        log.debug('Potential feature set: %r'%(active,))

        # Touch all packages
        touched = {}
        def is_valid(fn, notfound=None):
            val = valid.get(fn)
            if val is None or (notfound and not val):
                valid[fn] = True # ensure cycles terminate
                val = valid[fn] = all(any(is_valid(f2)
                                          for f2 in self.find_matches(ms))
                                      for ms in self.ms_depends(fn))
            if notfound and not val:
                notfound.append(ms)
            return val
        def touch(fn, notfound=None):
            val = touched.get(fn)
            if val is None or (notfound is not None and not val):
                val = touched[fn] = is_valid(fn, notfound)
                if val:
                    for ms in self.ms_depends(fn):
                        for f2 in self.find_matches(ms):
                            touch(f2, notfound)
            return val
        for ms in specs[:len0]:
            notfound = []
            if sum(touch(fn, notfound) for fn in self.find_matches(ms)) == 0 and not ms.optional and notfound:
                bad_deps.extend((notfound,(ms)))

        if bad_deps:
            res = []
            specs = set()
            for spec, src in bad_deps:
                specs.update(spec)
                res.append('  - %s: %s' % ('|'.join(map(str,src)),', '.join(map(str,spec))))
            raise NoPackagesFound('\n'.join([
                    "Could not find some dependencies for one or more packages:"]
                    + res), specs)

        # Throw an error for missing packages or dependencies
        if sat_only and (unsat or bad_deps):
            return False

        # For weak dependency conflicts, generate a hint
        if unsat:
            def mysat(specs):
                self.get_dists(specs, sat_only=True)
            stderrlog.info('\nError: Unsatisfiable package specifications.\nGenerating hint: \n')
            hint = minimal_unsatisfiable_subset(specs, sat=mysat, log=True)
            hint = ['  - %s'%str(x) for x in set(chain(unsat, hint))]
            hint = (['The following specifications were found to be in conflict:'] + hint
                + ['Use "conda info <package>" to see the dependencies for each package.'])
            sys.exit('\n'.join(hint))

        dists = {fn:info for fn,info in iteritems(self.index) if touched.get(fn)}
        return dists, specs
Пример #15
0
    def solve(self, specs, len0=None, returnall=False):
        try:
            stdoutlog.info("Solving package specifications ...")
            dotlog.debug("Solving for %s" % (specs,))

            # Find the compliant packages
            specs = list(map(MatchSpec, specs))
            if len0 is None:
                len0 = len(specs)
            dists, new_specs, unsat = self.get_dists(specs, True)
            if not dists and not unsat:
                return False if dists is None else ([[]] if returnall else [])

            # Check if satisfiable
            dotlog.debug('Checking satisfiability')
            r2 = Resolve(dists, True, True)
            C = r2.gen_clauses()
            constraints = r2.generate_spec_constraints(C, specs)
            solution = C.sat(constraints, True)
            if not solution:
                # Generate hint. First we determine a minimum unsat subset. This is the
                # smallest set of specs that conflict with each other
                def mysat(specs):
                    constraints = r2.generate_spec_constraints(C, specs)
                    res = C.sat(constraints, False) is not None
                    return res
                stdoutlog.info("\nUnsatisfiable specifications detected; generating hint ...")
                hint = minimal_unsatisfiable_subset(specs, sat=mysat, log=False)
                # Find dependency chains that establish invalidity
                hnames = set(h.name for h in hint)
                filter = {}
                for ms2 in hint:
                    for fkey in r2.find_matches(ms2.name):
                        if not self.match_fast(ms2, fkey):
                            filter[fkey] = False
                if unsat:
                    hnames.add(unsat)
                    for fkey in r2.find_matches(unsat):
                        filter[fkey] = False
                chains = []
                for ms in hint:
                    # This ensures the chains see the same filter; produces more hints
                    res = self.invalid_chains(ms, filter.copy())
                    # Only consider chains that end in one of the packages we care about
                    res = [r for r in res if str(r[-1]).split(' ', 1)[0] in hnames]
                    chains.extend(res or [(ms.spec,)])
                raise Unsatisfiable(chains)

            speco = []  # optional packages
            specr = []  # requested packages
            speca = []  # all other packages
            specm = set(r2.groups)  # missing from specs
            for k, s in enumerate(chain(specs, new_specs)):
                if s.name in specm:
                    specm.remove(s.name)
                if not s.optional:
                    (specr if k < len0 else speca).append(s)
                elif any(r2.find_matches(s)):
                    s = MatchSpec(s.name, optional=True, target=s.target)
                    speco.append(s)
                    speca.append(s)
            speca.extend(MatchSpec(s) for s in specm)

            # Removed packages: minimize count
            eq_optional_c = r2.generate_removal_count(C, speco)
            solution, obj7 = C.minimize(eq_optional_c, solution)
            dotlog.debug('Package removal metric: %d' % obj7)

            # Requested packages: maximize versions, then builds
            eq_req_v, eq_req_b = r2.generate_version_metrics(C, specr)
            solution, obj3 = C.minimize(eq_req_v, solution)
            solution, obj4 = C.minimize(eq_req_b, solution)
            dotlog.debug('Initial package version/build metrics: %d/%d' % (obj3, obj4))

            # Track features: minimize feature count
            eq_feature_count = r2.generate_feature_count(C)
            solution, obj1 = C.minimize(eq_feature_count, solution)
            dotlog.debug('Track feature count: %d' % obj1)

            # Featured packages: maximize featured package count
            eq_feature_metric, ftotal = r2.generate_feature_metric(C)
            solution, obj2 = C.minimize(eq_feature_metric, solution)
            obj2 = ftotal - obj2
            dotlog.debug('Package feature count: %d' % obj2)

            # Remaining packages: maximize versions, then builds, then count
            eq_v, eq_b = r2.generate_version_metrics(C, speca)
            solution, obj5 = C.minimize(eq_v, solution)
            solution, obj6 = C.minimize(eq_b, solution)
            dotlog.debug('Additional package version/build metrics: %d/%d' % (obj5, obj6))

            # Prune unnecessary packages
            eq_c = r2.generate_package_count(C, specm)
            solution, obj7 = C.minimize(eq_c, solution, trymax=True)
            dotlog.debug('Weak dependency count: %d' % obj7)

            def clean(sol):
                return [q for q in (C.from_index(s) for s in sol)
                        if q and q[0] != '!' and '@' not in q]
            dotlog.debug('Looking for alternate solutions')
            nsol = 1
            psolutions = []
            psolution = clean(solution)
            psolutions.append(psolution)
            while True:
                nclause = tuple(C.Not(C.from_name(q)) for q in psolution)
                solution = C.sat((nclause,), True)
                if solution is None:
                    break
                nsol += 1
                if nsol > 10:
                    dotlog.debug('Too many solutions; terminating')
                    break
                psolution = clean(solution)
                psolutions.append(psolution)

            if nsol > 1:
                psols2 = list(map(set, psolutions))
                common = set.intersection(*psols2)
                diffs = [sorted(set(sol) - common) for sol in psols2]
                stdoutlog.info(
                    '\nWarning: %s possible package resolutions '
                    '(only showing differing packages):%s%s' %
                    ('>10' if nsol > 10 else nsol,
                     dashlist(', '.join(diff) for diff in diffs),
                     '\n  ... and others' if nsol > 10 else ''))

            def stripfeat(sol):
                return sol.split('[')[0]
            stdoutlog.info('\n')
            if returnall:
                return [sorted(map(stripfeat, psol)) for psol in psolutions]
            else:
                return sorted(map(stripfeat, psolutions[0]))
        except:
            stdoutlog.info('\n')
            raise