Imported Upstream version 0.7.20 upstream/0.7.20
authorDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 23 Dec 2021 05:47:19 +0000 (14:47 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 23 Dec 2021 05:47:19 +0000 (14:47 +0900)
17 files changed:
NEWS
VERSION.cmake
bindings/solv.i
doc/libsolv-bindings.txt
ext/repo_comps.c
ext/repo_testcase.c
ext/solv_xmlparser.c
ext/solv_xmlparser.h
ext/testcase.c
package/libsolv.changes
package/libsolv.spec.in
src/conda.c
src/rules.c
src/solver.c
src/solver.h
test/testcases/excludefromweak/excludefromweak-obsoletes.t [new file with mode: 0644]
test/testcases/excludefromweak/excludefromweak.t [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 1bfc9d0..d99f0ee 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,15 @@
 This file contains the major changes between
 libsolv versions:
 
+Version 0.7.20
+- selected bug fixes:
+  * fix misparsing of '&' in attributes with libxml2
+  * choice rules: treat orphaned packages as newest
+  * fix compatibility with Python 3.10
+- new features:
+  * new SOLVER_EXCLUDEFROMWEAK job to ignore pkgs for weak dependencies
+  * support for environments in comps parser
+
 Version 0.7.19
 - selected bug fixes:
   * fix rare segfault in resolve_jobrules() that could happen
index 1cecd64..5587ccf 100644 (file)
@@ -49,5 +49,5 @@ SET(LIBSOLVEXT_SOVERSION "1")
 
 SET(LIBSOLV_MAJOR "0")
 SET(LIBSOLV_MINOR "7")
-SET(LIBSOLV_PATCH "19")
+SET(LIBSOLV_PATCH "20")
 
index 1882b13..61c6fc8 100644 (file)
 %markfunc Pool "mark_Pool";
 #endif
 
+#ifdef SWIGPYTHON
+%begin %{
+#define PY_SSIZE_T_CLEAN
+%}
+#endif
+
 /**
  ** binaryblob handling
  **/
@@ -63,13 +69,13 @@ typedef struct {
   $2 = size;
 }
 
-%typemap(freearg,noblock=1,match="in") (const unsigned char *str, int len) {
+%typemap(freearg,noblock=1,match="in") (const unsigned char *str, size_t len) {
   if (alloc$argnum == SWIG_NEWOBJ) %delete_array(buf$argnum);
 }
 
 %typemap(out,noblock=1,fragment="SWIG_FromCharPtrAndSize") BinaryBlob {
 #if defined(SWIGPYTHON) && defined(PYTHON3)
-  $result = $1.data ? Py_BuildValue("y#", $1.data, $1.len) : SWIG_Py_Void();
+  $result = $1.data ? Py_BuildValue("y#", $1.data, (Py_ssize_t)$1.len) : SWIG_Py_Void();
 #elif defined(SWIGTCL)
   Tcl_SetObjResult(interp, $1.data ? Tcl_NewByteArrayObj($1.data, $1.len) : NULL);
 #else
@@ -1277,6 +1283,7 @@ typedef struct {
   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_EXCLUDEFROMWEAK = SOLVER_EXCLUDEFROMWEAK;
   static const Id SOLVER_JOBMASK = SOLVER_JOBMASK;
   static const Id SOLVER_WEAK = SOLVER_WEAK;
   static const Id SOLVER_ESSENTIAL = SOLVER_ESSENTIAL;
index ac112cf..0fa313b 100644 (file)
@@ -2076,6 +2076,10 @@ 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_EXCLUDEFROMWEAK*::
+Avoid the specified packages to satisfy recommended or supplemented dependencies.
+Unlike SOLVER_DISFAVOR, it does not interfere with other rules.
+
 *SOLVER_JOBMASK*::
 A mask containing all the above action bits.
 
index 014f89a..633fbaa 100644 (file)
@@ -54,6 +54,8 @@ enum state {
   STATE_CDISPLAY_ORDER,
   STATE_GROUPLIST,
   STATE_GROUPID,
+  STATE_ENVIRONMENT,
+  STATE_OPTIONLIST,
   NUMSTATES
 };
 
@@ -61,6 +63,7 @@ static struct solv_xmlparser_element stateswitches[] = {
   { STATE_START,       "comps",         STATE_COMPS,         0 },
   { STATE_COMPS,       "group",         STATE_GROUP,         0 },
   { STATE_COMPS,       "category",      STATE_CATEGORY,      0 },
+  { STATE_COMPS,       "environment",   STATE_ENVIRONMENT,   0 },
   { STATE_GROUP,       "id",            STATE_ID,            1 },
   { STATE_GROUP,       "name",          STATE_NAME,          1 },
   { STATE_GROUP,       "description",   STATE_DESCRIPTION,   1 },
@@ -77,6 +80,13 @@ static struct solv_xmlparser_element stateswitches[] = {
   { STATE_CATEGORY ,   "grouplist",     STATE_GROUPLIST,     0 },
   { STATE_CATEGORY ,   "display_order", STATE_DISPLAY_ORDER, 1 },
   { STATE_GROUPLIST,   "groupid",       STATE_GROUPID,       1 },
+  { STATE_ENVIRONMENT, "id",            STATE_ID,            1 },
+  { STATE_ENVIRONMENT, "name",          STATE_NAME,          1 },
+  { STATE_ENVIRONMENT, "description",   STATE_DESCRIPTION,   1 },
+  { STATE_ENVIRONMENT, "grouplist",     STATE_GROUPLIST,     0 },
+  { STATE_ENVIRONMENT, "optionlist",    STATE_OPTIONLIST,    0 },
+  { STATE_ENVIRONMENT, "display_order", STATE_DISPLAY_ORDER, 1 },
+  { STATE_OPTIONLIST,  "groupid",       STATE_GROUPID,       1 },
   { NUMSTATES }
 };
 
@@ -128,9 +138,15 @@ startElement(struct solv_xmlparser *xmlp, int state, const char *name, const cha
     {
     case STATE_GROUP:
     case STATE_CATEGORY:
+    case STATE_ENVIRONMENT:
       s = pd->solvable = pool_id2solvable(pool, repo_add_solvable(pd->repo));
       pd->handle = s - pool->solvables;
-      pd->kind = state == STATE_GROUP ? "group" : "category";
+      if (state == STATE_GROUP)
+        pd->kind = "group";
+      else if (state == STATE_CATEGORY)
+        pd->kind = "category";
+      else
+        pd->kind = "environment";
       pd->isvisible = COMPS_DEFAULT_ISVISIBLE;
       pd->isdefault = COMPS_DEFAULT_ISDEFAULT;
       break;
@@ -160,6 +176,18 @@ startElement(struct solv_xmlparser *xmlp, int state, const char *name, const cha
        break;
       }
 
+    case STATE_GROUPLIST:
+      {
+       pd->reqtype = SOLVABLE_REQUIRES;
+       break;
+      }
+
+    case STATE_OPTIONLIST:
+      {
+       pd->reqtype = SOLVABLE_SUGGESTS;
+       break;
+      }
+
     default:
       break;
     }
@@ -177,6 +205,7 @@ endElement(struct solv_xmlparser *xmlp, int state, char *content)
     {
     case STATE_GROUP:
     case STATE_CATEGORY:
+    case STATE_ENVIRONMENT:
       if (!s->arch)
        s->arch = ARCH_NOARCH;
       if (!s->evr)
@@ -211,7 +240,7 @@ endElement(struct solv_xmlparser *xmlp, int state, char *content)
 
     case STATE_GROUPID:
       id = pool_str2id(pd->pool, join2(&pd->jd, "group", ":", content), 1);
-      s->requires = repo_addid_dep(pd->repo, s->requires, id, 0);
+      repo_add_idarray(pd->repo, pd->handle, pd->reqtype, id);
       break;
 
     case STATE_USERVISIBLE:
index 5cc0327..00f7b54 100644 (file)
@@ -480,6 +480,12 @@ testcase_write_testtags(Repo *repo, FILE *fp)
       tmp = solvable_lookup_str(s, SOLVABLE_BUILDVERSION);
       if (tmp)
         fprintf(fp, "=Bvr: %s\n", tmp);
+      if (solvable_lookup_idarray(s, SOLVABLE_TRACK_FEATURES, &q))
+       {
+         int i;
+         for (i = 0; i < q.count; i++)
+           fprintf(fp, "=Trf: %s\n", pool_id2str(pool, q.elements[i]));
+       }
       ti = solvable_lookup_num(s, SOLVABLE_BUILDTIME, 0);
       if (ti)
        fprintf(fp, "=Tim: %u\n", ti);
@@ -707,6 +713,9 @@ testcase_add_testtags(Repo *repo, FILE *fp, int flags)
        case 'B' << 16 | 'v' << 8 | 'r':
          repodata_set_str(data, s - pool->solvables, SOLVABLE_BUILDVERSION, line + 6);
          break;
+       case 'T' << 16 | 'r' << 8 | 'f':
+         repodata_add_poolstr_array(data, s - pool->solvables, SOLVABLE_TRACK_FEATURES, line + 6);
+         break;
         default:
          break;
         }
index 6292663..87bd096 100644 (file)
@@ -54,6 +54,47 @@ character_data(void *userData, const XML_Char *s, int len)
 }
 
 #ifdef WITH_LIBXML2
+static void fixup_att_inplace(char *at)
+{
+  while ((at = strchr(at, '&')) != 0)
+    {
+      at++;
+      if (!memcmp(at, "#38;", 4))
+       memmove(at, at + 4, strlen(at + 4) + 1);
+    }
+}
+
+static const xmlChar **fixup_atts(struct solv_xmlparser *xmlp, const xmlChar **atts)
+{
+  size_t needsize = 0;
+  size_t natts;
+  char **at;
+
+  for (natts = 0; atts[natts]; natts++)
+    if (strchr((char *)atts[natts], '&'))
+      needsize += strlen((const char *)atts[natts]) + 1;
+  if (!needsize)
+    return atts;
+  at = xmlp->attsdata = solv_realloc(xmlp->attsdata, (natts + 1) * sizeof(xmlChar *) + needsize);
+  needsize = (natts + 1) * sizeof(xmlChar *);
+  for (natts = 0; atts[natts]; natts++)
+    {
+      at[natts] = (char *)atts[natts];
+      if (strchr(at[natts], '&'))
+        {
+         size_t l = strlen(at[natts]) + 1;
+         memcpy((char *)at + needsize, at[natts], l);
+         at[natts] = (char *)at + needsize;
+         needsize += l;
+         fixup_att_inplace(at[natts]);
+        }
+    }
+  at[natts] = 0;
+  return (const xmlChar **)at;
+}
+#endif
+
+#ifdef WITH_LIBXML2
 static void
 start_element(void *userData, const xmlChar *name, const xmlChar **atts)
 #else
@@ -97,6 +138,8 @@ start_element(void *userData, const char *name, const char **atts)
       static const char *nullattr;
       atts = (const xmlChar **)&nullattr;
     }
+  else if (xmlp->state != oldstate)
+    atts = fixup_atts(xmlp, atts);
 #endif
   if (xmlp->state != oldstate)
     xmlp->startelement(xmlp, xmlp->state, el->element, (const char **)atts);
@@ -177,6 +220,7 @@ solv_xmlparser_free(struct solv_xmlparser *xmlp)
   queue_free(&xmlp->elementq);
   xmlp->content = solv_free(xmlp->content);
   xmlp->errstr = solv_free(xmlp->errstr);
+  xmlp->attsdata = solv_free(xmlp->attsdata);
 }
 
 static void
index ced0571..717983f 100644 (file)
@@ -30,6 +30,7 @@ struct solv_xmlparser {
 
   Id *elementhelper;
   void *parser;
+  void *attsdata;
 };
 
 #define SOLV_XMLPARSER_OK      0
index 20b0c48..055452f 100644 (file)
@@ -60,6 +60,7 @@ static struct job2str {
   { SOLVER_FAVOR,          "favor" },
   { SOLVER_DISFAVOR,       "disfavor" },
   { SOLVER_BLACKLIST,      "blacklist" },
+  { SOLVER_EXCLUDEFROMWEAK,   "excludefromweak" },
   { 0, 0 }
 };
 
index 6270b61..c402812 100644 (file)
@@ -1,4 +1,19 @@
 -------------------------------------------------------------------
+Sat Sep 25 22:45:07 CEST 2021 - mls@suse.de
+
+- fix misparsing of '&' in attributes with libxml2
+- choice rules: treat orphaned packages as newest [bsc#1190465]
+- fix compatibility with Python 3.10
+- new SOLVER_EXCLUDEFROMWEAK job type
+- support for environments in comps parser
+
+-------------------------------------------------------------------
+Fri Jul 30 11:43:29 UTC 2021 - Dominique Leuenberger <dimstar@opensuse.org>
+
+- Disable python2 usage on suse_version >= 1550 by default (still
+  possible to use osc build --with=python).
+
+-------------------------------------------------------------------
 Wed Apr  7 14:56:16 CEST 2021 - mls@suse.de
 
 - fix rare segfault in resolve_jobrules() that could happen
index 0964ad6..5e4e30d 100644 (file)
@@ -1,7 +1,7 @@
 #
 # spec file for package libsolv
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2021 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
+
 %define libname libsolv@LIBSOLV_SOVERSION@
 
 %if 0%{?sle_version} >= 120300 || 0%{?suse_version} >= 1330 || !0%{?suse_version}
 # we need at least swig 1.3.40 for the bindings ($typemap support)
 %if 0%{?suse_version} != 1110
 %bcond_without python3
+%if 0%{?suse_version} < 1550
 %bcond_without python
+%else
+%bcond_with python
+%endif
 %bcond_without ruby
 %bcond_without perl
 %else
@@ -56,7 +61,7 @@ Release:        0
 Summary:        Package dependency solver using a satisfiability algorithm
 License:        BSD-3-Clause
 Group:          Development/Libraries/C and C++
-Url:            https://github.com/openSUSE/libsolv
+URL:            https://github.com/openSUSE/libsolv
 Source:         libsolv-%{version}.tar.bz2
 BuildRequires:  cmake
 BuildRequires:  gcc-c++
@@ -112,7 +117,6 @@ BuildRequires:  xz-devel
 BuildRequires:  libzstd-devel
 %endif
 
-
 %description
 libsolv is a library for solving packages and reading repositories.
 The solver uses a satisfiability algorithm.
index 21ad6bf..6f6a65a 100644 (file)
@@ -670,7 +670,7 @@ pool_conda_matchspec(Pool *pool, const char *name)
   if (build)
     {
       *p++ = ' ';
-      memcpy(p, build, buildend - build);
+      memmove(p, build, buildend - build);
       p += buildend - build;
     }
   evrid = pool_strn2id(pool, version, p - version, 1);
index b1b5f09..212df32 100644 (file)
@@ -3203,7 +3203,7 @@ solver_choicerulecheck2(Solver *solv, Id pi, Id pt, Queue *q)
       if (!ur->p)
         ur = solv->rules + solv->featurerules + (pi - pool->installed->start);
       if (!ur->p)
-       return 0;
+       return 1;               /* orphaned, thus newest */
       queue_push2(q, pi, 0);
       FOR_RULELITERALS(p, pp, ur)
        if (p > 0 && p != pi)
index 89a2ed1..1dc2c78 100644 (file)
@@ -1398,6 +1398,8 @@ solver_free(Solver *solv)
   map_free(&solv->droporphanedmap);
   map_free(&solv->cleandepsmap);
   map_free(&solv->allowuninstallmap);
+  map_free(&solv->excludefromweakmap);
+
 
   solv_free(solv->favormap);
   solv_free(solv->decisionmap);
@@ -2202,6 +2204,20 @@ prune_disfavored(Solver *solv, Queue *plist)
     queue_truncate(plist, j);
 }
 
+static void
+prune_exclude_from_weak(Solver *solv, Queue *plist)
+{
+  int i, j;
+  for (i = j = 0; i < plist->count; i++)
+    {
+      Id p = plist->elements[i];
+      if (!MAPTST(&solv->excludefromweakmap, p))
+        plist->elements[j++] = p;
+    }
+  if (i != j)
+    queue_truncate(plist, j);
+}
+
 static int
 resolve_weak(Solver *solv, int level, int disablerules, Queue *dq, Queue *dqs, int *rerunp)
 {
@@ -2275,6 +2291,8 @@ resolve_weak(Solver *solv, int level, int disablerules, Queue *dq, Queue *dqs, i
            continue;
          if (solv->havedisfavored && solv->favormap[i] < 0)
            continue;   /* disfavored supplements, do not install */
+         if (solv->excludefromweakmap.size && MAPTST(&solv->excludefromweakmap, i))
+           continue;   /* excluded for weak deps, do not install */
          queue_push(dqs, i);
        }
     }
@@ -2283,6 +2301,10 @@ resolve_weak(Solver *solv, int level, int disablerules, Queue *dq, Queue *dqs, i
   if (dq->count && solv->havedisfavored)
     prune_disfavored(solv, dq);
 
+  /* filter out weak_excluded recommended packages */
+  if (solv->excludefromweakmap.size)
+    prune_exclude_from_weak(solv, dq);
+
   /* filter out all packages obsoleted by installed packages */
   /* this is no longer needed if we have (and trust) reverse obsoletes */
   if ((dqs->count || dq->count) && solv->installed)
@@ -3320,6 +3342,37 @@ add_complex_jobrules(Solver *solv, Id dep, int flags, int jobidx, int weak)
 #endif
 
 static void
+solver_add_exclude_from_weak(Solver *solv)
+{
+  Queue *job = &solv->job;
+  Pool *pool = solv->pool;
+  int i;
+  Id p, pp, how, what, select;
+for (i = 0; i < job->count; i += 2)
+  {
+    how = job->elements[i];
+    if ((how & SOLVER_JOBMASK) != SOLVER_EXCLUDEFROMWEAK)
+       continue;
+    if (!solv->excludefromweakmap.size)
+       map_grow(&solv->excludefromweakmap, pool->nsolvables);
+    what = job->elements[i + 1];
+    select = how & SOLVER_SELECTMASK;
+    if (select == SOLVER_SOLVABLE_REPO)
+      {
+       Repo *repo = pool_id2repo(pool, what);
+         if (repo)
+           {
+             Solvable *s;
+             FOR_REPO_SOLVABLES(repo, p, s)
+               MAPSET(&solv->excludefromweakmap, p);
+           }
+       }
+      FOR_JOB_SELECT(p, pp, select, what)
+       MAPSET(&solv->excludefromweakmap, p);
+    }
+}
+
+static void
 setup_favormap(Solver *solv)
 {
   Queue *job = &solv->job;
@@ -3385,6 +3438,7 @@ solver_solve(Solver *solv, Queue *job)
   int hasfavorjob = 0;
   int haslockjob = 0;
   int hasblacklistjob = 0;
+  int hasexcludefromweakjob = 0;
 
   solve_start = solv_timems(0);
 
@@ -3436,6 +3490,7 @@ solver_solve(Solver *solv, Queue *job)
   map_zerosize(&solv->droporphanedmap);
   solv->allowuninstall_all = 0;
   map_zerosize(&solv->allowuninstallmap);
+  map_zerosize(&solv->excludefromweakmap);
   map_zerosize(&solv->cleandepsmap);
   map_zerosize(&solv->weakrulemap);
   solv->favormap = solv_free(solv->favormap);
@@ -3999,6 +4054,10 @@ solver_solve(Solver *solv, Queue *job)
          POOL_DEBUG(SOLV_DEBUG_JOB, "job: blacklist %s\n", solver_select2str(pool, select, what));
          hasblacklistjob = 1;
          break;
+        case SOLVER_EXCLUDEFROMWEAK:
+         POOL_DEBUG(SOLV_DEBUG_JOB, "job: excludefromweak %s\n", solver_select2str(pool, select, what));
+         hasexcludefromweakjob = 1;
+         break;
        default:
          POOL_DEBUG(SOLV_DEBUG_JOB, "job: unknown job\n");
          break;
@@ -4057,6 +4116,9 @@ solver_solve(Solver *solv, Queue *job)
   else
     solv->blackrules = solv->blackrules_end = solv->nrules;
 
+  if (hasexcludefromweakjob)
+    solver_add_exclude_from_weak(solv);
+
   if (solv->havedisfavored && solv->strongrecommends && solv->recommendsruleq)
     solver_addrecommendsrules(solv);
   else
index 2dec259..d30ae36 100644 (file)
@@ -208,6 +208,8 @@ struct s_Solver {
   Map allowuninstallmap;               /* ok to uninstall those */
   int allowuninstall_all;
 
+  Map excludefromweakmap;              /* remove them from candidates for supplements and recommends */
+
   Id *favormap;                                /* favor job index, > 0: favored, < 0: disfavored */
   int havedisfavored;                  /* do we have disfavored packages? */
 
@@ -248,6 +250,7 @@ typedef struct s_Solver Solver;
 #define SOLVER_FAVOR                   0x0c00
 #define SOLVER_DISFAVOR                        0x0d00
 #define SOLVER_BLACKLIST               0x0e00
+#define SOLVER_EXCLUDEFROMWEAK         0x1000
 
 #define SOLVER_JOBMASK                 0xff00
 
diff --git a/test/testcases/excludefromweak/excludefromweak-obsoletes.t b/test/testcases/excludefromweak/excludefromweak-obsoletes.t
new file mode 100644 (file)
index 0000000..adc20d5
--- /dev/null
@@ -0,0 +1,35 @@
+repo @System 0 testtags <inline>
+#>=Pkg: pkg-A 1.0 1 noarch
+#>=Prv: pkg-A = 1.0-1
+#>=Rec: pkg-C
+#>=Pkg: pkg-B 1.0 1 noarch
+#>=Prv: pkg-B = 1.0-1
+
+repo available -99.-1000 testtags <inline>
+#>=Pkg: pkg-A 1.0 3 noarch
+#>=Prv: pkg-A = 1.0-3
+#>=Rec: pkg-B
+#>=Pkg: pkg-B 1.0 2 noarch
+#>=Prv: pkg-B = 1.0-2
+#>=Pkg: pkg-C 1.0 1 noarch
+#>=Prv: pkg-C = 1.0-1
+#>=Obs: pkg-B
+
+system x86_64 rpm @System
+poolflags implicitobsoleteusescolors
+solverflags allowvendorchange keepexplicitobsoletes bestobeypolicy keeporphans yumobsoletes
+
+job update all packages [forcebest]
+job excludefromweak name pkg-C
+result transaction,problems <inline>
+#>erase pkg-B-1.0-1.noarch@@System pkg-C-1.0-1.noarch@available
+#>install pkg-C-1.0-1.noarch@available
+#>upgrade pkg-A-1.0-1.noarch@@System pkg-A-1.0-3.noarch@available
+
+nextjob
+job update oneof pkg-A-1.0-1.noarch@@System pkg-B-1.0-1.noarch@@System pkg-A-1.0-3.noarch@available pkg-B-1.0-2.noarch@available pkg-C-1.0-1.noarch@available [forcebest,targeted,setevr,setarch]
+job excludefromweak name pkg-C
+result transaction,problems <inline>
+#>erase pkg-B-1.0-1.noarch@@System pkg-C-1.0-1.noarch@available
+#>install pkg-C-1.0-1.noarch@available
+#>upgrade pkg-A-1.0-1.noarch@@System pkg-A-1.0-3.noarch@available
diff --git a/test/testcases/excludefromweak/excludefromweak.t b/test/testcases/excludefromweak/excludefromweak.t
new file mode 100644 (file)
index 0000000..4d6d859
--- /dev/null
@@ -0,0 +1,24 @@
+repo @System 0 testtags <inline>
+
+repo available -99.-1000 testtags <inline>
+#>=Pkg: pkg-A 1 1 noarch
+#>=Prv: pkg-A = 1-1
+#>=Rec: pkg-B
+#>=Pkg: pkg-B 1 1 noarch
+#>=Prv: pkg-B = 1-1
+
+system x86_64 rpm @System
+poolflags implicitobsoleteusescolors
+solverflags allowvendorchange keepexplicitobsoletes bestobeypolicy keeporphans yumobsoletes
+
+job install oneof pkg-A-1-1.noarch@available [forcebest,targeted,setevr,setarch]
+job excludefromweak name pkg-B
+result transaction,problems <inline>
+#>install pkg-A-1-1.noarch@available
+
+nextjob
+job install oneof pkg-A-1-1.noarch@available [forcebest,targeted,setevr,setarch]
+job excludefromweak name pkg-A
+job excludefromweak name pkg-B
+result transaction,problems <inline>
+#>install pkg-A-1-1.noarch@available