From: Michael Schroeder Date: Wed, 5 Mar 2014 14:33:56 +0000 (+0100) Subject: add support for complex dependencies X-Git-Tag: upstream/0.6.4~73 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e67081ca53d718a041070da8b5fc92b7cec796e3;p=platform%2Fupstream%2Flibsolv.git add support for complex dependencies --- diff --git a/CMakeLists.txt b/CMakeLists.txt index c1f9fc5..816c333 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,7 +206,7 @@ FOREACH (VAR HAVE_STRCHRNUL HAVE_FOPENCOOKIE HAVE_FUNOPEN WORDS_BIGENDIAN ENABLE_RPMDB ENABLE_PUBKEY ENABLE_RPMMD ENABLE_RPMDB_BYRPMHEADER ENABLE_SUSEREPO ENABLE_COMPS ENABLE_HELIXREPO ENABLE_MDKREPO ENABLE_ARCHREPO ENABLE_DEBIAN ENABLE_HAIKU ENABLE_LZMA_COMPRESSION ENABLE_BZIP2_COMPRESSION ENABLE_PGPVRFY ENABLE_APPDATA - ENABLE_LINKED_PKGS) + ENABLE_LINKED_PKGS ENABLE_COMPLEX_DEPS) IF(${VAR}) ADD_DEFINITIONS (-D${VAR}=1) SET (SWIG_FLAGS ${SWIG_FLAGS} -D${VAR}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c917c7e..f6542ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,7 @@ SET (libsolv_SRCS bitmap.c poolarch.c poolvendor.c poolid.c strpool.c dirpool.c solver.c solverdebug.c repo_solv.c repo_write.c evr.c pool.c queue.c repo.c repodata.c repopage.c util.c policy.c solvable.c - transaction.c rules.c problems.c linkedpkg.c + transaction.c rules.c problems.c linkedpkg.c cplxdeps.c chksum.c md5.c sha1.c sha2.c solvversion.c selection.c) SET (libsolv_HEADERS diff --git a/src/cplxdeps.c b/src/cplxdeps.c new file mode 100644 index 0000000..edb7cf8 --- /dev/null +++ b/src/cplxdeps.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2014, Novell Inc. + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +/* + * cplxdeps.c + * + * normalize complex dependencies into CNF/DNF form + */ + +#include +#include +#include +#include +#include + +#include "pool.h" +#include "cplxdeps.h" + +#ifdef ENABLE_COMPLEX_DEPS + +#undef CPLXDEBUG + +int +pool_is_complex_dep_rd(Pool *pool, Reldep *rd) +{ + for (;;) + { + if (rd->flags == REL_AND || rd->flags == REL_COND) /* those two are the complex ones */ + return 1; + if (rd->flags != REL_OR) + return 0; + if (ISRELDEP(rd->name) && pool_is_complex_dep_rd(pool, GETRELDEP(pool, rd->name))) + return 1; + if (!ISRELDEP(rd->evr)) + return 0; + rd = GETRELDEP(pool, rd->evr); + } +} + +/* expand simple dependencies into package lists */ +static int +expand_simpledeps(Pool *pool, Queue *bq, int start, int split) +{ + int end = bq->count; + int i, x; + int newsplit = 0; + for (i = start; i < end; i++) + { + if (i == split) + newsplit = bq->count - (end - start); + x = bq->elements[i]; + if (x == pool->nsolvables) + { + Id *dp = pool->whatprovidesdata + bq->elements[++i]; + for (; *dp; dp++) + queue_push(bq, *dp); + } + else + queue_push(bq, x); + } + if (i == split) + newsplit = bq->count - (end - start); + queue_deleten(bq, start, end - start); + return newsplit; +} + +/* invert all literals in the blocks. note that this also turns DNF into CNF and vice versa */ +static void +invert_depblocks(Pool *pool, Queue *bq, int start) +{ + int i, j, end; + expand_simpledeps(pool, bq, start, 0); + end = bq->count; + for (i = j = start; i < end; i++) + { + if (bq->elements[i]) + { + bq->elements[i] = -bq->elements[i]; + continue; + } + /* end of block reached, reorder */ + if (i - 1 > j) + { + int k; + for (k = i - 1; j < k; j++, k--) + { + Id t = bq->elements[j]; + bq->elements[j] = bq->elements[k]; + bq->elements[k] = t; + } + } + j = i + 1; + } +} + +/* + * returns: + * 0: no blocks + * 1: matches all + * -1: at least one block + */ +static int +normalize_dep(Pool *pool, Id dep, Queue *bq, int todnf) +{ + int bqcnt = bq->count; + int bqcnt2; + Id dp; + +#ifdef CPLXDEBUG + printf("normalize_dep %s todnf:%d\n", pool_dep2str(pool, dep), todnf); +#endif + if (pool_is_complex_dep(pool, dep)) + { + Reldep *rd = GETRELDEP(pool, dep); + if (rd->flags == REL_AND || rd->flags == REL_OR || rd->flags == REL_COND) + { + int flags = rd->flags; + int r, mode; + + /* in inverted mode, COND means AND. otherwise it means OR NOT */ + if (flags == REL_COND && todnf) + flags = REL_AND; + mode = flags == REL_AND ? 0 : 1; + + /* get blocks of first argument */ + r = normalize_dep(pool, rd->name, bq, todnf); + if (r == 0) + { + if (flags == REL_AND) + return 0; + if (flags == REL_COND) + { + r = normalize_dep(pool, rd->evr, bq, todnf ^ 1); + if (r == 0 || r == 1) + return r == 0 ? 1 : 0; + invert_depblocks(pool, bq, bqcnt); /* invert block for COND */ + return r; + } + return normalize_dep(pool, rd->evr, bq, todnf); + } + if (r == 1) + { + if (flags != REL_AND) + return 1; + return normalize_dep(pool, rd->evr, bq, todnf); + } + + /* get blocks of second argument */ + bqcnt2 = bq->count; + /* COND is OR with NEG on evr block, so we invert the todnf flag in that case*/ + r = normalize_dep(pool, rd->evr, bq, flags == REL_COND ? todnf ^ 1 : todnf); + if (r == 0) + { + if (flags == REL_OR) + return -1; + queue_truncate(bq, bqcnt); + return flags == REL_COND ? 1 : 0; + } + if (r == 1) + { + if (flags == REL_OR) + { + queue_truncate(bq, bqcnt); + return 1; + } + return -1; + } + if (flags == REL_COND) + invert_depblocks(pool, bq, bqcnt2); /* invert 2nd block */ + if (mode == todnf) + { + /* simple case: just join em. nothing more to do here. */ +#ifdef CPLXDEBUG + printf("SIMPLE JOIN %d %d %d\n", bqcnt, bqcnt2, bq->count); +#endif + return -1; + } + else + { + /* complex case: mix em */ + int i, j, bqcnt3; +#ifdef CPLXDEBUG + printf("COMPLEX JOIN %d %d %d\n", bqcnt, bqcnt2, bq->count); +#endif + bqcnt2 = expand_simpledeps(pool, bq, bqcnt, bqcnt2); + bqcnt3 = bq->count; + for (i = bqcnt; i < bqcnt2; i++) + { + for (j = bqcnt2; j < bqcnt3; j++) + { + int a, b; + int bqcnt4 = bq->count; + int k = i; + + /* mix i block with j block, both blocks are sorted */ + while (bq->elements[k] && bq->elements[j]) + { + if (bq->elements[k] < bq->elements[j]) + queue_push(bq, bq->elements[k++]); + else + { + if (bq->elements[k] == bq->elements[j]) + k++; + queue_push(bq, bq->elements[j++]); + } + } + while (bq->elements[j]) + queue_push(bq, bq->elements[j++]); + while (bq->elements[k]) + queue_push(bq, bq->elements[k++]); + + /* block is finished, check for A + -A */ + for (a = bqcnt4, b = bq->count - 1; a < b; ) + { + if (-bq->elements[a] == bq->elements[b]) + break; + if (-bq->elements[a] > bq->elements[b]) + a++; + else + b--; + } + if (a < b) + queue_truncate(bq, bqcnt4); /* ignore this block */ + else + queue_push(bq, 0); /* finish block */ + } + /* advance to next block */ + while (bq->elements[i]) + i++; + } + i = -1; + if (bqcnt3 == bq->count) /* ignored all blocks? */ + i = todnf ? 0 : 1; + queue_deleten(bq, bqcnt, bqcnt3 - bqcnt); + return i; + } + } + } + + /* fallback case: just use package list */ + dp = pool_whatprovides(pool, dep); + if (dp <= 2 || !pool->whatprovidesdata[dp]) + return dp == 2 ? 1 : 0; + if (todnf) + { + for (; pool->whatprovidesdata[dp]; dp++) + queue_push2(bq, pool->whatprovidesdata[dp], 0); + } + else + { + queue_push2(bq, pool->nsolvables, dp); /* not yet expanded marker */ + queue_push(bq, 0); + } + return -1; +} + +int +pool_normalize_complex_dep(Pool *pool, Id dep, Queue *bq, int flags) +{ + int i, bqcnt = bq->count; + + i = normalize_dep(pool, dep, bq, (flags & CPLXDEPS_TODNF) ? 1 : 0); + if ((flags & CPLXDEPS_EXPAND) != 0) + { + if (i != 0 && i != 1) + expand_simpledeps(pool, bq, bqcnt, 0); + } + if ((flags & CPLXDEPS_INVERT) != 0) + { + if (i == 0 || i == 1) + i ^= 1; + else + invert_depblocks(pool, bq, bqcnt); + } +#ifdef CPLXDEBUG + if (i == 0) + printf("NONE\n"); + else if (i == 1) + printf("ALL\n"); + else + { + for (i = bqcnt; i < bq->count; i++) + { + if (bq->elements[i] == pool->nsolvables) + { + Id *dp = pool->whatprovidesdata + bq->elements[++i]; + printf(" ("); + while (*dp) + printf(" %s", pool_solvid2str(pool, *dp++)); + printf(" )"); + } + else if (bq->elements[i] > 0) + printf(" %s", pool_solvid2str(pool, bq->elements[i])); + else if (bq->elements[i] < 0) + printf(" -%s", pool_solvid2str(pool, -bq->elements[i])); + else + printf(" ||"); + } + printf("\n"); + i = -1; + } +#endif + return i; +} + +#endif /* ENABLE_COMPLEX_DEPS */ + diff --git a/src/cplxdeps.h b/src/cplxdeps.h new file mode 100644 index 0000000..101d7ad --- /dev/null +++ b/src/cplxdeps.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2014, Novell Inc. + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +/* + * cplxdeps.h (internal) + */ + +#ifndef LIBSOLV_CPLXDEPS_H +#define LIBSOLV_CPLXDEPS_H + +extern int pool_is_complex_dep_rd(Pool *pool, Reldep *rd); + +static inline int +pool_is_complex_dep(Pool *pool, Id dep) +{ + if (ISRELDEP(dep)) + { + Reldep *rd = GETRELDEP(pool, dep); + if (rd->flags >= 8 && pool_is_complex_dep_rd(pool, rd)) + return 1; + } + return 0; +} + +extern int pool_normalize_complex_dep(Pool *pool, Id dep, Queue *bq, int flags); + +#define CPLXDEPS_TODNF (1 << 0) +#define CPLXDEPS_EXPAND (1 << 1) +#define CPLXDEPS_INVERT (1 << 2) + +#endif + diff --git a/src/rules.c b/src/rules.c index dad682f..9139812 100644 --- a/src/rules.c +++ b/src/rules.c @@ -27,6 +27,7 @@ #include "policy.h" #include "solverdebug.h" #include "linkedpkg.h" +#include "cplxdeps.h" #define RULES_BLOCK 63 @@ -532,6 +533,165 @@ add_package_link(Solver *solv, Solvable *s, Map *m, Queue *workq) #endif +#ifdef ENABLE_COMPLEX_DEPS + +static void +add_complex_deprules(Solver *solv, Id p, Id dep, int type, int dontfix, Queue *workq, Map *m) +{ + Pool *pool = solv->pool; + Repo *installed = solv->installed; + int i, j; + Queue bq; + + queue_init(&bq); + + /* CNF expansion for requires, DNF + INVERT expansion for conflicts */ + i = pool_normalize_complex_dep(pool, dep, &bq, type == SOLVER_RULE_RPM_PACKAGE_REQUIRES ? 0 : (CPLXDEPS_TODNF | CPLXDEPS_EXPAND | CPLXDEPS_INVERT)); + /* handle special cases */ + if (i == 0) + { + if (dontfix) + { + POOL_DEBUG(SOLV_DEBUG_RULE_CREATION, "ignoring broken dependency %s of installed package %s\n", pool_dep2str(pool, dep), pool_solvid2str(pool, p)); + } + else + { + POOL_DEBUG(SOLV_DEBUG_RULE_CREATION, "package %s [%d] is not installable (%s)\n", pool_solvid2str(pool, p), p, pool_dep2str(pool, dep)); + addrpmrule(solv, -p, 0, type == SOLVER_RULE_RPM_PACKAGE_REQUIRES ? SOLVER_RULE_RPM_NOTHING_PROVIDES_DEP : type, dep); + } + queue_free(&bq); + return; + } + if (i == 1) + { + queue_free(&bq); + return; + } + + /* go through all blocks and add a rule for each block */ + for (i = 0; i < bq.count; i++) + { + if (!bq.elements[i]) + continue; /* huh? */ + if (bq.elements[i] == pool->nsolvables) + { + /* conventional requires (cannot be a conflicts as they have been expanded) */ + Id *dp = pool->whatprovidesdata + bq.elements[i + 1]; + i += 2; + if (dontfix) + { + for (j = 0; dp[j] != 0; j++) + if (pool->solvables[dp[j]].repo == installed) + break; /* provider was installed */ + if (!dp[j]) + { + POOL_DEBUG(SOLV_DEBUG_RULE_CREATION, "ignoring broken requires %s of installed package %s\n", pool_dep2str(pool, dep), pool_solvid2str(pool, p)); + continue; + } + } + if (!*dp) + { + /* nothing provides req! */ + POOL_DEBUG(SOLV_DEBUG_RULE_CREATION, "package %s [%d] is not installable (%s)\n", pool_solvid2str(pool, p), p, pool_dep2str(pool, dep)); + addrpmrule(solv, -p, 0, SOLVER_RULE_RPM_NOTHING_PROVIDES_DEP, dep); + continue; + } + addrpmrule(solv, -p, dp - pool->whatprovidesdata, SOLVER_RULE_RPM_PACKAGE_REQUIRES, dep); + /* push all non-visited providers on the work queue */ + if (m) + for (; *dp; dp++) + if (!MAPTST(m, *dp)) + queue_push(workq, *dp); + continue; + } + if (!bq.elements[i + 1]) + { + Id p2 = bq.elements[i]; + /* simple rule with just two literals */ + if (dontfix && p2 < 0 && pool->solvables[-p2].repo == installed) + continue; + if (dontfix && p2 > 0 && pool->solvables[p2].repo != installed) + continue; + if (p == p2) + continue; + if (-p == p2) + { + if (type == SOLVER_RULE_RPM_PACKAGE_CONFLICT) + { + if (pool->forbidselfconflicts && !is_otherproviders_dep(pool, dep)) + addrpmrule(solv, -p, 0, SOLVER_RULE_RPM_SELF_CONFLICT, dep); + continue; + } + addrpmrule(solv, -p, 0, type, dep); + continue; + } + if (p2 > 0) + addrpmrule(solv, p2, -p, type, dep); /* hack so that we don't need pool_queuetowhatprovides */ + else + addrpmrule(solv, -p, p2, type, dep); + if (m && p2 > 0 && !MAPTST(m, p2)) + queue_push(workq, p2); + } + else + { + Id *qele; + int qcnt; + + qele = bq.elements + i; + qcnt = i; + while (bq.elements[i]) + i++; + qcnt = i - qcnt; + if (dontfix) + { + for (j = 0; j < qcnt; j++) + { + if (qele[j] > 0 && pool->solvables[qele[j]].repo == installed) + break; + if (qele[j] < 0 && pool->solvables[-qele[j]].repo != installed) + break; + } + if (j == qcnt) + continue; + } + /* add -p to (ordered) rule (overwriting the trailing zero) */ + for (j = 0; ; j++) + { + if (j == qcnt || qele[j] > -p) + { + if (j < qcnt) + memmove(qele + j + 1, qele + j, (qcnt - j) * sizeof(Id)); + qele[j] = -p; + qcnt++; + break; + } + if (qele[j] == -p) + break; + } + /* check if the rule contains both p and -p */ + for (j = 0; j < qcnt; j++) + if (qele[j] == p) + break; + if (j == qcnt) + { + /* hack: create fake queue 'q' so that we can call pool_queuetowhatprovides */ + Queue q; + memset(&q, 0, sizeof(q)); + q.count = qcnt - 1; + q.elements = qele + 1; + addrpmrule(solv, qele[0], pool_queuetowhatprovides(pool, &q), type, dep); + if (m) + for (j = 0; j < qcnt; j++) + if (qele[j] > 0 && !MAPTST(m, qele[j])) + queue_push(workq, qele[j]); + } + } + } + queue_free(&bq); +} + +#endif + /*------------------------------------------------------------------- * * add (install) rules for solvable @@ -631,6 +791,15 @@ solver_addrpmrulesforsolvable(Solver *solv, Solvable *s, Map *m) if (req == SOLVABLE_PREREQMARKER) /* skip the marker */ continue; +#ifdef ENABLE_COMPLEX_DEPS + if (pool_is_complex_dep(pool, req)) + { + /* we have AND/COND deps, normalize */ + add_complex_deprules(solv, n, req, SOLVER_RULE_RPM_PACKAGE_REQUIRES, dontfix, &workq, m); + continue; + } +#endif + /* find list of solvables providing 'req' */ dp = pool_whatprovides_ptr(pool, req); @@ -704,6 +873,14 @@ solver_addrpmrulesforsolvable(Solver *solv, Solvable *s, Map *m) /* foreach conflicts of 's' */ while ((con = *conp++) != 0) { +#ifdef ENABLE_COMPLEX_DEPS + if (!ispatch && pool_is_complex_dep(pool, con)) + { + /* we have AND/COND deps, normalize */ + add_complex_deprules(solv, n, con, SOLVER_RULE_RPM_PACKAGE_CONFLICT, dontfix, &workq, m); + continue; + } +#endif /* foreach providers of a conflict of 's' */ FOR_PROVIDES(p, pp, con) { diff --git a/src/solver.c b/src/solver.c index 4ffdec4..8ca9efe 100644 --- a/src/solver.c +++ b/src/solver.c @@ -25,6 +25,7 @@ #include "policy.h" #include "poolarch.h" #include "solverdebug.h" +#include "cplxdeps.h" #define RULES_BLOCK 63 @@ -1750,6 +1751,132 @@ prune_to_update_targets(Solver *solv, Id *cp, Queue *q) queue_truncate(q, j); } +#ifdef ENABLE_COMPLEX_DEPS + +static void +add_complex_recommends(Solver *solv, Id rec, Queue *dq, Map *dqmap) +{ + Pool *pool = solv->pool; + int oldcnt = dq->count; + int cutcnt, blkcnt; + Id p; + int i, j; + + printf("ADD_COMPLEX_RECOMMENDS %s\n", pool_dep2str(pool, rec)); + i = pool_normalize_complex_dep(pool, rec, dq, CPLXDEPS_EXPAND); + if (i == 0 || i == 1) + return; + cutcnt = dq->count; + for (i = oldcnt; i < cutcnt; i++) + { + blkcnt = dq->count; + for (; (p = dq->elements[i]) != 0; i++) + { + if (p < 0) + { + if (solv->decisionmap[-p] <= 0) + break; + continue; + } + if (solv->decisionmap[p] > 0) + { + queue_truncate(dq, blkcnt); + break; + } + if (dqmap) + { + if (!MAPTST(dqmap, p)) + continue; + } + else + { + if (solv->decisionmap[p] < 0) + continue; + if (solv->dupmap_all && solv->installed && pool->solvables[p].repo == solv->installed && (solv->droporphanedmap_all || (solv->droporphanedmap.size && MAPTST(&solv->droporphanedmap, p - solv->installed->start)))) + continue; + } + queue_push(dq, p); + } + while (dq->elements[i]) + i++; + } + queue_deleten(dq, oldcnt, cutcnt - oldcnt); + /* unify */ + if (dq->count != oldcnt) + { + for (j = oldcnt; j < dq->count; j++) + { + p = dq->elements[j]; + for (i = 0; i < j; i++) + if (dq->elements[i] == p) + { + dq->elements[j] = 0; + break; + } + } + for (i = j = oldcnt; j < dq->count; j++) + if (dq->elements[j]) + dq->elements[i++] = dq->elements[j]; + queue_truncate(dq, i); + } + printf("RETURN:\n"); + for (i = oldcnt; i < dq->count; i++) + printf(" - %s\n", pool_solvid2str(pool, dq->elements[i])); +} + +static void +do_complex_recommendations(Solver *solv, Id rec, Map *m, int noselected) +{ + Pool *pool = solv->pool; + Queue dq; + Id p; + int i, blk; + + printf("DO_COMPLEX_RECOMMENDATIONS %s\n", pool_dep2str(pool, rec)); + queue_init(&dq); + i = pool_normalize_complex_dep(pool, rec, &dq, CPLXDEPS_EXPAND); + if (i == 0 || i == 1) + { + queue_free(&dq); + return; + } + for (i = 0; i < dq.count; i++) + { + blk = i; + for (; (p = dq.elements[i]) != 0; i++) + { + if (p < 0) + { + if (solv->decisionmap[-p] <= 0) + break; + continue; + } + if (solv->decisionmap[p] > 0) + { + if (noselected) + break; + MAPSET(m, p); + for (i++; (p = dq.elements[i]) != 0; i++) + if (p > 0 && solv->decisionmap[p] > 0) + MAPSET(m, p); + p = 1; + break; + } + } + if (!p) + { + for (i = blk; (p = dq.elements[i]) != 0; i++) + if (p > 0) + MAPSET(m, p); + } + while (dq.elements[i]) + i++; + } + queue_free(&dq); +} + +#endif + /*------------------------------------------------------------------- * * solver_run_sat @@ -2205,6 +2332,13 @@ solver_run_sat(Solver *solv, int disablerules, int doweak) recp = s->repo->idarraydata + s->recommends; while ((rec = *recp++) != 0) { +#ifdef ENABLE_COMPLEX_DEPS + if (pool_is_complex_dep(pool, rec)) + { + add_complex_recommends(solv, rec, &dq, 0); + continue; + } +#endif qcount = dq.count; FOR_PROVIDES(p, pp, rec) { @@ -2408,6 +2542,11 @@ solver_run_sat(Solver *solv, int disablerules, int doweak) while ((rec = *recp++) != 0) { queue_empty(&dq); +#ifdef ENABLE_COMPLEX_DEPS + if (pool_is_complex_dep(pool, rec)) + add_complex_recommends(solv, rec, &dq, &dqmap); + else +#endif FOR_PROVIDES(p, pp, rec) { if (solv->decisionmap[p] > 0) @@ -2416,7 +2555,7 @@ solver_run_sat(Solver *solv, int disablerules, int doweak) break; } else if (solv->decisionmap[p] == 0 && MAPTST(&dqmap, p)) - queue_pushunique(&dq, p); + queue_push(&dq, p); } if (!dq.count) continue; @@ -3856,6 +3995,13 @@ void solver_get_recommendations(Solver *solv, Queue *recommendationsq, Queue *su recp = s->repo->idarraydata + s->recommends; while ((rec = *recp++) != 0) { +#ifdef ENABLE_COMPLEX_DEPS + if (pool_is_complex_dep(pool, rec)) + { + do_complex_recommendations(solv, rec, &solv->recommendsmap, noselected); + continue; + } +#endif FOR_PROVIDES(p, pp, rec) if (solv->decisionmap[p] > 0) break; @@ -3921,6 +4067,13 @@ void solver_get_recommendations(Solver *solv, Queue *recommendationsq, Queue *su sugp = s->repo->idarraydata + s->suggests; while ((sug = *sugp++) != 0) { +#ifdef ENABLE_COMPLEX_DEPS + if (pool_is_complex_dep(pool, sug)) + { + do_complex_recommendations(solv, sug, &solv->suggestsmap, noselected); + continue; + } +#endif FOR_PROVIDES(p, pp, sug) if (solv->decisionmap[p] > 0) break;