]> git.ipfire.org Git - thirdparty/libsolv.git/commitdiff
Implement SOLVER_FAVOR and SOLVER_DISFAVOR
authorMichael Schroeder <mls@suse.de>
Tue, 3 May 2016 14:22:06 +0000 (16:22 +0200)
committerMichael Schroeder <mls@suse.de>
Tue, 3 May 2016 14:22:43 +0000 (16:22 +0200)
bindings/solv.i
doc/libsolv-bindings.3
doc/libsolv-bindings.txt
ext/testcase.c
src/policy.c
src/policy.h
src/solver.c
src/solver.h
test/testcases/favor/recommends.t [new file with mode: 0644]
test/testcases/favor/requires.t [new file with mode: 0644]
test/testcases/favor/supplements.t [new file with mode: 0644]

index 5312900437c6440fca1b6599d4d32aa1839b48d0..b21fbbcacfe2eed6b6666183c9fb453aabb9a54c 100644 (file)
@@ -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;
index 14bf96c437ed9ecb76561c0c4390b45ed5bee08d..6b57ebfb6da831f71b7952f8be7f50848402533f 100644 (file)
@@ -1,13 +1,13 @@
 '\" t
 .\"     Title: Libsolv-Bindings
 .\"    Author: [see the "Author" section]
-.\" Generator: DocBook XSL Stylesheets v1.78.0 <http://docbook.sf.net/>
-.\"      Date: 12/14/2015
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\"      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\&.
index 1ee699dd705dcb41329d18b6b6c14265759b72cb..79f4c2cffb111e62c33f52d0834a2993577bf727 100644 (file)
@@ -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.
 
index a138e6b50b3a19806c1f43a55d0edfe858065995..16c7a187e3a99147d584ed05fac3613bc24e5156 100644 (file)
@@ -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 }
 };
 
index 12ad771a812ed62f2714f5f0added8e3a1e4163c..528c8c9803cf3130a309c0f4c7e0f131948d5e68 100644 (file)
@@ -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);
        }
     }
 }
index 73410eedba9a18a4ade30db2f6923f3c292c902b..8f875c79a65ec0f01898458880d0ada0f3a887f1 100644 (file)
@@ -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
index d5989cc5f3b8f11e144bad46aabdd477aee6ad3c..cd9df6f2e2bcabc0183b6b3181d4de70513be4c2 100644 (file)
@@ -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;
index 2ae9c8d569ccff6953d72e4ac1dac1105bb29591..9424902fa28676a4b7b3d0b57a2add0935cf90f3 100644 (file)
@@ -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 (file)
index 0000000..c802d4a
--- /dev/null
@@ -0,0 +1,50 @@
+repo system 0 empty
+repo test 0 testtags <inline>
+#>=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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>install A-1-1.noarch@test
+#>install C-1-1.noarch@test
+
+nextjob
+job install name A
+job disfavor name C
+result transaction,problems <inline>
+#>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 <inline>
+#>install A-1-1.noarch@test
diff --git a/test/testcases/favor/requires.t b/test/testcases/favor/requires.t
new file mode 100644 (file)
index 0000000..e96a905
--- /dev/null
@@ -0,0 +1,112 @@
+repo system 0 empty
+repo test 0 testtags <inline>
+#>=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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>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 (file)
index 0000000..7f415b0
--- /dev/null
@@ -0,0 +1,71 @@
+repo system 0 empty
+repo test 0 testtags <inline>
+#>=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 <inline>
+#>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 <inline>
+#>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 <inline>
+#>install A2-1-1.noarch@test
+#>install B2-1-1.noarch@test
+
+nextjob
+job install name A2
+job favor name C2
+result transaction,problems <inline>
+#>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 <inline>
+#>install A-1-1.noarch@test
+#>install C-1-1.noarch@test
+
+nextjob
+job install name A
+job disfavor name C
+result transaction,problems <inline>
+#>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 <inline>
+#>install A-1-1.noarch@test