From f3dbcac41a547ee75ad52af82a8ba56f0c83eb7e Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Oct 03 2016 14:52:58 +0000
Subject: [PATCH 1/4] getRPMDeps exposes additional deps


Adding Conflicts, Suggests, Enhances, Supplements and Recommend header fields
in addition to original Requires, Conflicts, Obsoletes and Provides

relates: https://pagure.io/koji/issue/142

---

diff --git a/hub/kojihub.py b/hub/kojihub.py
index be21e1c..f1b4cec 100644
--- a/hub/kojihub.py
+++ b/hub/kojihub.py
@@ -9520,7 +9520,7 @@ class RootExports(object):
 
         results = []
 
-        for dep_name in ['REQUIRE', 'PROVIDE', 'CONFLICT', 'OBSOLETE']:
+        for dep_name in ['REQUIRE', 'PROVIDE', 'CONFLICT', 'OBSOLETE', 'SUGGEST', 'ENHANCE', 'SUPPLEMENT', 'RECOMMEND']:
             dep_id = getattr(koji, 'DEP_' + dep_name)
             if depType is None or depType == dep_id:
                 fields = koji.get_header_fields(rpm_path, [dep_name + 'NAME',
diff --git a/koji/__init__.py b/koji/__init__.py
index 2252e4f..64435a7 100644
--- a/koji/__init__.py
+++ b/koji/__init__.py
@@ -182,6 +182,10 @@ DEP_REQUIRE = 0
 DEP_PROVIDE = 1
 DEP_OBSOLETE = 2
 DEP_CONFLICT = 3
+DEP_SUGGEST = 4
+DEP_ENHANCE = 5
+DEP_SUPPLEMENT = 6
+DEP_RECOMMEND = 7
 
 #dependency flags
 RPMSENSE_LESS = 2
diff --git a/tests/data/rpms/test-deps-1-1.fc24.x86_64.rpm b/tests/data/rpms/test-deps-1-1.fc24.x86_64.rpm
new file mode 100644
index 0000000..975b2ea
Binary files /dev/null and b/tests/data/rpms/test-deps-1-1.fc24.x86_64.rpm differ
diff --git a/tests/data/specs/test-deps.spec b/tests/data/specs/test-deps.spec
new file mode 100644
index 0000000..d31312d
--- /dev/null
+++ b/tests/data/specs/test-deps.spec
@@ -0,0 +1,28 @@
+Name:		test-deps
+Version:	1
+Release:	1%{?dist}
+Summary:	Testing dependency header fields
+
+License:        none
+
+Requires:	require1
+Requires:	require2
+Provides:       provide1
+Provides:       provide2
+Obsoletes:      obsoletes1
+Obsoletes:      obsoletes2
+Conflicts:      conflicts1
+Conflicts:      conflicts2
+Suggests:       suggests1
+Suggests:       suggests2
+Enhances:       enhances1
+Enhances:       enhances2
+Supplements:    supplements1
+Supplements:    supplements2
+Recommends:     recommends1
+Recommends:     recommends2
+
+%description
+Testing dependency header fields
+
+%files
diff --git a/tests/test_hub/test_getRPMDeps.py b/tests/test_hub/test_getRPMDeps.py
new file mode 100644
index 0000000..c87c59f
--- /dev/null
+++ b/tests/test_hub/test_getRPMDeps.py
@@ -0,0 +1,41 @@
+import mock
+import os
+import unittest
+
+import koji
+import kojihub
+
+
+class TestGetRPMDeps(unittest.TestCase):
+    @mock.patch('kojihub.get_rpm')
+    @mock.patch('kojihub.get_build')
+    @mock.patch('koji.pathinfo')
+    def test_getRPMDeps(self, pi, build, rpm):
+        pi.build.return_value = os.path.join(os.path.dirname(__file__), '../data/rpms')
+        pi.rpm.return_value = 'test-deps-1-1.fc24.x86_64.rpm'
+        getRPMDeps = kojihub.RootExports().getRPMDeps
+        res = getRPMDeps('')
+        self.assertEqual(len(res), 22)
+        types = set([x['type'] for x in res])
+        self.assertEqual(set([koji.DEP_REQUIRE,
+                              koji.DEP_PROVIDE,
+                              koji.DEP_OBSOLETE,
+                              koji.DEP_CONFLICT,
+                              koji.DEP_SUGGEST,
+                              koji.DEP_ENHANCE,
+                              koji.DEP_SUPPLEMENT,
+                              koji.DEP_RECOMMEND,
+                              ]), types)
+
+        # test correct mapping of names
+        result = {}
+        for r in res:
+            result.setdefault(r['type'], set()).add(r['name'])
+        self.assertIn('require1', result[koji.DEP_REQUIRE])
+        self.assertIn('provide1', result[koji.DEP_PROVIDE])
+        self.assertIn('obsoletes1', result[koji.DEP_OBSOLETE])
+        self.assertIn('conflicts1', result[koji.DEP_CONFLICT])
+        self.assertIn('suggests1', result[koji.DEP_SUGGEST])
+        self.assertIn('enhances1', result[koji.DEP_ENHANCE])
+        self.assertIn('supplements1', result[koji.DEP_SUPPLEMENT])
+        self.assertIn('recommends1', result[koji.DEP_RECOMMEND])
diff --git a/tests/test_parsers.py b/tests/test_parsers.py
index 93de88a..c41fbd1 100644
--- a/tests/test_parsers.py
+++ b/tests/test_parsers.py
@@ -2,9 +2,12 @@
 
 """Test the __init__.py module"""
 
-import koji
+import os
+import rpm
 import unittest
 
+import koji
+
 class INITTestCase(unittest.TestCase):
     """Main test case container"""
 
@@ -63,5 +66,35 @@ class INITTestCase(unittest.TestCase):
         self.assertEqual(ret['arch'], "src")
         self.assertEqual(ret['src'], True)
 
+
+class HeaderTestCase(unittest.TestCase):
+    rpm_path = os.path.join(os.path.dirname(__file__), 'data/rpms/test-deps-1-1.fc24.x86_64.rpm')
+
+    def setUp(self):
+        self.fd = open(self.rpm_path)
+
+    def tearDown(self):
+        self.fd.close()
+
+    def test_get_rpm_header(self):
+        self.assertRaises(IOError, koji.get_rpm_header, 'nonexistent_path')
+        self.assertRaises(AttributeError, koji.get_rpm_header, None)
+        self.assertIsInstance(koji.get_rpm_header(self.rpm_path), rpm.hdr)
+        self.assertIsInstance(koji.get_rpm_header(self.fd), rpm.hdr)
+        # TODO:
+        # test ts
+
+    def test_get_header_fields(self):
+        # incorrect
+        self.assertRaises(IOError, koji.get_header_fields, 'nonexistent_path', [])
+        self.assertRaises(koji.GenericError, koji.get_header_fields, self.rpm_path, 'nonexistent_header')
+        self.assertEqual(koji.get_header_fields(self.rpm_path, []), {})
+
+        # correct
+        self.assertEqual(['REQUIRES'], koji.get_header_fields(self.rpm_path, ['REQUIRES']).keys())
+        self.assertEqual(['PROVIDES', 'REQUIRES'], sorted(koji.get_header_fields(self.rpm_path, ['REQUIRES', 'PROVIDES'])))
+        hdr = koji.get_rpm_header(self.rpm_path)
+        self.assertEqual(['REQUIRES'], koji.get_header_fields(hdr, ['REQUIRES']).keys())
+
 if __name__ == '__main__':
     unittest.main()

From 54227239992e5221163b77e1ccc1f4f88f208f67 Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Oct 06 2016 11:29:51 +0000
Subject: [PATCH 2/4] show optional deps in web ui


---

diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py
index 63c6be6..d789305 100644
--- a/www/kojiweb/index.py
+++ b/www/kojiweb/index.py
@@ -1363,14 +1363,22 @@ def rpminfo(environ, rpmID, fileOrder='name', fileStart=None, buildrootOrder='-i
     if rpm['buildroot_id'] != None:
         builtInRoot = server.getBuildroot(rpm['buildroot_id'])
     if rpm['external_repo_id'] == 0:
-        values['requires'] = server.getRPMDeps(rpm['id'], koji.DEP_REQUIRE)
-        values['requires'].sort(_sortbyname)
         values['provides'] = server.getRPMDeps(rpm['id'], koji.DEP_PROVIDE)
         values['provides'].sort(_sortbyname)
         values['obsoletes'] = server.getRPMDeps(rpm['id'], koji.DEP_OBSOLETE)
         values['obsoletes'].sort(_sortbyname)
         values['conflicts'] = server.getRPMDeps(rpm['id'], koji.DEP_CONFLICT)
         values['conflicts'].sort(_sortbyname)
+        values['requires'] = server.getRPMDeps(rpm['id'], koji.DEP_REQUIRE)
+        values['requires'].sort(_sortbyname)
+        values['recommends'] = server.getRPMDeps(rpm['id'], koji.DEP_RECOMMEND)
+        values['recommends'].sort(_sortbyname)
+        values['suggests'] = server.getRPMDeps(rpm['id'], koji.DEP_SUGGEST)
+        values['suggests'].sort(_sortbyname)
+        values['supplements'] = server.getRPMDeps(rpm['id'], koji.DEP_SUPPLEMENT)
+        values['supplements'].sort(_sortbyname)
+        values['enhances'] = server.getRPMDeps(rpm['id'], koji.DEP_ENHANCE)
+        values['enhances'].sort(_sortbyname)
         headers = server.getRPMHeaders(rpm['id'], headers=['summary', 'description'])
         values['summary'] = koji.fixEncoding(headers.get('summary'))
         values['description'] = koji.fixEncoding(headers.get('description'))
diff --git a/www/kojiweb/rpminfo.chtml b/www/kojiweb/rpminfo.chtml
index 586279e..3f862db 100644
--- a/www/kojiweb/rpminfo.chtml
+++ b/www/kojiweb/rpminfo.chtml
@@ -92,6 +92,38 @@
       </td>
     </tr>
     <tr>
+      <th>Obsoletes</th>
+      <td class="container">
+        #if $len($obsoletes) > 0
+        <table class="nested">
+          #for $dep in $obsoletes
+          <tr>
+            <td>$util.escapeHTML($util.formatDep($dep.name, $dep.version, $dep.flags))</td>
+          </tr>
+          #end for
+        </table>
+        #else
+        No Obsoletes
+        #end if
+      </td>
+    </tr>
+    <tr>
+      <th>Conflicts</th>
+      <td class="container">
+        #if $len($conflicts) > 0
+        <table class="nested">
+          #for $dep in $conflicts
+          <tr>
+            <td>$util.escapeHTML($util.formatDep($dep.name, $dep.version, $dep.flags))</td>
+          </tr>
+          #end for
+        </table>
+        #else
+        No Conflicts
+        #end if
+      </td>
+    </tr>
+    <tr>
       <th>Requires</th>
       <td class="container">
         #if $len($requires) > 0
@@ -108,34 +140,66 @@
       </td>
     </tr>
     <tr>
-      <th>Obsoletes</th>
+      <th>Recommends</th>
       <td class="container">
-        #if $len($obsoletes) > 0
+        #if $len($recommends) > 0
         <table class="nested">
-          #for $dep in $obsoletes
+          #for $dep in $recommends
           <tr>
             <td>$util.escapeHTML($util.formatDep($dep.name, $dep.version, $dep.flags))</td>
           </tr>
           #end for
         </table>
         #else
-        No Obsoletes
+        No Recommends
         #end if
       </td>
     </tr>
     <tr>
-      <th>Conflicts</th>
+      <th>Suggests</th>
       <td class="container">
-        #if $len($conflicts) > 0
+        #if $len($suggests) > 0
         <table class="nested">
-          #for $dep in $conflicts
+          #for $dep in $suggests
           <tr>
             <td>$util.escapeHTML($util.formatDep($dep.name, $dep.version, $dep.flags))</td>
           </tr>
           #end for
         </table>
         #else
-        No Conflicts
+        No Suggests
+        #end if
+      </td>
+    </tr>
+    <tr>
+      <th>Supplements</th>
+      <td class="container">
+        #if $len($supplements) > 0
+        <table class="nested">
+          #for $dep in $supplements
+          <tr>
+            <td>$util.escapeHTML($util.formatDep($dep.name, $dep.version, $dep.flags))</td>
+          </tr>
+          #end for
+        </table>
+        #else
+        No Supplements
+        #end if
+      </td>
+    </tr>
+    <tr>
+      <th>Enhances</th>
+      <td class="container">
+        #if $len($enhances) > 0
+        <table class="nested">
+          #for $dep in $enhances
+          <tr>
+            <td>$util.escapeHTML($util.formatDep($dep.name, $dep.version, $dep.flags))</td>
+          </tr>
+          #end for
+        </table>
+        #else
+        No Enhances
         #end if
       </td>
     </tr>

From e1bbce63419c535d5540acaaa8afffcc71ac567c Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Oct 06 2016 12:08:01 +0000
Subject: [PATCH 3/4] ignore optional deps for rpm < 4.12


---

diff --git a/koji/__init__.py b/koji/__init__.py
index 64435a7..a81a183 100644
--- a/koji/__init__.py
+++ b/koji/__init__.py
@@ -95,6 +95,14 @@ RPM_FILEDIGESTALGO_IDS = {
     11:   'SHA224'
     }
 
+# rpm 4.12 introduces optional deps
+try:
+    RPM_SUPPORTS_OPTIONAL_DEPS = int(rpm.__version_info__[0]) > 4 or \
+                                 (int(rpm.__version_info__[0]) == 4 and int(rpm.__version_info__[1]) >= 12)
+except AttributeError:
+    # older versions don't even have __version_info__
+    RPM_SUPPORTS_OPTIONAL_DEPS = False
+
 class Enum(dict):
     """A simple class to track our enumerated constants
 
@@ -858,6 +866,11 @@ def get_rpm_header(f, ts=None):
 
 def get_header_field(hdr, name):
     """Extract named field from an rpm header"""
+    if not RPM_SUPPORTS_OPTIONAL_DEPS and name in ('SUGGESTNAME', 'SUGGESTVERSION', 'SUGGESTFLAGS',
+                                                   'ENHANCENAME', 'ENHANCEVERSION', 'ENHANCEFLAGS',
+                                                   'SUPPLEMENTNAME', 'SUPPLEMENTVERSION', 'SUPPLEMENTFLAGS',
+                                                   'RECOMMENDNAME', 'RECOMMENDVERSION', 'RECOMMENDFLAGS'):
+        return []
     idx = getattr(rpm, "RPMTAG_%s" % name.upper(), None)
     if idx is None:
         raise GenericError, "No such rpm header field: %s" % name
diff --git a/tests/test_hub/test_getRPMDeps.py b/tests/test_hub/test_getRPMDeps.py
index c87c59f..0d3149e 100644
--- a/tests/test_hub/test_getRPMDeps.py
+++ b/tests/test_hub/test_getRPMDeps.py
@@ -15,27 +15,46 @@ class TestGetRPMDeps(unittest.TestCase):
         pi.rpm.return_value = 'test-deps-1-1.fc24.x86_64.rpm'
         getRPMDeps = kojihub.RootExports().getRPMDeps
         res = getRPMDeps('')
-        self.assertEqual(len(res), 22)
-        types = set([x['type'] for x in res])
-        self.assertEqual(set([koji.DEP_REQUIRE,
-                              koji.DEP_PROVIDE,
-                              koji.DEP_OBSOLETE,
-                              koji.DEP_CONFLICT,
-                              koji.DEP_SUGGEST,
-                              koji.DEP_ENHANCE,
-                              koji.DEP_SUPPLEMENT,
-                              koji.DEP_RECOMMEND,
-                              ]), types)
+        # limit test for rpm < 4.12
+        if koji.RPM_SUPPORTS_OPTIONAL_DEPS:
+            self.assertEqual(len(res), 22)
+            types = set([x['type'] for x in res])
+            self.assertEqual(set([koji.DEP_REQUIRE,
+                                  koji.DEP_PROVIDE,
+                                  koji.DEP_OBSOLETE,
+                                  koji.DEP_CONFLICT,
+                                  koji.DEP_SUGGEST,
+                                  koji.DEP_ENHANCE,
+                                  koji.DEP_SUPPLEMENT,
+                                  koji.DEP_RECOMMEND,
+                                  ]), types)
 
-        # test correct mapping of names
-        result = {}
-        for r in res:
-            result.setdefault(r['type'], set()).add(r['name'])
-        self.assertIn('require1', result[koji.DEP_REQUIRE])
-        self.assertIn('provide1', result[koji.DEP_PROVIDE])
-        self.assertIn('obsoletes1', result[koji.DEP_OBSOLETE])
-        self.assertIn('conflicts1', result[koji.DEP_CONFLICT])
-        self.assertIn('suggests1', result[koji.DEP_SUGGEST])
-        self.assertIn('enhances1', result[koji.DEP_ENHANCE])
-        self.assertIn('supplements1', result[koji.DEP_SUPPLEMENT])
-        self.assertIn('recommends1', result[koji.DEP_RECOMMEND])
+            # test correct mapping of names
+            result = {}
+            for r in res:
+                result.setdefault(r['type'], set()).add(r['name'])
+            self.assertTrue('require1' in result[koji.DEP_REQUIRE])
+            self.assertTrue('provide1' in result[koji.DEP_PROVIDE])
+            self.assertTrue('obsoletes1' in result[koji.DEP_OBSOLETE])
+            self.assertTrue('conflicts1' in result[koji.DEP_CONFLICT])
+            self.assertTrue('suggests1' in result[koji.DEP_SUGGEST])
+            self.assertTrue('enhances1' in result[koji.DEP_ENHANCE])
+            self.assertTrue('supplements1' in result[koji.DEP_SUPPLEMENT])
+            self.assertTrue('recommends1' in result[koji.DEP_RECOMMEND])
+        else:
+            self.assertEqual(len(res), 14)
+            types = set([x['type'] for x in res])
+            self.assertEqual(set([koji.DEP_REQUIRE,
+                                  koji.DEP_PROVIDE,
+                                  koji.DEP_OBSOLETE,
+                                  koji.DEP_CONFLICT,
+                                  ]), types)
+
+            # test correct mapping of names
+            result = {}
+            for r in res:
+                result.setdefault(r['type'], set()).add(r['name'])
+            self.assertTrue('require1' in result[koji.DEP_REQUIRE])
+            self.assertTrue('provide1' in result[koji.DEP_PROVIDE])
+            self.assertTrue('obsoletes1' in result[koji.DEP_OBSOLETE])
+            self.assertTrue('conflicts1' in result[koji.DEP_CONFLICT])

From 2e8d82ca6d432cf8ed3519a3c0301447c65dbe7f Mon Sep 17 00:00:00 2001
From: Tomas Kopecek <tkopecek@redhat.com>
Date: Oct 06 2016 12:12:45 +0000
Subject: [PATCH 4/4] don't show optional deps if they are not supported by rpm


---

diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py
index d789305..fa9019d 100644
--- a/www/kojiweb/index.py
+++ b/www/kojiweb/index.py
@@ -1371,14 +1371,18 @@ def rpminfo(environ, rpmID, fileOrder='name', fileStart=None, buildrootOrder='-i
         values['conflicts'].sort(_sortbyname)
         values['requires'] = server.getRPMDeps(rpm['id'], koji.DEP_REQUIRE)
         values['requires'].sort(_sortbyname)
-        values['recommends'] = server.getRPMDeps(rpm['id'], koji.DEP_RECOMMEND)
-        values['recommends'].sort(_sortbyname)
-        values['suggests'] = server.getRPMDeps(rpm['id'], koji.DEP_SUGGEST)
-        values['suggests'].sort(_sortbyname)
-        values['supplements'] = server.getRPMDeps(rpm['id'], koji.DEP_SUPPLEMENT)
-        values['supplements'].sort(_sortbyname)
-        values['enhances'] = server.getRPMDeps(rpm['id'], koji.DEP_ENHANCE)
-        values['enhances'].sort(_sortbyname)
+        if koji.RPM_SUPPORTS_OPTIONAL_DEPS:
+            values['optional_deps'] = True
+            values['recommends'] = server.getRPMDeps(rpm['id'], koji.DEP_RECOMMEND)
+            values['recommends'].sort(_sortbyname)
+            values['suggests'] = server.getRPMDeps(rpm['id'], koji.DEP_SUGGEST)
+            values['suggests'].sort(_sortbyname)
+            values['supplements'] = server.getRPMDeps(rpm['id'], koji.DEP_SUPPLEMENT)
+            values['supplements'].sort(_sortbyname)
+            values['enhances'] = server.getRPMDeps(rpm['id'], koji.DEP_ENHANCE)
+            values['enhances'].sort(_sortbyname)
+        else:
+            values['optional_deps'] = False
         headers = server.getRPMHeaders(rpm['id'], headers=['summary', 'description'])
         values['summary'] = koji.fixEncoding(headers.get('summary'))
         values['description'] = koji.fixEncoding(headers.get('description'))
diff --git a/www/kojiweb/rpminfo.chtml b/www/kojiweb/rpminfo.chtml
index 3f862db..e12db01 100644
--- a/www/kojiweb/rpminfo.chtml
+++ b/www/kojiweb/rpminfo.chtml
@@ -139,6 +139,7 @@
         #end if
       </td>
     </tr>
+    #if $optional_deps
     <tr>
       <th>Recommends</th>
       <td class="container">
@@ -203,6 +204,7 @@
         #end if
       </td>
     </tr>
+    #end if
     <tr>
       <th id="filelist">Files</th>
       <td class="container">

