Handle recommended packages in core and rpm backends

Identify and store recommended packages in the cache, add a query option
to read them and ignore them if they are not present when installing.

Initial identification code from Mark Hatle <mark.hatle@windriver.com>.

Upstream-Status: Pending

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>

diff --git a/smart/backends/rpm/base.py b/smart/backends/rpm/base.py
index 9332ea0..4fcfbee 100644
--- a/smart/backends/rpm/base.py
+++ b/smart/backends/rpm/base.py
@@ -225,6 +225,52 @@ class RPMPackage(Package):
                         break
                 else:
                     return False
+        srecs = fk(self.recommends)
+        orecs = fk(other.recommends)
+        if srecs != orecs:
+            for srec in srecs:
+                if srec.name[0] == "/" or srec in orecs:
+                    continue
+                for orec in orecs:
+                    if (srec.name == orec.name and
+                        srec.relation == orec.relation and
+                        checkver(srec.version, orec.version)):
+                        break
+                else:
+                    return False
+            for orec in orecs:
+                if orec.name[0] == "/" or orec in srecs:
+                    continue
+                for srec in srecs:
+                    if (srec.name == orec.name and
+                        srec.relation == orec.relation and
+                        checkver(srec.version, orec.version)):
+                        break
+                else:
+                    return False
+        srecs = fk(self.recommends)
+        orecs = fk(other.recommends)
+        if srecs != orecs:
+            for srec in srecs:
+                if srec.name[0] == "/" or srec in orecs:
+                    continue
+                for orec in orecs:
+                    if (srec.name == orec.name and
+                        srec.relation == orec.relation and
+                        checkver(srec.version, orec.version)):
+                        break
+                else:
+                    return False
+            for orec in orecs:
+                if orec.name[0] == "/" or orec in srecs:
+                    continue
+                for srec in srecs:
+                    if (srec.name == orec.name and
+                        srec.relation == orec.relation and
+                        checkver(srec.version, orec.version)):
+                        break
+                else:
+                    return False
         return True
 
     def coexists(self, other):
diff --git a/smart/ccache.c b/smart/ccache.c
index 7193185..8b66515 100644
--- a/smart/ccache.c
+++ b/smart/ccache.c
@@ -500,6 +500,46 @@ Package_equals(PackageObject *self, PackageObject *other)
         }
     }
 
+    ilen = 0;
+    jlen = 0;
+    for (i = 0; i != PyList_GET_SIZE(self->recommends); i++) {
+        PyObject *item = PyList_GET_ITEM(self->recommends, i);
+        if (!PyObject_IsInstance(item, (PyObject *)&Depends_Type)) {
+            PyErr_SetString(PyExc_TypeError, "Depends instance expected");
+            return NULL;
+        }
+        if (STR(((DependsObject *)item)->name)[0] != '/')
+            ilen += 1;
+    }
+    for (j = 0; j != PyList_GET_SIZE(other->recommends); j++) {
+        PyObject *item = PyList_GET_ITEM(other->recommends, j);
+        if (!PyObject_IsInstance(item, (PyObject *)&Depends_Type)) {
+            PyErr_SetString(PyExc_TypeError, "Depends instance expected");
+            return NULL;
+        }
+        if (STR(((DependsObject *)item)->name)[0] != '/')
+            jlen += 1;
+    }
+    if (ilen != jlen) {
+        ret = Py_False;
+        goto exit;
+    }
+
+    ilen = PyList_GET_SIZE(self->recommends);
+    jlen = PyList_GET_SIZE(other->recommends);
+    for (i = 0; i != ilen; i++) {
+        PyObject *item = PyList_GET_ITEM(self->recommends, i);
+        if (STR(((DependsObject *)item)->name)[0] != '/') {
+            for (j = 0; j != jlen; j++)
+                if (item == PyList_GET_ITEM(other->recommends, j))
+                    break;
+            if (j == jlen) {
+                ret = Py_False;
+                goto exit;
+            }
+        }
+    }
+
 exit:
     Py_INCREF(ret);
     return ret;
@@ -1813,6 +1853,59 @@ Loader_buildPackage(LoaderObject *self, PyObject *args)
         }
     }
 
+    /* if recargs: */
+    if (recargs) {
+        int i = 0;
+        int len = PyList_GET_SIZE(recargs);
+        /* pkg.recommends = [] */
+        Py_DECREF(pkgobj->recommends);
+        pkgobj->recommends = PyList_New(len);
+        /* for args in recargs: */
+        for (; i != len; i++) {
+            PyObject *args = PyList_GET_ITEM(recargs, i);
+            DependsObject *recobj;
+            PyObject *rec;
+            
+            if (!PyTuple_Check(args)) {
+                PyErr_SetString(PyExc_TypeError,
+                                "Item in recargs is not a tuple");
+                return NULL;
+            }
+
+            /* rec = cache._objmap.get(args) */
+            rec = PyDict_GetItem(cache->_objmap, args);
+            recobj = (DependsObject *)rec;
+
+            /* if not rec: */
+            if (!rec) {
+                if (!PyTuple_Check(args) || PyTuple_GET_SIZE(args) < 2) {
+                    PyErr_SetString(PyExc_ValueError, "Invalid recargs tuple");
+                    return NULL;
+                }
+                /* rec = args[0](*args[1:]) */
+                callargs = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
+                rec = PyObject_CallObject(PyTuple_GET_ITEM(args, 0), callargs);
+                Py_DECREF(callargs);
+                if (!rec) return NULL;
+                recobj = (DependsObject *)rec;
+
+                /* cache._objmap[args] = rec */
+                PyDict_SetItem(cache->_objmap, args, rec);
+                Py_DECREF(rec);
+
+                /* cache._recommends.append(rec) */
+                PyList_Append(cache->_recommends, rec);
+            }
+
+            /* relpkgs.append(rec.packages) */
+            PyList_Append(relpkgs, recobj->packages);
+
+            /* pkg.recommends.append(rec) */
+            Py_INCREF(rec);
+            PyList_SET_ITEM(pkgobj->recommends, i, rec);
+        }
+    }
+
     /* if upgargs: */
     if (upgargs) {
         int i = 0;
@@ -2592,6 +2685,16 @@ Cache_reset(CacheObject *self, PyObject *args)
         if (PyList_Check(reqobj->providedby))
             LIST_CLEAR(reqobj->providedby);
     }
+    len = PyList_GET_SIZE(self->_recommends);
+    for (i = 0; i != len; i++) {
+        DependsObject *reqobj;
+        PyObject *req;
+        req = PyList_GET_ITEM(self->_recommends, i);
+        reqobj = (DependsObject *)req;
+        LIST_CLEAR(reqobj->packages);
+        if (PyList_Check(reqobj->providedby))
+            LIST_CLEAR(reqobj->providedby);
+    }
     len = PyList_GET_SIZE(self->_upgrades);
     for (i = 0; i != len; i++) {
         DependsObject *upgobj;
@@ -2834,6 +2937,30 @@ Cache__reload(CacheObject *self, PyObject *args)
                 }
 
                 /*
+                   for rec in pkg.recommends:
+                       rec.packages.append(pkg)
+                       if rec not in recommends:
+                           recommends[rec] = True
+                           objmap[rec.getInitArgs()] = rec
+                */
+                if (PyList_Check(pkg->recommends)) {
+                    klen = PyList_GET_SIZE(pkg->recommends);
+                    for (k = 0; k != klen; k++) {
+                        PyObject *rec = PyList_GET_ITEM(pkg->recommends, k);
+                        PyList_Append(((DependsObject *)rec)->packages,
+                                      (PyObject *)pkg);
+                        if (!PyDict_GetItem(recommends, rec)) {
+                            PyDict_SetItem(recommends, rec, Py_True);
+                            args = PyObject_CallMethod(rec, "getInitArgs",
+                                                       NULL);
+                            if (!args) return NULL;
+                            PyDict_SetItem(objmap, args, rec);
+                            Py_DECREF(args);
+                        }
+                    }
+                }
+
+                /*
                    for upg in pkg.upgrades:
                        upg.packages.append(pkg)
                        if upg not in upgrades:
@@ -3097,6 +3224,47 @@ Cache_linkDeps(CacheObject *self, PyObject *args)
         Py_DECREF(seq);
     }
 
+    /* recnames = {} */
+    recnames = PyDict_New();
+    /* for rec in self._recommends: */
+    len = PyList_GET_SIZE(self->_recommends);
+    for (i = 0; i != len; i++) {
+        PyObject *rec = PyList_GET_ITEM(self->_recommends, i);
+
+        /* for name in rec.getMatchNames(): */
+        PyObject *names = PyObject_CallMethod(rec, "getMatchNames", NULL);
+        PyObject *seq = PySequence_Fast(names, "getMatchNames() returned "
+                                               "non-sequence object");
+        int nameslen;
+        if (!seq) return NULL;
+        nameslen = PySequence_Fast_GET_SIZE(seq);
+        for (j = 0; j != nameslen; j++) {
+            PyObject *name = PySequence_Fast_GET_ITEM(seq, j);
+            
+            /* lst = recnames.get(name) */
+            lst = PyDict_GetItem(recnames, name);
+
+            /* 
+               if lst:
+                   lst.append(rec)
+               else:
+                   recnames[name] = [rec]
+            */
+            if (lst) {
+                PyList_Append(lst, rec);
+            } else {
+                lst = PyList_New(1);
+                Py_INCREF(rec);
+                PyList_SET_ITEM(lst, 0, rec);
+                PyDict_SetItem(recnames, name, lst);
+                Py_DECREF(lst);
+            }
+        }
+
+        Py_DECREF(names);
+        Py_DECREF(seq);
+    }
+
     /* upgnames = {} */
     upgnames = PyDict_New();
     /* for upg in self._upgrades: */
@@ -3286,6 +3454,56 @@ Cache_linkDeps(CacheObject *self, PyObject *args)
             }
         }
 
+        /* lst = recnames.get(prv.name) */
+        lst = PyDict_GetItem(recnames, prv->name);
+
+        /* if lst: */
+        if (lst) {
+            /* for rec in lst: */
+            int reclen = PyList_GET_SIZE(lst);
+            for (j = 0; j != reclen; j++) {
+                DependsObject *rec = (DependsObject *)PyList_GET_ITEM(lst, j);
+                /* if rec.matches(prv): */
+                PyObject *ret = PyObject_CallMethod((PyObject *)rec, "matches",
+                                                    "O", (PyObject *)prv);
+                if (!ret) return NULL;
+                if (PyObject_IsTrue(ret)) {
+                    /*
+                       if rec.providedby:
+                           rec.providedby.append(prv)
+                       else:
+                           rec.providedby = [prv]
+                    */
+                    if (PyList_Check(rec->providedby)) {
+                        PyList_Append(rec->providedby, (PyObject *)prv);
+                    } else {
+                        PyObject *_lst = PyList_New(1);
+                        Py_INCREF(prv);
+                        PyList_SET_ITEM(_lst, 0, (PyObject *)prv);
+                        Py_DECREF(rec->providedby);
+                        rec->providedby = _lst;
+                    }
+
+                    /*
+                       if prv.recommendedby:
+                           prv.recommendedby.append(prv)
+                       else:
+                           prv.recommendedby = [prv]
+                    */
+                    if (PyList_Check(prv->recommendedby)) {
+                        PyList_Append(prv->recommendedby, (PyObject *)rec);
+                    } else {
+                        PyObject *_lst = PyList_New(1);
+                        Py_INCREF(rec);
+                        PyList_SET_ITEM(_lst, 0, (PyObject *)rec);
+                        Py_DECREF(prv->recommendedby);
+                        prv->recommendedby = _lst;
+                    }
+                }
+                Py_DECREF(ret);
+            }
+        }
+
         /* lst = upgnames.get(prv.name) */
         lst = PyDict_GetItem(upgnames, prv->name);
 
@@ -3821,6 +4094,21 @@ Cache__setstate__(CacheObject *self, PyObject *state)
         }
 
         /*
+           for rec in pkg.recommends:
+               rec.packages.append(pkg)
+               recommends[rec] = True
+        */
+        if (PyList_Check(pkgobj->recommends)) {
+            jlen = PyList_GET_SIZE(pkgobj->recommends);
+            for (j = 0; j != jlen; j++) {
+                PyObject *rec = PyList_GET_ITEM(pkgobj->recommends, j);
+                DependsObject *recobj = (DependsObject *)rec;
+                PyList_Append(recobj->packages, pkg);
+                PyDict_SetItem(recommends, rec, Py_True);
+            }
+        }
+
+        /*
            for upg in pkg.upgrades:
                upg.packages.append(pkg)
                upgrades[upg] = True
diff --git a/smart/commands/query.py b/smart/commands/query.py
index 9265cd9..b6f5697 100644
--- a/smart/commands/query.py
+++ b/smart/commands/query.py
@@ -750,6 +750,22 @@ class TextOutput(NullOutput):
             name = str(prvpkg)
         print "       ", "%s (%s)" % (name, prv)
 
+    def showRecommends(self, pkg, rec):
+        if self._firstrecommends:
+            self._firstrecommends = False
+            print " ", _("Recommends:")
+        print "   ", rec
+
+    def showRecommendsProvidedBy(self, pkg, req, prv, prvpkg):
+        if self._firstrecommendsprovidedby:
+            self._firstrecommendsprovidedby = False
+            print "     ", _("Provided By:")
+        if self.opts.hide_version:
+            name = prvpkg.name
+        else:
+            name = str(prvpkg)
+        print "       ", "%s (%s)" % (name, prv)
+
     def showUpgrades(self, pkg, upg):
         if self._firstupgrades:
             self._firstupgrades = False
