From: Michael Schroeder Date: Tue, 3 May 2016 14:22:06 +0000 (+0200) Subject: Implement SOLVER_FAVOR and SOLVER_DISFAVOR X-Git-Tag: 0.6.21~16 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8ca4202f1ed33564181b24990ab751de160695c0;p=thirdparty%2Flibsolv.git Implement SOLVER_FAVOR and SOLVER_DISFAVOR --- diff --git a/bindings/solv.i b/bindings/solv.i index 53129004..b21fbbca 100644 --- a/bindings/solv.i +++ b/bindings/solv.i @@ -1172,6 +1172,8 @@ typedef struct { static const Id SOLVER_DROP_ORPHANED = SOLVER_DROP_ORPHANED; static const Id SOLVER_USERINSTALLED = SOLVER_USERINSTALLED; static const Id SOLVER_ALLOWUNINSTALL = SOLVER_ALLOWUNINSTALL; + static const Id SOLVER_FAVOR = SOLVER_FAVOR; + static const Id SOLVER_DISFAVOR = SOLVER_DISFAVOR; static const Id SOLVER_JOBMASK = SOLVER_JOBMASK; static const Id SOLVER_WEAK = SOLVER_WEAK; static const Id SOLVER_ESSENTIAL = SOLVER_ESSENTIAL; diff --git a/doc/libsolv-bindings.3 b/doc/libsolv-bindings.3 index 14bf96c4..6b57ebfb 100644 --- a/doc/libsolv-bindings.3 +++ b/doc/libsolv-bindings.3 @@ -1,13 +1,13 @@ '\" t .\" Title: Libsolv-Bindings .\" Author: [see the "Author" section] -.\" Generator: DocBook XSL Stylesheets v1.78.0 -.\" Date: 12/14/2015 +.\" Generator: DocBook XSL Stylesheets v1.78.1 +.\" Date: 05/03/2016 .\" Manual: LIBSOLV .\" Source: libsolv .\" Language: English .\" -.TH "LIBSOLV\-BINDINGS" "3" "12/14/2015" "libsolv" "LIBSOLV" +.TH "LIBSOLV\-BINDINGS" "3" "05/03/2016" "libsolv" "LIBSOLV" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -3256,6 +3256,16 @@ The matching installed packages are considered to be installed by a user, thus n Allow the solver to deinstall the matching installed packages if they get into the way of resolving a dependency\&. This is like the SOLVER_FLAG_ALLOW_UNINSTALL flag, but limited to a specific set of packages\&. .RE .PP +\fBSOLVER_FAVOR\fR +.RS 4 +Prefer the specified packages if the solver encounters an alternative\&. If a job contains multiple matching favor/disfavor elements, the last one takes precedence\&. +.RE +.PP +\fBSOLVER_DISFAVOR\fR +.RS 4 +Avoid the specified packages if the solver encounters an alternative\&. This can also be used to block recommended or supplemented packages from being installed\&. +.RE +.PP \fBSOLVER_JOBMASK\fR .RS 4 A mask containing all the above action bits\&. diff --git a/doc/libsolv-bindings.txt b/doc/libsolv-bindings.txt index 1ee699dd..79f4c2cf 100644 --- a/doc/libsolv-bindings.txt +++ b/doc/libsolv-bindings.txt @@ -1878,6 +1878,16 @@ Allow the solver to deinstall the matching installed packages if they get into the way of resolving a dependency. This is like the SOLVER_FLAG_ALLOW_UNINSTALL flag, but limited to a specific set of packages. +*SOLVER_FAVOR*:: +Prefer the specified packages if the solver encounters an alternative. If +a job contains multiple matching favor/disfavor elements, the last one takes +precedence. + +*SOLVER_DISFAVOR*:: +Avoid the specified packages if the solver encounters an alternative. This +can also be used to block recommended or supplemented packages from being +installed. + *SOLVER_JOBMASK*:: A mask containing all the above action bits. diff --git a/ext/testcase.c b/ext/testcase.c index a138e6b5..16c7a187 100644 --- a/ext/testcase.c +++ b/ext/testcase.c @@ -46,6 +46,8 @@ static struct job2str { { SOLVER_DROP_ORPHANED, "droporphaned" }, { SOLVER_USERINSTALLED, "userinstalled" }, { SOLVER_ALLOWUNINSTALL, "allowuninstall" }, + { SOLVER_FAVOR, "favor" }, + { SOLVER_DISFAVOR, "disfavor" }, { 0, 0 } }; diff --git a/src/policy.c b/src/policy.c index 12ad771a..528c8c98 100644 --- a/src/policy.c +++ b/src/policy.c @@ -453,6 +453,89 @@ prefer_suggested(Solver *solv, Queue *plist) } } +static int +sort_by_favorq_cmp(const void *ap, const void *bp, void *dp) +{ + const Id *a = ap, *b = bp, *d = dp; + return d[b[0]] - d[a[0]]; +} + +static void +sort_by_favorq(Queue *favorq, Id *el, int cnt) +{ + int i; + /* map to offsets into favorq */ + for (i = 0; i < cnt; i++) + { + Id p = el[i]; + /* lookup p in sorted favorq */ + int med = 0, low = 0; + int high = favorq->count / 2; + while (low != high) + { + med = (low + high) / 2; + Id pp = favorq->elements[2 * med]; + if (pp < p) + low = med; + else if (pp > p) + high = med; + else + break; + } + while(med && favorq->elements[2 * med - 2] == p) + med--; + if (favorq->elements[2 * med] == p) + el[i] = 2 * med + 1; + else + el[i] = 0; /* hmm */ + } + /* sort by position */ + solv_sort(el, cnt, sizeof(Id), sort_by_favorq_cmp, favorq->elements); + /* map back */ + for (i = 0; i < cnt; i++) + if (el[i]) + el[i] = favorq->elements[el[i] - 1]; +} + +/* bring favored packages to front and disfavored packages to back */ +static void +prefer_favored(Solver *solv, Queue *plist) +{ + int i, fav, disfav, count; + if (!solv->favormap.size) + return; + for (i = fav = disfav = 0, count = plist->count; i < count; i++) + { + Id p = plist->elements[i]; + if (!MAPTST(&solv->favormap, p)) + continue; + if (solv->isdisfavormap.size && MAPTST(&solv->isdisfavormap, p)) + { + /* disfavored package. bring to back */ + if (i < plist->count - 1) + { + memmove(plist->elements + i, plist->elements + i + 1, (plist->count - 1 - i) * sizeof(Id)); + plist->elements[plist->count - 1] = p; + } + i--; + count--; + disfav++; + } + else + { + /* favored package. bring to front */ + if (i > fav) + memmove(plist->elements + fav + 1, plist->elements + fav, (i - fav) * sizeof(Id)); + plist->elements[fav++] = p; + } + } + /* if we have multiple favored/disfavored packages, sort by favorq index */ + if (fav > 1) + sort_by_favorq(solv->favorq, plist->elements, fav); + if (disfav > 1) + sort_by_favorq(solv->favorq, plist->elements + plist->count - disfav, disfav); +} + /* * prune to recommended/suggested packages. * does not prune installed packages (they are also somewhat recommended). @@ -1121,6 +1204,15 @@ void policy_filter_unwanted(Solver *solv, Queue *plist, int mode) { Pool *pool = solv->pool; + if (mode == POLICY_MODE_SUPPLEMENT) + { + /* reorder only */ + dislike_old_versions(pool, plist); + sort_by_common_dep(pool, plist); + prefer_suggested(solv, plist); + prefer_favored(solv, plist); + return; + } if (plist->count > 1) { if (mode != POLICY_MODE_SUGGEST) @@ -1144,6 +1236,7 @@ policy_filter_unwanted(Solver *solv, Queue *plist, int mode) dislike_old_versions(pool, plist); sort_by_common_dep(pool, plist); prefer_suggested(solv, plist); + prefer_favored(solv, plist); } } } diff --git a/src/policy.h b/src/policy.h index 73410eed..8f875c79 100644 --- a/src/policy.h +++ b/src/policy.h @@ -20,6 +20,7 @@ extern "C" { #define POLICY_MODE_RECOMMEND 1 #define POLICY_MODE_SUGGEST 2 #define POLICY_MODE_CHOOSE_NOREORDER 3 /* internal, do not use */ +#define POLICY_MODE_SUPPLEMENT 4 /* internal, do not use */ #define POLICY_ILLEGAL_DOWNGRADE 1 diff --git a/src/solver.c b/src/solver.c index d5989cc5..cd9df6f2 100644 --- a/src/solver.c +++ b/src/solver.c @@ -1701,6 +1701,7 @@ solver_free(Solver *solv) queuep_free(&solv->recommendscplxq); queuep_free(&solv->suggestscplxq); queuep_free(&solv->brokenorphanrules); + queuep_free(&solv->favorq); map_free(&solv->recommendsmap); map_free(&solv->suggestsmap); @@ -1716,6 +1717,8 @@ solver_free(Solver *solv) map_free(&solv->droporphanedmap); map_free(&solv->cleandepsmap); map_free(&solv->allowuninstallmap); + map_free(&solv->favormap); + map_free(&solv->isdisfavormap); solv_free(solv->decisionmap); solv_free(solv->rules); @@ -2063,6 +2066,22 @@ do_complex_recommendations(Solver *solv, Id rec, Map *m, int noselected) #endif +static void +prune_disfavored(Solver *solv, Queue *plist) +{ + int i, j; + if (!solv->isdisfavormap.size) + return; + for (i = j = 0; i < plist->count; i++) + { + Id p = plist->elements[i]; + if (!MAPTST(&solv->isdisfavormap, p)) + plist->elements[j++] = p; + } + if (i != j) + queue_truncate(plist, j); +} + /*------------------------------------------------------------------- * * solver_run_sat @@ -2522,10 +2541,16 @@ solver_run_sat(Solver *solv, int disablerules, int doweak) continue; if (solv->dupmap_all && solv->installed && s->repo == solv->installed && (solv->droporphanedmap_all || (solv->droporphanedmap.size && MAPTST(&solv->droporphanedmap, i - solv->installed->start)))) continue; + if (solv->isdisfavormap.size && MAPTST(&solv->isdisfavormap, i)) + continue; /* disfavored supplements, do not install */ queue_push(&dqs, i); } } + /* filter out disfavored recommended packages */ + if (dq.count && solv->isdisfavormap.size) + prune_disfavored(solv, &dq); + /* filter out all packages obsoleted by installed packages */ /* this is no longer needed if we have reverse obsoletes */ if ((dqs.count || dq.count) && solv->installed) @@ -2636,11 +2661,22 @@ solver_run_sat(Solver *solv, int disablerules, int doweak) for (i = 0; i < dq.count; i++) MAPSET(&dqmap, dq.elements[i]); - /* install all supplemented packages */ + /* prune dqs so that it only contains the best versions */ + for (i = j = 0; i < dqs.count; i++) + { + p = dqs.elements[i]; + if (MAPTST(&dqmap, p)) + dqs.elements[j++] = p; + } + dqs.count = j; + + /* install all supplemented packages, but order first */ + if (dqs.count > 1) + policy_filter_unwanted(solv, &dqs, POLICY_MODE_SUPPLEMENT); for (i = 0; i < dqs.count; i++) { p = dqs.elements[i]; - if (solv->decisionmap[p] || !MAPTST(&dqmap, p)) + if (solv->decisionmap[p]) continue; POOL_DEBUG(SOLV_DEBUG_POLICY, "installing supplemented %s\n", pool_solvid2str(pool, p)); olevel = level; @@ -3327,6 +3363,42 @@ add_complex_jobrules(Solver *solv, Id dep, int flags, int jobidx, int weak) } #endif +/* sort by package id, last entry wins */ +static int +setup_favormaps_cmp(const void *ap, const void *bp, void *dp) +{ + const Id *a = ap, *b = bp; + if ((*a - *b) != 0) + return *a - *b; + return (b[1] < 0 ? -b[1] : b[1]) - (a[1] < 0 ? -a[1] : a[1]); +} + +static void +setup_favormaps(Solver *solv) +{ + Queue *q = solv->favorq; + Pool *pool = solv->pool; + int i; + Id oldp = 0; + if (q->count > 2) + solv_sort(q->elements, q->count / 2, 2 * sizeof(Id), setup_favormaps_cmp, solv); + map_grow(&solv->favormap, pool->nsolvables); + for (i = 0; i < q->count; i += 2) + { + Id p = q->elements[i]; + if (p == oldp) + continue; + oldp = p; + MAPSET(&solv->favormap, p); + if (q->elements[i + 1] < 0) + { + if (!solv->isdisfavormap.size) + map_grow(&solv->isdisfavormap, pool->nsolvables); + MAPSET(&solv->isdisfavormap, p); + } + } +} + /* * * solve job queue @@ -3400,6 +3472,8 @@ solver_solve(Solver *solv, Queue *job) map_zerosize(&solv->allowuninstallmap); map_zerosize(&solv->cleandepsmap); map_zerosize(&solv->weakrulemap); + map_zerosize(&solv->favormap); + map_zerosize(&solv->isdisfavormap); queue_empty(&solv->weakruleq); solv->watches = solv_free(solv->watches); queue_empty(&solv->ruletojob); @@ -3935,6 +4009,21 @@ solver_solve(Solver *solv, Queue *job) case SOLVER_ALLOWUNINSTALL: POOL_DEBUG(SOLV_DEBUG_JOB, "job: allowuninstall %s\n", solver_select2str(pool, select, what)); break; + case SOLVER_FAVOR: + case SOLVER_DISFAVOR: + POOL_DEBUG(SOLV_DEBUG_JOB, "job: %s %s\n", (how & SOLVER_JOBMASK) == SOLVER_FAVOR ? "favor" : "disfavor", solver_select2str(pool, select, what)); + FOR_JOB_SELECT(p, pp, select, what) + { + int j; + if (!solv->favorq) + { + solv->favorq = solv_calloc(1, sizeof(Queue)); + queue_init(solv->favorq); + } + j = solv->favorq->count + 1; + queue_push2(solv->favorq, p, (how & SOLVER_JOBMASK) == SOLVER_FAVOR ? j : -j); + } + break; default: POOL_DEBUG(SOLV_DEBUG_JOB, "job: unknown job\n"); break; @@ -3955,6 +4044,10 @@ solver_solve(Solver *solv, Queue *job) assert(solv->ruletojob.count == solv->nrules - solv->jobrules); solv->jobrules_end = solv->nrules; + /* transform favorq into two maps */ + if (solv->favorq) + setup_favormaps(solv); + /* now create infarch and dup rules */ if (!solv->noinfarchcheck) { @@ -5157,6 +5250,12 @@ pool_job2str(Pool *pool, Id how, Id what, Id flagmask) case SOLVER_ALLOWUNINSTALL: strstart = "allow deinstallation of "; break; + case SOLVER_FAVOR: + strstart = "favor "; + break; + case SOLVER_DISFAVOR: + strstart = "disfavor "; + break; default: strstart = "unknown job "; break; diff --git a/src/solver.h b/src/solver.h index 2ae9c8d5..9424902f 100644 --- a/src/solver.h +++ b/src/solver.h @@ -199,6 +199,9 @@ struct _Solver { Map allowuninstallmap; /* ok to uninstall those */ int allowuninstall_all; + Queue *favorq; + Map favormap; /* favored / disfavored packages */ + Map isdisfavormap; #endif /* LIBSOLV_INTERNAL */ }; @@ -229,6 +232,8 @@ typedef struct _Solver Solver; #define SOLVER_DROP_ORPHANED 0x0900 #define SOLVER_USERINSTALLED 0x0a00 #define SOLVER_ALLOWUNINSTALL 0x0b00 +#define SOLVER_FAVOR 0x0c00 +#define SOLVER_DISFAVOR 0x0d00 #define SOLVER_JOBMASK 0xff00 diff --git a/test/testcases/favor/recommends.t b/test/testcases/favor/recommends.t new file mode 100644 index 00000000..c802d4a0 --- /dev/null +++ b/test/testcases/favor/recommends.t @@ -0,0 +1,50 @@ +repo system 0 empty +repo test 0 testtags +#>=Ver: 2.0 +#>=Pkg: A 1 1 noarch +#>=Rec: X +#>=Pkg: B 1 1 noarch +#>=Prv: X +#>=Pkg: C 1 1 noarch +#>=Prv: X +system unset * system + +# first favor B +job install name A +job favor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +# then favor C +nextjob +job install name A +job favor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + +# check disfavor +nextjob +job install name A +job disfavor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + +nextjob +job install name A +job disfavor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +# check disfavor both, this is different from +# the requires case + +nextjob +job install name A +job disfavor name B +job disfavor name C +result transaction,problems +#>install A-1-1.noarch@test diff --git a/test/testcases/favor/requires.t b/test/testcases/favor/requires.t new file mode 100644 index 00000000..e96a9051 --- /dev/null +++ b/test/testcases/favor/requires.t @@ -0,0 +1,112 @@ +repo system 0 empty +repo test 0 testtags +#>=Ver: 2.0 +#>=Pkg: A 1 1 noarch +#>=Req: X +#>=Pkg: B 1 1 noarch +#>=Prv: X +#>=Pkg: C 1 1 noarch +#>=Prv: X +system unset * system + +# first favor B +job install name A +job favor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +# then favor C +nextjob +job install name A +job favor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + + +# if both are favored, the last one wins +nextjob +job install name A +job favor name C +job favor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +nextjob +job install name A +job favor name B +job favor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + +# now test disfavor + +# first disfavor B +nextjob +job install name A +job disfavor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + +# then disfavor C +nextjob +job install name A +job disfavor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +# then both +nextjob +job install name A +job disfavor name B +job disfavor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +nextjob +job install name A +job disfavor name C +job disfavor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + +# then test combination +nextjob +job install name A +job favor name B +job disfavor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + +nextjob +job install name A +job disfavor name B +job favor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +nextjob +job install name A +job favor name C +job disfavor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +nextjob +job install name A +job disfavor name C +job favor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + diff --git a/test/testcases/favor/supplements.t b/test/testcases/favor/supplements.t new file mode 100644 index 00000000..7f415b0a --- /dev/null +++ b/test/testcases/favor/supplements.t @@ -0,0 +1,71 @@ +repo system 0 empty +repo test 0 testtags +#>=Ver: 2.0 +#>=Pkg: A 1 1 noarch +#>=Pkg: B 1 1 noarch +#>=Sup: A +#>=Pkg: C 1 1 noarch +#>=Sup: A +#>=Pkg: A2 1 1 noarch +#>=Pkg: B2 1 1 noarch +#>=Sup: A2 +#>=Pkg: C2 1 1 noarch +#>=Sup: A2 +#>=Con: B2 +system unset * system + +# first favor B +job install name A +job favor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test +#>install C-1-1.noarch@test + +# then favor C +nextjob +job install name A +job favor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test +#>install C-1-1.noarch@test + +# same with A2 where B2 and C2 conflict + +nextjob +job install name A2 +job favor name B2 +result transaction,problems +#>install A2-1-1.noarch@test +#>install B2-1-1.noarch@test + +nextjob +job install name A2 +job favor name C2 +result transaction,problems +#>install A2-1-1.noarch@test +#>install C2-1-1.noarch@test + + +# check disfavor +nextjob +job install name A +job disfavor name B +result transaction,problems +#>install A-1-1.noarch@test +#>install C-1-1.noarch@test + +nextjob +job install name A +job disfavor name C +result transaction,problems +#>install A-1-1.noarch@test +#>install B-1-1.noarch@test + +nextjob +job install name A +job disfavor name B +job disfavor name C +result transaction,problems +#>install A-1-1.noarch@test