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 0489e11..b9e9cb2 100644
--- a/smart/backends/rpm/base.py
+++ b/smart/backends/rpm/base.py
@@ -198,6 +198,29 @@ 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
         return True
 
     def coexists(self, other):
diff --git a/smart/backends/rpm/header.py b/smart/backends/rpm/header.py
index 31786cc..4880f43 100644
--- a/smart/backends/rpm/header.py
+++ b/smart/backends/rpm/header.py
@@ -292,6 +292,7 @@ class RPMHeaderLoader(Loader):
                     f = [0]
                 elif type(f) != list:
                     f = [f]
+                recdict = {}
                 reqdict = {}
                 for i in range(len(n)):
                     ni = n[i]
@@ -308,10 +309,16 @@ class RPMHeaderLoader(Loader):
                             # RPMSENSE_SCRIPT_PREUN |
                             # RPMSENSE_SCRIPT_POST |
                             # RPMSENSE_SCRIPT_POSTUN == 7744
-                            reqdict[(f[i]&7744 and PreReq or Req,
-                                     intern(ni), r, vi)] = True
+                            if (f[i]&rpm.RPMSENSE_MISSINGOK):
+                                recdict[(f[i]&7744 and PreReq or Req,
+                                         intern(ni), r, vi)] = True
+                            else:
+                                reqdict[(f[i]&7744 and PreReq or Req,
+                                         intern(ni), r, vi)] = True
+                recargs = collapse_libc_requires(recdict.keys())
                 reqargs = collapse_libc_requires(reqdict.keys())
             else:
+                recargs = None
                 reqargs = None
 
             n = h[1054] # RPMTAG_CONFLICTNAME
@@ -365,7 +372,7 @@ class RPMHeaderLoader(Loader):
                 versionarch = "%s@%s" % (distversion, arch)
 
             pkg = self.buildPackage((Pkg, name, versionarch),
-                                    prvargs, reqargs, upgargs, cnfargs)
+                                    prvargs, reqargs, upgargs, cnfargs, recargs)
             pkg.loaders[self] = offset
             self._offsets[offset] = pkg
             self._groups[pkg] = intern(h[rpm.RPMTAG_GROUP])
@@ -583,8 +590,8 @@ class URPMILoader(RPMHeaderListLoader):
     def setErrataFlags(self, flagdict):
         self._flagdict = flagdict
     
-    def buildPackage(self, pkgargs, prvargs, reqargs, upgargs, cnfargs):
-        pkg = Loader.buildPackage(self, pkgargs, prvargs, reqargs, upgargs, cnfargs)
+    def buildPackage(self, pkgargs, prvargs, reqargs, upgargs, cnfargs, recargs):
+        pkg = Loader.buildPackage(self, pkgargs, prvargs, reqargs, upgargs, cnfargs, recargs)
         name = pkgargs[1]
         if hasattr(self, '_flagdict') and self._flagdict and name in self._flagdict:
             if sysconf.getReadOnly():
diff --git a/smart/backends/rpm/metadata.py b/smart/backends/rpm/metadata.py
index 2c54f39..568fe06 100644
--- a/smart/backends/rpm/metadata.py
+++ b/smart/backends/rpm/metadata.py
@@ -165,6 +165,7 @@ class RPMMetaDataLoader(Loader):
         distepoch = None
         info = {}
         reqdict = {}
+        recdict = {}
         prvdict = {}
         upgdict = {}
         cnfdict = {}
@@ -287,12 +288,16 @@ class RPMMetaDataLoader(Loader):
 
                     lasttag = queue[-1].tag
                     if lasttag == REQUIRES:
-                        if elem.get("pre") == "1":
-                            reqdict[(RPMPreRequires,
-                                     ename, erelation, eversion)] = True
+                        if elem.get("missingok") == "1":
+                            recdict[(RPMRequires,
+                                    ename, erelation, eversion)] = True
                         else:
-                            reqdict[(RPMRequires,
-                                     ename, erelation, eversion)] = True
+                            if elem.get("pre") == "1":
+                                reqdict[(RPMPreRequires,
+                                        ename, erelation, eversion)] = True
+                            else:
+                                reqdict[(RPMRequires,
+                                        ename, erelation, eversion)] = True
 
                     elif lasttag == PROVIDES:
                         if ename[0] == "/":
@@ -328,6 +333,12 @@ class RPMMetaDataLoader(Loader):
                                        (RPMProvides, x[1], x[3]) in prvdict or
                                        system_provides.match(*x[:3]))]
                     reqargs = collapse_libc_requires(reqargs)
+
+                    recargs = [x for x in recdict
+                               if not ((x[2] is None or "=" in x[2]) and
+                                       (RPMProvides, x[1], x[3]) in prvdict or
+                                       system_provides.match(*x[:3]))]
+
                     prvargs = prvdict.keys()
                     cnfargs = cnfdict.keys()
                     upgargs = upgdict.keys()
@@ -339,7 +350,7 @@ class RPMMetaDataLoader(Loader):
                         versionarch = "%s@%s" % (distversion, arch)
 
                     pkg = self.buildPackage((RPMPackage, name, versionarch),
-                                            prvargs, reqargs, upgargs, cnfargs)
+                                            prvargs, reqargs, upgargs, cnfargs, recargs)
                     pkg.loaders[self] = info
 
                     # Store the provided files for future usage.
@@ -362,6 +373,7 @@ class RPMMetaDataLoader(Loader):
                     distepoch = None
                     pkgid = None
                     reqdict.clear()
+                    recdict.clear()
                     prvdict.clear()
                     upgdict.clear()
                     cnfdict.clear()
diff --git a/smart/cache.py b/smart/cache.py
index b829825..cec8bb3 100644
--- a/smart/cache.py
+++ b/smart/cache.py
@@ -32,7 +32,8 @@ class Package(object):
         self.name = name
         self.version = version
         self.provides = ()
-        self.requires = ()
+        self.requires = []
+        self.recommends = []
         self.upgrades = ()
         self.conflicts = ()
         self.installed = False
@@ -55,7 +56,9 @@ class Package(object):
             fk([x for x in self.provides if x.name[0] != "/"]) !=
             fk([x for x in other.provides if x.name[0] != "/"]) or
             fk([x for x in self.requires if x.name[0] != "/"]) !=
-            fk([x for x in other.requires if x.name[0] != "/"])):
+            fk([x for x in other.requires if x.name[0] != "/"]) or
+            fk([x for x in self.recommends if x.name[0] != "/"]) !=
+            fk([x for x in other.recommends if x.name[0] != "/"])):
             return False
         return True
 
@@ -110,6 +113,7 @@ class Package(object):
                 self.version,
                 self.provides,
                 self.requires,
+                self.recommends,
                 self.upgrades,
                 self.conflicts,
                 self.installed,
@@ -122,6 +126,7 @@ class Package(object):
          self.version,
          self.provides,
          self.requires,
+         self.recommends,
          self.upgrades,
          self.conflicts,
          self.installed,
@@ -274,6 +279,7 @@ class Provides(object):
         self.version = version
         self.packages = []
         self.requiredby = ()
+        self.recommendedby = ()
         self.upgradedby = ()
         self.conflictedby = ()
 
@@ -401,7 +407,7 @@ class Loader(object):
     def loadFileProvides(self, fndict):
         pass
 
-    def buildPackage(self, pkgargs, prvargs, reqargs, upgargs, cnfargs):
+    def buildPackage(self, pkgargs, prvargs, reqargs, upgargs, cnfargs, recargs = None):
         cache = self._cache
         pkg = pkgargs[0](*pkgargs[1:])
         relpkgs = []
@@ -427,6 +433,17 @@ class Loader(object):
                 relpkgs.append(req.packages)
                 pkg.requires.append(req)
 
+        if recargs:
+            pkg.recommends = []
+            for args in recargs:
+                rec = cache._objmap.get(args)
+                if not rec:
+                    rec = args[0](*args[1:])
+                    cache._objmap[args] = rec
+                    cache._recommends.append(rec)
+                relpkgs.append(rec.packages)
+                pkg.recommends.append(rec)
+
         if upgargs:
             pkg.upgrades = []
             for args in upgargs:
@@ -572,6 +589,7 @@ class Cache(object):
         self._packages = []
         self._provides = []
         self._requires = []
+        self._recommends = []
         self._upgrades = []
         self._conflicts = []
         self._objmap = {}
@@ -581,6 +599,8 @@ class Cache(object):
             del prv.packages[:]
             if prv.requiredby:
                 del prv.requiredby[:]
+            if prv.recommendedby:
+                del prv.recommendedby[:]
             if prv.upgradedby:
                 del prv.upgradedby[:]
             if prv.conflictedby:
@@ -589,6 +609,10 @@ class Cache(object):
             del req.packages[:]
             if req.providedby:
                 del req.providedby[:]
+        for rec in self._recommends:
+            del rec.packages[:]
+            if rec.providedby:
+                del rec.providedby[:]
         for upg in self._upgrades:
             del upg.packages[:]
             if upg.providedby:
@@ -600,6 +624,7 @@ class Cache(object):
         del self._packages[:]
         del self._provides[:]
         del self._requires[:]
+        del self._recommends[:]
         del self._upgrades[:]
         del self._conflicts[:]
         self._objmap.clear()
@@ -621,6 +646,7 @@ class Cache(object):
         packages = {}
         provides = {}
         requires = {}
+        recommends = {}
         upgrades = {}
         conflicts = {}
         objmap = self._objmap
@@ -646,6 +672,11 @@ class Cache(object):
                         if req not in requires:
                             objmap[req.getInitArgs()] = req
                             requires[req] = True
+                    for rec in pkg.recommends[:]:
+                        rec.packages.append(pkg)
+                        if rec not in recommends:
+                            objmap[rec.getInitArgs()] = rec
+                            recommends[rec] = True
                     for upg in pkg.upgrades:
                         upg.packages.append(pkg)
                         if upg not in upgrades:
@@ -659,6 +690,7 @@ class Cache(object):
         self._packages[:] = packages.keys()
         self._provides[:] = provides.keys()
         self._requires[:] = requires.keys()
+        self._recommends[:] = recommends.keys()
         self._upgrades[:] = upgrades.keys()
         self._conflicts[:] = conflicts.keys()
 
@@ -710,6 +742,14 @@ class Cache(object):
                     lst.append(req)
                 else:
                     reqnames[name] = [req]
+        recnames = {}
+        for rec in self._recommends:
+            for name in rec.getMatchNames():
+                lst = recnames.get(name)
+                if lst:
+                    lst.append(rec)
+                else:
+                    recnames[name] = [rec]
         upgnames = {}
         for upg in self._upgrades:
             for name in upg.getMatchNames():
@@ -739,6 +779,18 @@ class Cache(object):
                             prv.requiredby.append(req)
                         else:
                             prv.requiredby = [req]
+            lst = recnames.get(prv.name)
+            if lst:
+                for rec in lst:
+                    if rec.matches(prv):
+                        if rec.providedby:
+                            rec.providedby.append(prv)
+                        else:
+                            rec.providedby = [prv]
+                        if prv.recommendedby:
+                            prv.recommendedby.append(rec)
+                        else:
+                            prv.recommendedby = [rec]
             lst = upgnames.get(prv.name)
             if lst:
                 for upg in lst:
@@ -782,6 +834,12 @@ class Cache(object):
         else:
             return [x for x in self._requires if x.name == name]
 
+    def getRecommends(self, name=None):
+        if not name:
+            return self._recommends
+        else:
+            return [x for x in self._recommends if x.name == name]
+
     def getUpgrades(self, name=None):
         if not name:
             return self._upgrades
@@ -807,6 +865,12 @@ class Cache(object):
                 for req in self._requires:
                     if prvname in req.getMatchNames() and req.matches(prv):
                         searcher.addResult(req)
+        if searcher.recommends:
+            for prv in searcher.recommends:
+                prvname = prv.name
+                for req in self._recommends:
+                    if prvname in req.getMatchNames() and req.matches(prv):
+                        searcher.addResult(req)
         if searcher.upgrades:
             for prv in searcher.upgrades:
                 prvname = prv.name
@@ -839,6 +903,7 @@ class Cache(object):
         self._packages = state["_packages"]
         provides = {}
         requires = {}
+        recommends = {}
         upgrades = {}
         conflicts = {}
         for pkg in self._packages:
@@ -848,6 +913,9 @@ class Cache(object):
             for req in pkg.requires:
                 req.packages.append(pkg)
                 requires[req] = True
+            for rec in pkg.recommends:
+                rec.packages.append(pkg)
+                recommends[rec] = True
             for upg in pkg.upgrades:
                 upg.packages.append(pkg)
                 upgrades[upg] = True
@@ -856,6 +924,7 @@ class Cache(object):
                 conflicts[cnf] = True
         self._provides = provides.keys()
         self._requires = requires.keys()
+        self._recommends = recommends.keys()
         self._upgrades = upgrades.keys()
         self._conflicts = conflicts.keys()
         self._objmap = {}
diff --git a/smart/ccache.c b/smart/ccache.c
index 7541e26..7193185 100644
--- a/smart/ccache.c
+++ b/smart/ccache.c
@@ -82,6 +82,7 @@ typedef struct {
     PyObject *version;
     PyObject *provides;
     PyObject *requires;
+    PyObject *recommends;
     PyObject *upgrades;
     PyObject *conflicts;
     PyObject *installed;
@@ -96,6 +97,7 @@ typedef struct {
     PyObject *version;
     PyObject *packages;
     PyObject *requiredby;
+    PyObject *recommendedby;
     PyObject *upgradedby;
     PyObject *conflictedby;
 } ProvidesObject;
@@ -123,6 +125,7 @@ typedef struct {
     PyObject *_packages;
     PyObject *_provides;
     PyObject *_requires;
+    PyObject *_recommends;
     PyObject *_upgrades;
     PyObject *_conflicts;
     PyObject *_objmap;
@@ -211,7 +214,8 @@ Package_init(PackageObject *self, PyObject *args)
     Py_INCREF(self->name);
     Py_INCREF(self->version);
     self->provides = PyTuple_New(0);
-    self->requires = PyTuple_New(0);
+    self->requires = PyList_New(0);
+    self->recommends = PyList_New(0);
     self->upgrades = PyTuple_New(0);
     self->conflicts = PyTuple_New(0);
     Py_INCREF(Py_False);
@@ -228,6 +232,7 @@ Package_traverse(PackageObject *self, visitproc visit, void *arg)
 {
     Py_VISIT(self->provides);
     Py_VISIT(self->requires);
+    Py_VISIT(self->recommends);
     Py_VISIT(self->upgrades);
     Py_VISIT(self->conflicts);
     Py_VISIT(self->loaders);
@@ -239,6 +244,7 @@ Package_clear(PackageObject *self)
 {
     Py_CLEAR(self->provides);
     Py_CLEAR(self->requires);
+    Py_CLEAR(self->recommends);
     Py_CLEAR(self->upgrades);
     Py_CLEAR(self->conflicts);
     Py_CLEAR(self->loaders);
@@ -252,6 +258,7 @@ Package_dealloc(PackageObject *self)
     Py_XDECREF(self->version);
     Py_XDECREF(self->provides);
     Py_XDECREF(self->requires);
+    Py_XDECREF(self->recommends);
     Py_XDECREF(self->upgrades);
     Py_XDECREF(self->conflicts);
     Py_XDECREF(self->installed);
@@ -453,6 +460,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;
@@ -606,13 +653,14 @@ Package_getPriority(PackageObject *self, PyObject *args)
 static PyObject *
 Package__getstate__(PackageObject *self, PyObject *args)
 {
-    PyObject *state = PyTuple_New(10);
+    PyObject *state = PyTuple_New(11);
     if (!state) return NULL;
 
     Py_INCREF(self->name);
     Py_INCREF(self->version);
     Py_INCREF(self->provides);
     Py_INCREF(self->requires);
+    Py_INCREF(self->recommends);
     Py_INCREF(self->upgrades);
     Py_INCREF(self->conflicts);
     Py_INCREF(self->installed);
@@ -620,16 +668,17 @@ Package__getstate__(PackageObject *self, PyObject *args)
     Py_INCREF(self->priority);
     Py_INCREF(self->loaders);
 
-    PyTuple_SET_ITEM(state, 0, self->name);
-    PyTuple_SET_ITEM(state, 1, self->version);
-    PyTuple_SET_ITEM(state, 2, self->provides);
-    PyTuple_SET_ITEM(state, 3, self->requires);
-    PyTuple_SET_ITEM(state, 4, self->upgrades);
-    PyTuple_SET_ITEM(state, 5, self->conflicts);
-    PyTuple_SET_ITEM(state, 6, self->installed);
-    PyTuple_SET_ITEM(state, 7, self->essential);
-    PyTuple_SET_ITEM(state, 8, self->priority);
-    PyTuple_SET_ITEM(state, 9, self->loaders);
+    PyTuple_SET_ITEM(state,  0, self->name);
+    PyTuple_SET_ITEM(state,  1, self->version);
+    PyTuple_SET_ITEM(state,  2, self->provides);
+    PyTuple_SET_ITEM(state,  3, self->requires);
+    PyTuple_SET_ITEM(state,  4, self->recommends);
+    PyTuple_SET_ITEM(state,  5, self->upgrades);
+    PyTuple_SET_ITEM(state,  6, self->conflicts);
+    PyTuple_SET_ITEM(state,  7, self->installed);
+    PyTuple_SET_ITEM(state,  8, self->essential);
+    PyTuple_SET_ITEM(state,  9, self->priority);
+    PyTuple_SET_ITEM(state, 10, self->loaders);
 
     return state;
 }
@@ -637,7 +686,7 @@ Package__getstate__(PackageObject *self, PyObject *args)
 static PyObject *
 Package__setstate__(PackageObject *self, PyObject *state)
 {
-    if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 10) {
+    if (!PyTuple_Check(state) || PyTuple_GET_SIZE(state) != 11) {
         PyErr_SetString(StateVersionError, "");
         return NULL;
     }
@@ -645,18 +694,20 @@ Package__setstate__(PackageObject *self, PyObject *state)
     self->version = PyTuple_GET_ITEM(state, 1);
     self->provides = PyTuple_GET_ITEM(state, 2);
     self->requires = PyTuple_GET_ITEM(state, 3);
-    self->upgrades = PyTuple_GET_ITEM(state, 4);
-    self->conflicts = PyTuple_GET_ITEM(state, 5);
-    self->installed = PyTuple_GET_ITEM(state, 6);
-    self->essential = PyTuple_GET_ITEM(state, 7);
-    self->priority = PyTuple_GET_ITEM(state, 8);
-    self->loaders = PyTuple_GET_ITEM(state, 9);
+    self->recommends = PyTuple_GET_ITEM(state, 4);
+    self->upgrades = PyTuple_GET_ITEM(state, 5);
+    self->conflicts = PyTuple_GET_ITEM(state, 6);
+    self->installed = PyTuple_GET_ITEM(state, 7);
+    self->essential = PyTuple_GET_ITEM(state, 8);
+    self->priority = PyTuple_GET_ITEM(state, 9);
+    self->loaders = PyTuple_GET_ITEM(state, 10);
 
 
     Py_INCREF(self->name);
     Py_INCREF(self->version);
     Py_INCREF(self->provides);
     Py_INCREF(self->requires);
+    Py_INCREF(self->recommends);
     Py_INCREF(self->upgrades);
     Py_INCREF(self->conflicts);
     Py_INCREF(self->installed);
@@ -686,6 +737,7 @@ static PyMemberDef Package_members[] = {
     {"version", T_OBJECT, OFF(version), 0, 0},
     {"provides", T_OBJECT, OFF(provides), 0, 0},
     {"requires", T_OBJECT, OFF(requires), 0, 0},
+    {"recommends", T_OBJECT, OFF(recommends), 0, 0},
     {"upgrades", T_OBJECT, OFF(upgrades), 0, 0},
     {"conflicts", T_OBJECT, OFF(conflicts), 0, 0},
     {"installed", T_OBJECT, OFF(installed), 0, 0},
@@ -750,6 +802,7 @@ Provides_init(ProvidesObject *self, PyObject *args)
     Py_INCREF(self->version);
     self->packages = PyList_New(0);
     self->requiredby = PyTuple_New(0);
+    self->recommendedby = PyTuple_New(0);
     self->upgradedby = PyTuple_New(0);
     self->conflictedby = PyTuple_New(0);
     return 0;
@@ -760,6 +813,7 @@ Provides_traverse(ProvidesObject *self, visitproc visit, void *arg)
 {
     Py_VISIT(self->packages);
     Py_VISIT(self->requiredby);
+    Py_VISIT(self->recommendedby);
     Py_VISIT(self->upgradedby);
     Py_VISIT(self->conflictedby);
     return 0;
@@ -770,6 +824,7 @@ Provides_clear(ProvidesObject *self)
 {
     Py_CLEAR(self->packages);
     Py_CLEAR(self->requiredby);
+    Py_CLEAR(self->recommendedby);
     Py_CLEAR(self->upgradedby);
     Py_CLEAR(self->conflictedby);
     return 0;
@@ -782,6 +837,7 @@ Provides_dealloc(ProvidesObject *self)
     Py_XDECREF(self->version);
     Py_XDECREF(self->packages);
     Py_XDECREF(self->requiredby);
+    Py_XDECREF(self->recommendedby);
     Py_XDECREF(self->upgradedby);
     Py_XDECREF(self->conflictedby);
     self->ob_type->tp_free((PyObject *)self);
@@ -960,6 +1016,7 @@ static PyMemberDef Provides_members[] = {
     {"version", T_OBJECT, OFF(version), 0, 0},
     {"packages", T_OBJECT, OFF(packages), 0, 0},
     {"requiredby", T_OBJECT, OFF(requiredby), 0, 0},
+    {"recommendedby", T_OBJECT, OFF(recommendedby), 0, 0},
     {"upgradedby", T_OBJECT, OFF(upgradedby), 0, 0},
     {"conflictedby", T_OBJECT, OFF(conflictedby), 0, 0},
     {NULL}
@@ -1555,6 +1612,7 @@ Loader_buildPackage(LoaderObject *self, PyObject *args)
     PyObject *reqargs;
     PyObject *upgargs;
     PyObject *cnfargs;
+    PyObject *recargs = NULL;
     PyObject *callargs;
     
     PyObject *pkg;
@@ -1574,9 +1632,10 @@ Loader_buildPackage(LoaderObject *self, PyObject *args)
 
     cache = (CacheObject *)self->_cache;
 
-    if (!PyArg_ParseTuple(args, "O!O&O&O&O&", &PyTuple_Type, &pkgargs,
+    if (!PyArg_ParseTuple(args, "O!O&O&O&O&|O&", &PyTuple_Type, &pkgargs,
                           mylist, &prvargs, mylist, &reqargs,
-                          mylist, &upgargs, mylist, &cnfargs))
+                          mylist, &upgargs, mylist, &cnfargs,
+                          mylist, &recargs))
         return NULL;
 
     if (PyTuple_GET_SIZE(pkgargs) < 2) {
@@ -1701,6 +1760,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;
@@ -2391,6 +2503,7 @@ Cache_init(CacheObject *self, PyObject *args)
     self->_packages = PyList_New(0);
     self->_provides = PyList_New(0);
     self->_requires = PyList_New(0);
+    self->_recommends = PyList_New(0);
     self->_upgrades = PyList_New(0);
     self->_conflicts = PyList_New(0);
     self->_objmap = PyDict_New();
@@ -2404,6 +2517,7 @@ Cache_traverse(CacheObject *self, visitproc visit, void *arg)
     Py_VISIT(self->_packages);
     Py_VISIT(self->_provides);
     Py_VISIT(self->_requires);
+    Py_VISIT(self->_recommends);
     Py_VISIT(self->_upgrades);
     Py_VISIT(self->_conflicts);
     Py_VISIT(self->_objmap);
@@ -2417,6 +2531,7 @@ Cache_clear(CacheObject *self)
     Py_CLEAR(self->_packages);
     Py_CLEAR(self->_provides);
     Py_CLEAR(self->_requires);
+    Py_CLEAR(self->_recommends);
     Py_CLEAR(self->_upgrades);
     Py_CLEAR(self->_conflicts);
     Py_CLEAR(self->_objmap);
@@ -2430,6 +2545,7 @@ Cache_dealloc(CacheObject *self)
     Py_XDECREF(self->_packages);
     Py_XDECREF(self->_provides);
     Py_XDECREF(self->_requires);
+    Py_XDECREF(self->_recommends);
     Py_XDECREF(self->_upgrades);
     Py_XDECREF(self->_conflicts);
     Py_XDECREF(self->_objmap);
@@ -2449,6 +2565,8 @@ Cache_reset(CacheObject *self, PyObject *args)
         LIST_CLEAR(prvobj->packages);
         if (PyList_Check(prvobj->requiredby))
             LIST_CLEAR(prvobj->requiredby);
+        if (PyList_Check(prvobj->recommendedby))
+            LIST_CLEAR(prvobj->recommendedby);
         if (PyList_Check(prvobj->upgradedby))
             LIST_CLEAR(prvobj->upgradedby);
         if (PyList_Check(prvobj->conflictedby))
@@ -2464,6 +2582,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;
@@ -2487,6 +2615,7 @@ Cache_reset(CacheObject *self, PyObject *args)
     LIST_CLEAR(self->_packages);
     LIST_CLEAR(self->_provides);
     LIST_CLEAR(self->_requires);
+    LIST_CLEAR(self->_recommends);
     LIST_CLEAR(self->_upgrades);
     LIST_CLEAR(self->_conflicts);
     PyDict_Clear(self->_objmap);
@@ -2534,6 +2663,7 @@ Cache__reload(CacheObject *self, PyObject *args)
       packages = {}
       provides = {}
       requires = {}
+      recommends = {}
       upgrades = {}
       conflicts = {}
       objmap = self._objmap
@@ -2541,11 +2671,12 @@ Cache__reload(CacheObject *self, PyObject *args)
     PyObject *packages = PyDict_New();
     PyObject *provides = PyDict_New();
     PyObject *requires = PyDict_New();
+    PyObject *recommends = PyDict_New();
     PyObject *upgrades = PyDict_New();
     PyObject *conflicts = PyDict_New();
     PyObject *objmap = self->_objmap;
     int i, ilen;
-    if (!packages || !provides || !requires || !conflicts)
+    if (!packages || !provides || !requires || !recommends || !conflicts )
         return NULL;
 
     /* for loader in loaders: */
@@ -2679,6 +2810,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:
@@ -2747,6 +2902,11 @@ Cache__reload(CacheObject *self, PyObject *args)
     self->_requires = PyDict_Keys(requires);
     Py_DECREF(requires);
 
+    /* self._recommends[:] = recommends.keys() */
+    Py_DECREF(self->_recommends);
+    self->_recommends = PyDict_Keys(recommends);
+    Py_DECREF(recommends);
+
     /* self._upgrades[:] = upgrades.keys() */
     Py_DECREF(self->_upgrades);
     self->_upgrades = PyDict_Keys(upgrades);
@@ -2852,7 +3012,7 @@ PyObject *
 Cache_linkDeps(CacheObject *self, PyObject *args)
 {
     int i, j, len;
-    PyObject *reqnames, *upgnames, *cnfnames;
+    PyObject *reqnames, *recnames, *upgnames, *cnfnames;
     PyObject *lst;
 
     /* reqnames = {} */
@@ -2896,6 +3056,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: */
@@ -3035,6 +3236,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);
 
@@ -3139,6 +3390,7 @@ Cache_linkDeps(CacheObject *self, PyObject *args)
     }
 
     Py_DECREF(reqnames);
+    Py_DECREF(recnames);
     Py_DECREF(upgnames);
     Py_DECREF(cnfnames);
 
@@ -3215,6 +3467,29 @@ Cache_getRequires(CacheObject *self, PyObject *args)
 }
 
 PyObject *
+Cache_getRecommends(CacheObject *self, PyObject *args)
+{
+    const char *name = NULL;
+    PyObject *lst;
+    int i, len;
+    if (!PyArg_ParseTuple(args, "|s", &name))
+        return NULL;
+    if (!name) {
+        Py_INCREF(self->_recommends);
+        return self->_recommends;
+    }
+    lst = PyList_New(0);
+    len = PyList_GET_SIZE(self->_recommends);
+    for (i = 0; i != len; i++) {
+        DependsObject *rec =
+            (DependsObject*)PyList_GET_ITEM(self->_recommends, i);
+        if (strcmp(STR(rec->name), name) == 0)
+            PyList_Append(lst, (PyObject *)rec);
+    }
+    return lst;
+}
+
+PyObject *
 Cache_getUpgrades(CacheObject *self, PyObject *args)
 {
     const char *name = NULL;
@@ -3324,6 +3599,38 @@ Cache_search(CacheObject *self, PyObject *searcher)
     }
     Py_DECREF(lst);
 
+    lst = PyObject_GetAttrString(searcher, "recommends");
+    if (lst == NULL || !PyList_Check(lst)) {
+        PyErr_SetString(PyExc_TypeError, "Invalid recommends attribute");
+        return NULL;
+    }
+    for (i = 0; i != PyList_GET_SIZE(lst); i++) {
+        ProvidesObject *prv = (ProvidesObject *)PyList_GET_ITEM(lst, i);
+        for (j = 0; j != PyList_GET_SIZE(self->_recommends); j++) {
+            PyObject *rec = PyList_GET_ITEM(self->_recommends, j);
+            PyObject *names = PyObject_CallMethod(rec, "getMatchNames", NULL);
+            PyObject *seq = PySequence_Fast(names, "getMatchNames() returned "
+                                                   "non-sequence object");
+            if (seq == NULL) return NULL;
+            for (k = 0; k != PySequence_Fast_GET_SIZE(seq); k++) {
+                if (strcmp(PyString_AS_STRING(PySequence_Fast_GET_ITEM(seq, k)),
+                           PyString_AS_STRING(prv->name)) == 0) {
+                    res = PyObject_CallMethod(rec, "matches", "O", prv);
+                    if (res == NULL)
+                        return NULL;
+                    if (PyObject_IsTrue(res))
+                        CALLMETHOD(searcher, "addResult", "O", rec);
+                    Py_DECREF(res);
+                    break;
+                }
+            }
+
+            Py_DECREF(names);
+            Py_DECREF(seq);
+        }
+    }
+    Py_DECREF(lst);
+
     lst = PyObject_GetAttrString(searcher, "upgrades");
     if (lst == NULL || !PyList_Check(lst)) {
         PyErr_SetString(PyExc_TypeError, "Invalid upgrades attribute");
@@ -3420,7 +3727,7 @@ Cache__getstate__(CacheObject *self, PyObject *args)
 static PyObject *
 Cache__setstate__(CacheObject *self, PyObject *state)
 {
-    PyObject *provides, *requires, *upgrades, *conflicts;
+    PyObject *provides, *requires, *recommends, *upgrades, *conflicts;
     int i, ilen;
     int j, jlen;
     
@@ -3452,11 +3759,13 @@ Cache__setstate__(CacheObject *self, PyObject *state)
     /*
        provides = {}
        requires = {}
+       recommends = {}
        upgrades = {}
        conflicts = {}
     */
     provides = PyDict_New();
     requires = PyDict_New();
+    recommends = PyDict_New();
     upgrades = PyDict_New();
     conflicts = PyDict_New();
 
@@ -3497,6 +3806,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
@@ -3525,6 +3849,7 @@ Cache__setstate__(CacheObject *self, PyObject *state)
                 PyDict_SetItem(conflicts, cnf, Py_True);
             }
         }
+
     }
 
     /* self._provides = provides.keys() */
@@ -3535,6 +3860,10 @@ Cache__setstate__(CacheObject *self, PyObject *state)
     self->_requires = PyDict_Keys(requires);
     Py_DECREF(requires);
 
+    /* self._recommends = recommends.keys() */
+    self->_recommends = PyDict_Keys(recommends);
+    Py_DECREF(recommends);
+
     /* self._upgrades = upgrades.keys() */
     self->_upgrades = PyDict_Keys(upgrades);
     Py_DECREF(upgrades);
@@ -3562,6 +3891,7 @@ static PyMethodDef Cache_methods[] = {
     {"getPackages", (PyCFunction)Cache_getPackages, METH_VARARGS, NULL},
     {"getProvides", (PyCFunction)Cache_getProvides, METH_VARARGS, NULL},
     {"getRequires", (PyCFunction)Cache_getRequires, METH_VARARGS, NULL},
+    {"getRecommends", (PyCFunction)Cache_getRecommends, METH_VARARGS, NULL},
     {"getUpgrades", (PyCFunction)Cache_getUpgrades, METH_VARARGS, NULL},
     {"getConflicts", (PyCFunction)Cache_getConflicts, METH_VARARGS, NULL},
     {"search", (PyCFunction)Cache_search, METH_O, NULL},
@@ -3576,6 +3906,7 @@ static PyMemberDef Cache_members[] = {
     {"_packages", T_OBJECT, OFF(_packages), RO, 0},
     {"_provides", T_OBJECT, OFF(_provides), RO, 0},
     {"_requires", T_OBJECT, OFF(_requires), RO, 0},
+    {"_recommends", T_OBJECT, OFF(_recommends), RO, 0},
     {"_upgrades", T_OBJECT, OFF(_upgrades), RO, 0},
     {"_conflicts", T_OBJECT, OFF(_conflicts), RO, 0},
     {"_objmap", T_OBJECT, OFF(_objmap), RO, 0},
diff --git a/smart/commands/query.py b/smart/commands/query.py
index 808e53a..9265cd9 100644
--- a/smart/commands/query.py
+++ b/smart/commands/query.py
@@ -107,6 +107,8 @@ def option_parser(**kwargs):
                       help=_("show requires for the given packages"))
     parser.add_option("--show-prerequires", action="store_true",
                       help=_("show requires selecting only pre-dependencies"))
+    parser.add_option("--show-recommends", action="store_true",
+                      help=_("show recommends for the given packages"))
     parser.add_option("--show-upgrades", action="store_true",
                       help=_("show upgrades for the given packages"))
     parser.add_option("--show-conflicts", action="store_true",
@@ -488,6 +490,19 @@ def main(ctrl, opts, reloadchannels=True):
                                 continue
                             output.showRequiresProvidedBy(pkg, req,
                                                           prv, prvpkg)
+        if pkg.recommends and (opts.show_recommends):
+            pkg.recommends.sort()
+            first = True
+            for req in pkg.recommends:
+                output.showRecommends(pkg, req)
+                if opts.show_providedby and req.providedby:
+                    for prv in req.providedby:
+                        prv.packages.sort()
+                        for prvpkg in prv.packages:
+                            if opts.installed and not prvpkg.installed:
+                                continue
+                            output.showRecommendsProvidedBy(pkg, req,
+                                                          prv, prvpkg)
         if pkg.upgrades and (opts.show_upgrades or whoupgrades):
             pkg.upgrades.sort()
             first = True
@@ -594,6 +609,12 @@ class NullOutput(object):
     def showRequiresProvidedBy(self, pkg, req, prv, prvpkg):
         pass
 
+    def showRecommends(self, pkg, req):
+        pass
+
+    def showRecommendsProvidedBy(self, pkg, req, prv, prvpkg):
+        pass
+
     def showUpgrades(self, pkg, upg):
         pass
 
@@ -619,6 +640,8 @@ class TextOutput(NullOutput):
         self._firstconflictedby = True
         self._firstrequires = True
         self._firstrequiresprovidedby = True
+        self._firstrecommends = True
+        self._firstrecommendsprovidedby = True
         self._firstupgrades = True
         self._firstupgradesprovidedby = True
         self._firstconflicts = True
@@ -711,6 +734,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
@@ -797,6 +836,18 @@ class GraphVizOutput(NullOutput):
             self._shown[req, prv] = True
             print '    "Requires: %s" -> "Provides: %s";' % (req, prv)
 
+    def showRecommends(self, pkg, req):
+        if (pkg, req) not in self._shown:
+            self._shown[pkg, req] = True
+            print '    "%s" -> "Recommends: %s";' % (pkg, req)
+
+    def showRecommendsProvidedBy(self, pkg, req, prv, prvpkg):
+        self.showPackage(prvpkg)
+        self.showProvides(prvpkg, prv)
+        if (req, prv) not in self._shown:
+            self._shown[req, prv] = True
+            print '    "Recommends: %s" -> "Provides: %s";' % (req, prv)
+
     def showUpgrades(self, pkg, upg):
         if (pkg, upg) not in self._shown:
             self._shown[pkg, upg] = True
diff --git a/smart/control.py b/smart/control.py
index fd7083a..d44abe7 100644
--- a/smart/control.py
+++ b/smart/control.py
@@ -447,7 +447,7 @@ class Control(object):
         queue = marked.keys()
         while queue:
             pkg = queue.pop(0)
-            for req in pkg.requires:
+            for req in pkg.requires + pkg.recommends:
                 for prv in req.providedby:
                     for prvpkg in prv.packages:
                         if (prvpkg.installed and
@@ -794,7 +794,7 @@ class Control(object):
         pkglst = []
         for pkg in changeset:
             n = 0
-            for req in pkg.requires:
+            for req in pkg.requires + pkg.recommends:
                 for prv in req.providedby:
                     for prvpkg in prv.packages:
                         if changeset.get(prvpkg) is INSTALL:
diff --git a/smart/searcher.py b/smart/searcher.py
index 216f4ce..32eb825 100644
--- a/smart/searcher.py
+++ b/smart/searcher.py
@@ -45,9 +45,9 @@ class Searcher(object):
 
     - provides is matched in Provides.search(), for the same reason.
 
-    - requires, upgrades, and conflicts don't have special searching
-      methods. Instead, their usual match() method is given an instance
-      of the Provides type.
+    - requires, recommends, upgrades, and conflicts don't have special
+      searching methods. Instead, their usual match() method is given
+      an instance of the Provides type.
 
     - group, path, url, and other information which is found by
       PackageInfo, is searched by the Loader.search() method and
@@ -62,6 +62,7 @@ class Searcher(object):
         self.nameversion = []
         self.provides = []
         self.requires = []
+        self.recommends = []
         self.upgrades = []
         self.conflicts = []
         self.path = []
@@ -76,6 +77,7 @@ class Searcher(object):
         del self.nameversion[:]
         del self.provides[:]
         del self.requires[:]
+        del self.recommends[:]
         del self.upgrades[:]
         del self.conflicts[:]
         del self.path[:]
@@ -122,6 +124,8 @@ class Searcher(object):
             self.addProvides(s[9:], cutoff)
         elif s.startswith("requires:"):
             self.addRequires(s[9:])
+        elif s.startswith("recommends:"):
+            self.addRecommends(s[11:])
         elif s.startswith("upgrades:"):
             self.addUpgrades(s[9:])
         elif s.startswith("conflicts:"):
@@ -151,6 +155,7 @@ class Searcher(object):
         return s and (
                 s.startswith("provides:") or
                 s.startswith("requires:") or
+                s.startswith("recommends:") or
                 s.startswith("upgrades:") or
                 s.startswith("conflicts:") or
                 s.startswith("url:") or
@@ -182,6 +187,9 @@ class Searcher(object):
     def addRequires(self, s):
         self.requires.append(self._buildProvides(s))
 
+    def addRecommends(self, s):
+        self.recommends.append(self._buildProvides(s))
+
     def addUpgrades(self, s):
         self.upgrades.append(self._buildProvides(s))
 
diff --git a/smart/transaction.py b/smart/transaction.py
index eb320d2..300b9cc 100644
--- a/smart/transaction.py
+++ b/smart/transaction.py
@@ -573,7 +573,7 @@ class Transaction(object):
                 self._remove(namepkg, changeset, locked, pending, depth)
 
         # Install packages required by this one.
-        for req in pkg.requires:
+        for req in pkg.requires + pkg.recommends:
 
             # Check if someone is already providing it.
             prvpkgs = {}
@@ -596,8 +596,12 @@ class Transaction(object):
 
             if not prvpkgs:
                 # No packages provide it at all. Give up.
-                raise Failed, _("Can't install %s: no package provides %s") % \
-                              (pkg, req)
+                if req in pkg.requires:
+                    raise Failed, _("Can't install %s: no package provides %s") % \
+                                (pkg, req)
+                else:
+                    # It's only a recommend, skip
+                    continue
 
             if len(prvpkgs) == 1:
                 # Don't check locked here. prvpkgs was
@@ -1359,7 +1363,7 @@ class ChangeSetSplitter(object):
         set = self._changeset
 
         # Check all dependencies needed by this package.
-        for req in pkg.requires:
+        for req in pkg.requires + pkg.recommends:
 
             # Check if any already installed or to be installed
             # package will solve the problem.
@@ -1424,8 +1428,9 @@ class ChangeSetSplitter(object):
 
             # There are no solutions for the problem.
             # Should we really care about it?
-            if (self._forcerequires or
-                isinstance(req, PreRequires)):
+            if ((self._forcerequires or
+                isinstance(req, PreRequires))
+                and req in pkg.requires):
                 raise Error, _("No providers for '%s', "
                                "required by '%s'") % (req, pkg)
 
@@ -1625,7 +1630,7 @@ def recursiveInternalRequires(pkgmap, pkg, numrel, done=None):
     return n
 
 def forwardRequires(pkg, map):
-    for req in pkg.requires:
+    for req in pkg.requires + pkg.recommends:
         if req not in map:
             map[req] = True
             for prv in req.providedby:
@@ -1794,6 +1799,15 @@ def checkPackages(cache, checkset, relateset, report=False):
                 iface.info(_("Unsatisfied dependency: %s requires %s") %
                            (pkg, req))
 
+        for req in pkg.recommends:
+            for prv in req.providedby:
+                for prvpkg in prv.packages:
+                    if prvpkg in relateset:
+                        break
+                else:
+                    continue
+                break
+
         if not pkg.installed:
             continue
 
-- 
1.7.9.5

