public inbox for cygwin-apps-cvs@sourceware.org
help / color / mirror / Atom feed
From: jturney@sourceware.org
To: cygwin-apps-cvs@sourceware.org
Subject: [calm - Cygwin server-side packaging maintenance script] branch master, updated. 20171008-23-g9282c36
Date: Thu, 09 Nov 2017 18:22:00 -0000	[thread overview]
Message-ID: <20171109182231.14833.qmail@sourceware.org> (raw)

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 69726 bytes --]




https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=9282c36d3e680ee8c7e296fb1ecb43d84d45301b

commit 9282c36d3e680ee8c7e296fb1ecb43d84d45301b
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Nov 9 16:12:56 2017 +0000

    Add basic build-depends: handling
    
    Converting a dependency atom to a package name with full generality requires
    a database of all the pathnames contained in all packages, which we don't
    have (yet)
    
    For the moment, pass through dependency atoms which are cygwin package
    names, and filter out all other kinds of dependency atoms
    
    Also check that build-depends: is being applied to a source package

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=52d4829e555044f3a4ca5601b836c1a32b7ceb42

commit 52d4829e555044f3a4ca5601b836c1a32b7ceb42
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Oct 25 17:38:26 2017 +0100

    Allow the effective version to be overridden by version: hint
    
    The archive filenames appear in setup.ini unchanged, but the version is
    taken from the hint, rather than deduced from the archive filename.
    
    This requires a change to collecting tars per VR, rather than keeping them
    all in one list.
    
    This could be used as a way to fix the epoch of packages which have a
    decreased version number, but were built without bumping the epoch.
    
    This could also be used to have a properly ordered version number whilst
    using upstream version numbers which don't do that for the archive
    filenames.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=fd8fbcee07c8df7e6604ab210f0be36603d24d7a

commit fd8fbcee07c8df7e6604ab210f0be36603d24d7a
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Oct 25 17:34:47 2017 +0100

    Wrap access to tar for a given version and category in a function
    
    It's complex and repeated in many places.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=bb65deb2ad607e2038cd82a0cbf69f197f39ead1

commit bb65deb2ad607e2038cd82a0cbf69f197f39ead1
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Nov 9 15:47:03 2017 +0000

    Disable pycodestyle E741 'ambiguous variable name'
    
    This rejects a variable named 'l', in case it gets confused with '1'

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=de3c3980d668c041f1c1359ffad4584ffdd8d323

commit de3c3980d668c041f1c1359ffad4584ffdd8d323
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Nov 9 15:44:32 2017 +0000

    Fix pycodestyle E722 'do not use bare except'

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=2be0160c571d7c78ef9c570fd97ccfd897e3e2a5

commit 2be0160c571d7c78ef9c570fd97ccfd897e3e2a5
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Nov 9 15:43:03 2017 +0000

    Fix various E305 'expected 2 blank lines' errors with latest pycodestyle

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=be4b51c30aa783f24be537170b77bc3fb103aa78

commit be4b51c30aa783f24be537170b77bc3fb103aa78
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Nov 9 15:33:17 2017 +0000

    Update for pep8 renamed to pycodestyle

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=e9919f28f8fdf1a4ba5d5e0e3044a666a8f43152

commit e9919f28f8fdf1a4ba5d5e0e3044a666a8f43152
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Oct 25 14:25:35 2017 +0100

    Add tests of obsoletes: and depends:

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=0af273df25f3d930dfa25a2b5dc49b9e4e4261ab

commit 0af273df25f3d930dfa25a2b5dc49b9e4e4261ab
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Wed Oct 25 15:16:47 2017 +0100

    Trim components of obsoletes: and depends: when we sort them
    
    Don't want components to be sorted with any leading whitespace

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=84829a01884989f65e49e8163dcc7eb3cec9d888

commit 84829a01884989f65e49e8163dcc7eb3cec9d888
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Jun 3 20:36:06 2017 +0100

    Add depends: handling
    
    Future work: We probably need something to set requires: hint based on
    depends: hint, if one isn't present.

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=22e09da2afaf70a4ce2294250b621ecc8075783e

commit 22e09da2afaf70a4ce2294250b621ecc8075783e
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu May 25 22:57:47 2017 +0100

    Add obsoletes: handling
    
    Validate that the obsoleted packages exist (unless disabled with okmissing)
    
    No validation is done on any version relation which might be specified, but
    it's not clear how we can do that...

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=828b91be6190a1707b8e89a157487419e36bcac7

commit 828b91be6190a1707b8e89a157487419e36bcac7
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Tue Oct 31 12:43:54 2017 +0000

    Try to rename setup.hint to pvr.hint at upload
    
    Update tests

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=558c8d958f4b64d99f39bc4bcf3f3ba12b5495f1

commit 558c8d958f4b64d99f39bc4bcf3f3ba12b5495f1
Author: Achim Gratz <Stromeko@nexgo.de>
Date:   Sat Nov 4 17:14:25 2017 +0100

    Make a failure to chmod the setup.ini file to mode 644 non-fatal.
    
    v2:
    Only do this in calm, not in mksetupini


Diff:
---
 .travis.yml                                        |    2 +-
 calm/buffering_smtp_handler.py                     |    2 +-
 calm/calm.py                                       |    6 +
 calm/dedupsrc.py                                   |    1 +
 calm/fix-missing-cygwin-dep.py                     |    2 +
 calm/hint.py                                       |   27 ++-
 calm/irk.py                                        |    1 +
 calm/mkgitoliteconf.py                             |    3 +-
 calm/mkmaintdir                                    |    1 +
 calm/mksetupini.py                                 |    2 +-
 calm/package.py                                    |  239 ++++++++++++--------
 calm/pkg2html.py                                   |    6 +-
 calm/setup_exe.py                                  |    1 +
 calm/uploads.py                                    |   34 +++-
 pep8                                               |    2 +-
 test/testdata/htdocs.expected/x86/obs-a/.htaccess  |    3 +
 .../testdata/htdocs.expected/x86/obs-a/obs-a-1.0-1 |   12 +
 .../htdocs.expected/x86/obs-a/obs-a-1.0-1-src      |   12 +
 test/testdata/htdocs.expected/x86/obs-b/.htaccess  |    3 +
 .../testdata/htdocs.expected/x86/obs-b/obs-b-1.0-1 |   12 +
 .../htdocs.expected/x86/obs-b/obs-b-1.0-1-src      |   12 +
 test/testdata/htdocs.expected/x86/packages.inc     |    5 +
 test/testdata/htdocs.expected/x86/test-c/.htaccess |    3 +
 .../htdocs.expected/x86/test-c/test-c-1.0-1        |   12 +
 .../htdocs.expected/x86/test-c/test-c-1.0-1-src    |   12 +
 test/testdata/htdocs.expected/x86/test-d/.htaccess |    3 +
 .../htdocs.expected/x86/test-d/test-d-1.0-1        |   12 +
 .../htdocs.expected/x86/test-d/test-d-1.0-1-src    |   12 +
 test/testdata/htdocs.expected/x86/test-e/.htaccess |    3 +
 .../htdocs.expected/x86/test-e/test-e-1.0-1        |   12 +
 .../htdocs.expected/x86/test-e/test-e-1.0-1-src    |   12 +
 test/testdata/inifile/setup.ini.expected           |   48 ++++
 test/testdata/process_arch/htdocs.expected         |    5 +
 test/testdata/process_arch/rel_area.expected       |   15 +-
 test/testdata/process_arch/setup.ini.expected      |   48 ++++
 .../noarch/release/obs-a/obs-a-1.0-1-src.tar.xz    |  Bin 0 -> 256 bytes
 .../relarea/noarch/release/obs-a/obs-a-1.0-1.hint  |    2 +
 .../noarch/release/obs-a/obs-a-1.0-1.tar.xz        |  Bin 0 -> 256 bytes
 .../noarch/release/obs-b/obs-b-1.0-1-src.tar.xz    |  Bin 0 -> 256 bytes
 .../relarea/noarch/release/obs-b/obs-b-1.0-1.hint  |    2 +
 .../noarch/release/obs-b/obs-b-1.0-1.tar.xz        |  Bin 0 -> 256 bytes
 .../noarch/release/test-c/test-c-1.0-1-src.tar.xz  |  Bin 0 -> 256 bytes
 .../noarch/release/test-c/test-c-1.0-1.hint        |    4 +
 .../noarch/release/test-c/test-c-1.0-1.tar.xz      |  Bin 0 -> 256 bytes
 .../noarch/release/test-d/test-d-1.0-1-src.tar.xz  |  Bin 0 -> 236 bytes
 .../noarch/release/test-d/test-d-1.0-1.hint        |    3 +
 .../noarch/release/test-d/test-d-1.0-1.tar.xz      |  Bin 0 -> 236 bytes
 .../noarch/release/test-e/test-e-1.0-1-src.tar.xz  |  Bin 0 -> 236 bytes
 .../noarch/release/test-e/test-e-1.0-1.hint        |    3 +
 .../noarch/release/test-e/test-e-1.0-1.tar.xz      |  Bin 0 -> 236 bytes
 test/testdata/uploads/move.expected                |    8 +-
 test/testdata/uploads/pkglist.expected             |   14 +-
 52 files changed, 501 insertions(+), 120 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index a5b35c2..ceda33e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,7 @@ language: python
 sudo: false
 python:
  - "3.4"
-install: "pip install pep8 dirq"
+install: "pip install pycodestyle dirq"
 script:
  - ./pep8
  - python -m unittest discover
diff --git a/calm/buffering_smtp_handler.py b/calm/buffering_smtp_handler.py
index 81a107b..0867c91 100644
--- a/calm/buffering_smtp_handler.py
+++ b/calm/buffering_smtp_handler.py
@@ -92,7 +92,7 @@ class BufferingSMTPHandler(logging.handlers.BufferingHandler):
                     smtp = smtplib.SMTP(self.mailhost, port)
                     smtp.send_message(m)
                     smtp.quit()
-                except:
+                except ImportError:
                     self.handleError(self.buffer[0])  # first record
 
             self.buffer = []
diff --git a/calm/calm.py b/calm/calm.py
index 729dec8..6f2ae7f 100755
--- a/calm/calm.py
+++ b/calm/calm.py
@@ -392,6 +392,12 @@ def do_output(args, state):
             # write setup.ini
             package.write_setup_ini(args, state.packages[arch], arch)
 
+            # make it world-readable, if we can
+            try:
+                os.chmod(args.inifile, 0o644)
+            except (OSError):
+                pass
+
             if not os.path.exists(inifile):
                 # if the setup.ini file doesn't exist yet
                 logging.warning('no existing %s' % (inifile))
diff --git a/calm/dedupsrc.py b/calm/dedupsrc.py
index f531a2e..8462fa7 100755
--- a/calm/dedupsrc.py
+++ b/calm/dedupsrc.py
@@ -148,6 +148,7 @@ def main():
 
     return dedup(args.archive[0], args.rel_area)
 
+
 #
 #
 #
diff --git a/calm/fix-missing-cygwin-dep.py b/calm/fix-missing-cygwin-dep.py
index b7c2c80..6ca3625 100755
--- a/calm/fix-missing-cygwin-dep.py
+++ b/calm/fix-missing-cygwin-dep.py
@@ -118,9 +118,11 @@ def main(args):
         # (written this way so it doesn't spoil a hardlinked backup of the releasearea)
         os.rename(fn, ofn)
 
+
 #
 #
 #
+
 if __name__ == "__main__":
     relarea_default = common_constants.FTP
 
diff --git a/calm/hint.py b/calm/hint.py
index bcdda95..0c9fd50 100755
--- a/calm/hint.py
+++ b/calm/hint.py
@@ -37,6 +37,7 @@ def merge_dicts(x, *y):
         z.update(i)
     return z
 
+
 # types of key:
 # 'multilineval' - always have a value, which may be multiline
 # 'val'          - always have a value
@@ -76,12 +77,13 @@ hintkeys[setup] = merge_dicts(commonkeys, versionkeys, {
 
 hintkeys[pvr] = merge_dicts(commonkeys, {
     'requires': 'optval',
-    # putative syntax for not yet implemented per-version dependencies
-    # (depends could be an alias for requires in this kind of hint file)
     'depends': 'optval',
     'build-depends': 'optval',
+    'obsoletes': 'optval',
     # mark the package as a test version
     'test': 'noval',
+    # version override
+    'version': 'val',
 })
 
 hintkeys[override] = merge_dicts(versionkeys, overridekeys)
@@ -191,6 +193,13 @@ def item_lexer(c):
         yield (i, o, None)
 
 
+def split_trim_sort_join(hint, splitchar, joinchar=None):
+    if joinchar is None:
+        joinchar = splitchar + ' '
+
+    return joinchar.join(sorted([s.strip() for s in hint.split(splitchar)]))
+
+
 # parse the file |fn| as a .hint file of kind |kind|
 def hint_file_parse(fn, kind):
     hints = OrderedDict()
@@ -296,9 +305,18 @@ def hint_file_parse(fn, kind):
                 if len(hints['sdesc']) > 2 * len(hints['ldesc']):
                     warnings.append('sdesc is much longer than ldesc')
 
-            # sort requires: as differences in ordering are uninteresting
+            # sort these hints, as differences in ordering are uninteresting
             if 'requires' in hints:
-                hints['requires'] = ' '.join(sorted(hints['requires'].split()))
+                hints['requires'] = split_trim_sort_join(hints['requires'], None, ' ')
+
+            if 'build-depends' in hints:
+                hints['build-depends'] = split_trim_sort_join(hints['build-depends'], None, ' ')
+
+            if 'depends' in hints:
+                hints['depends'] = split_trim_sort_join(hints['depends'], ',')
+
+            if 'obsoletes' in hints:
+                hints['obsoletes'] = split_trim_sort_join(hints['obsoletes'], ',')
 
         except UnicodeDecodeError:
             errors.append('invalid UTF-8')
@@ -338,6 +356,7 @@ def main(args):
 
     return status
 
+
 #
 #
 #
diff --git a/calm/irk.py b/calm/irk.py
index 7e425ef..548ed9f 100755
--- a/calm/irk.py
+++ b/calm/irk.py
@@ -47,5 +47,6 @@ def main():
         sys.stderr.write("irk: write to server failed: %r\n" % e)
         sys.exit(1)
 
+
 if __name__ == '__main__':
     main()
diff --git a/calm/mkgitoliteconf.py b/calm/mkgitoliteconf.py
index a82a083..374965e 100755
--- a/calm/mkgitoliteconf.py
+++ b/calm/mkgitoliteconf.py
@@ -83,11 +83,11 @@ def do_main(args):
         print("owner = %s" % (owner))
         print("")
 
+
 #
 #
 #
 
-
 def main():
     pkglist_default = common_constants.PKGMAINT
 
@@ -97,6 +97,7 @@ def main():
 
     do_main(args)
 
+
 #
 #
 #
diff --git a/calm/mkmaintdir b/calm/mkmaintdir
index 4d016d1..2023ae6 100755
--- a/calm/mkmaintdir
+++ b/calm/mkmaintdir
@@ -115,6 +115,7 @@ def main(args):
                 if not args.dryrun:
                     os.unlink(defunct)
 
+
 #
 #
 #
diff --git a/calm/mksetupini.py b/calm/mksetupini.py
index e7337fe..f24e4c5 100755
--- a/calm/mksetupini.py
+++ b/calm/mksetupini.py
@@ -100,7 +100,7 @@ def main():
     parser = argparse.ArgumentParser(description='Make setup.ini')
     parser.add_argument('--arch', action='store', required=True, choices=common_constants.ARCHES)
     parser.add_argument('--inifile', '-u', action='store', help='output filename', required=True)
-    parser.add_argument('--okmissing', action='append', help='missing things which are ok', choices=['curr', 'required-package'])
+    parser.add_argument('--okmissing', action='append', help='missing things which are ok', choices=['curr', 'depended-package', 'obsoleted-package', 'required-package'])
     parser.add_argument('--pkglist', action='store', nargs='?', metavar='FILE', help="package maintainer list (default: " + pkglist_default + ")", const=pkglist_default)
     parser.add_argument('--release', action='store', help='value for setup-release key (default: cygwin)', default='cygwin')
     parser.add_argument('--releasearea', action='store', metavar='DIR', help="release directory (default: " + relarea_default + ")", default=relarea_default, dest='rel_area')
diff --git a/calm/package.py b/calm/package.py
index fc0d7f4..2d00299 100755
--- a/calm/package.py
+++ b/calm/package.py
@@ -54,6 +54,7 @@ class Package(object):
         self.version_hints = {}
         self.override_hints = {}
         self.skip = False
+        self.vermap = defaultdict(defaultdict)
 
     def __repr__(self):
         return "Package('%s', %s, %s, %s, %s)" % (
@@ -63,6 +64,9 @@ class Package(object):
             pprint.pformat(self.override_hints),
             self.skip)
 
+    def tar(self, vr, category):
+        return self.tars[vr][self.vermap[vr][category]]
+
 
 # information we keep about a tar file
 class Tar(object):
@@ -232,7 +236,7 @@ def read_package(packages, basedir, dirpath, files, strict=False, remove=[], upl
         # build a list of version-releases (since replacement pvr.hint files are
         # allowed to be uploaded, we must consider both .tar and .hint files for
         # that), and collect the attributes for each tar file
-        tars = {}
+        tars = defaultdict(dict)
         vr_list = set()
 
         for f in list(files):
@@ -249,8 +253,8 @@ def read_package(packages, basedir, dirpath, files, strict=False, remove=[], upl
             # start with a number
             match = re.match(r'^' + re.escape(p) + '-(.+)-(\d[0-9a-zA-Z.]*)(-src|)\.' + match.group(1) + '$', f)
             if not match:
-                logging.log(strict_lvl, "file '%s' in package '%s' doesn't follow naming convention" % (f, p))
-                warnings = True
+                logging.error("file '%s' in package '%s' doesn't follow naming convention" % (f, p))
+                return True
             else:
                 v = match.group(1)
                 r = match.group(2)
@@ -271,24 +275,28 @@ def read_package(packages, basedir, dirpath, files, strict=False, remove=[], upl
                     warnings = True
 
                 # if not there already, add to version-release list
-                vr_list.add('%s-%s' % (v, r))
+                vr = '%s-%s' % (v, r)
+                vr_list.add(vr)
 
             if not f.endswith('.hint'):
                 # collect the attributes for each tar file
-                tars[f] = Tar()
-                tars[f].size = os.path.getsize(os.path.join(dirpath, f))
-                tars[f].is_empty = tarfile_is_empty(os.path.join(dirpath, f))
-                tars[f].mtime = os.path.getmtime(os.path.join(dirpath, f))
+                t = Tar()
+                t.size = os.path.getsize(os.path.join(dirpath, f))
+                t.is_empty = tarfile_is_empty(os.path.join(dirpath, f))
+                t.mtime = os.path.getmtime(os.path.join(dirpath, f))
 
                 if f in sha512:
-                    tars[f].sha512 = sha512[f]
+                    t.sha512 = sha512[f]
                 else:
-                    tars[f].sha512 = sha512_file(os.path.join(dirpath, f))
-                    logging.debug("no sha512.sum line for file %s in package '%s', computed sha512 hash is %s" % (f, p, tars[f].sha512))
+                    t.sha512 = sha512_file(os.path.join(dirpath, f))
+                    logging.debug("no sha512.sum line for file %s in package '%s', computed sha512 hash is %s" % (f, p, t.sha512))
+
+                tars[vr][f] = t
 
         # determine hints for each version we've encountered
         version_hints = {}
         hint_files = {}
+        actual_tars = {}
         for vr in vr_list:
             hint_fn = '%s-%s.hint' % (p, vr)
             if hint_fn in files:
@@ -298,17 +306,26 @@ def read_package(packages, basedir, dirpath, files, strict=False, remove=[], upl
                     return True
                 warnings = clean_hints(p, pvr_hint, strict_lvl, warnings)
                 files.remove(hint_fn)
-                hint_files[vr] = hint_fn
             elif legacy:
                 # otherwise, use setup.hint
                 pvr_hint = hints.copy()
                 legacy_used = True
+                hint_fn = None
             else:
                 # it's an error to not have either a setup.hint or a pvr.hint
                 logging.error("package %s has packages for version %s, but no %s or setup.hint" % (p, vr, hint_fn))
                 return True
 
-            version_hints[vr] = pvr_hint
+            # apply a version override
+            if 'version' in pvr_hint:
+                ovr = pvr_hint['version']
+            else:
+                ovr = vr
+
+            version_hints[ovr] = pvr_hint
+            if hint_fn:
+                hint_files[ovr] = hint_fn
+            actual_tars[ovr] = tars[vr]
 
         # ignore dotfiles
         for f in files:
@@ -328,10 +345,10 @@ def read_package(packages, basedir, dirpath, files, strict=False, remove=[], upl
         packages[p].version_hints = version_hints
         packages[p].override_hints = override_hints
         packages[p].legacy_hints = hints
-        packages[p].tars = tars
+        packages[p].tars = actual_tars
         packages[p].hint_files = hint_files
         packages[p].path = relpath
-        packages[p].skip = any(['skip' in version_hints[vr] for vr in vr_list])
+        packages[p].skip = any(['skip' in version_hints[vr] for vr in version_hints])
 
     elif (len(files) > 0) and (relpath.count(os.path.sep) > 1):
         logging.log(strict_lvl, "no .hint files in %s but has files: %s" % (dirpath, ', '.join(files)))
@@ -393,27 +410,41 @@ def validate_packages(args, packages):
         has_requires = False
 
         for (v, hints) in packages[p].version_hints.items():
-            if 'requires' in hints:
-                for r in hints['requires'].split():
-                    has_requires = True
-
-                    # a package should not appear in it's own requires
-                    if r == p:
-                        lvl = logging.WARNING if p not in past_mistakes.self_requires else logging.DEBUG
-                        logging.log(lvl, "package '%s' version '%s' requires itself" % (p, v))
-
-                    # all packages listed in requires must exist (unless
-                    # okmissing says that's ok)
-                    if r not in packages:
-                        if 'required-package' not in getattr(args, 'okmissing', []):
-                            logging.error("package '%s' version '%s' requires nonexistent package '%s'" % (p, v, r))
+            for (c, okmissing, splitchar) in [
+                    ('requires', 'required-package', None),
+                    ('depends', 'depended-package', ','),
+                    ('obsoletes', 'obsoleted-package', ',')
+            ]:
+                if c in hints:
+                    for r in hints[c].split(splitchar):
+                        if c == 'requires':
+                            has_requires = True
+
+                        # remove any extraneous whitespace
+                        r = r.strip()
+
+                        # strip off any version relation enclosed in '()'
+                        # following the package name
+                        if splitchar:
+                            r = re.sub(r'(.*) +\(.*\)', r'\1', r)
+
+                        # a package should not appear in it's own hint
+                        if r == p:
+                            lvl = logging.WARNING if p not in past_mistakes.self_requires else logging.DEBUG
+                            logging.log(lvl, "package '%s' version '%s' %s itself" % (p, v, c))
+
+                        # all packages listed in a hint must exist (unless
+                        # okmissing says that's ok)
+                        if r not in packages:
+                            if okmissing not in getattr(args, 'okmissing', []):
+                                logging.error("package '%s' version '%s' %s nonexistent package '%s'" % (p, v, c, r))
+                                error = True
+                            continue
+
+                        # hint referencing a source-only package makes no sense
+                        if packages[r].skip:
+                            logging.error("package '%s' version '%s' %s source-only package '%s'" % (p, v, c, r))
                             error = True
-                        continue
-
-                    # requiring a source-only package makes no sense
-                    if packages[r].skip:
-                        logging.error("package '%s' version '%s' requires source-only package '%s'" % (p, v, r))
-                        error = True
 
             # if external-source is used, the package must exist
             if 'external-source' in hints:
@@ -427,31 +458,28 @@ def validate_packages(args, packages):
         has_install = False
         has_nonempty_install = False
 
-        for (t, tar) in packages[p].tars.items():
-            # categorize each tarfile as either 'source' or 'install'
-            if re.search(r'-src.*\.tar', t):
-                category = 'source'
-            else:
-                category = 'install'
-                has_install = True
-                is_empty[t] = packages[p].tars[t].is_empty
-                if not is_empty[t]:
-                    has_nonempty_install = True
-
-            # extract just the version part from tar filename
-            v = re.sub(r'^' + re.escape(p) + '-', '', t)
-            v = re.sub(r'(-src|)\.tar\.(bz2|gz|lzma|xz)$', '', v)
-
-            # for each version, a package can contain at most one source tar
-            # file and at most one install tar file.  warn if we have too many
-            # (for e.g. both a .xz and .bz2 install tar file)
-            if category in packages[p].vermap[v]:
-                logging.error("package '%s' has more than one %s tar file for version '%s'" % (p, category, v))
-                error = True
+        for vr in packages[p].tars:
+            for (t, tar) in packages[p].tars[vr].items():
+                # categorize each tarfile as either 'source' or 'install'
+                if re.search(r'-src.*\.tar', t):
+                    category = 'source'
+                else:
+                    category = 'install'
+                    has_install = True
+                    is_empty[t] = packages[p].tars[vr][t].is_empty
+                    if not is_empty[t]:
+                        has_nonempty_install = True
+
+                # for each version, a package can contain at most one source tar
+                # file and at most one install tar file.  warn if we have too many
+                # (for e.g. both a .xz and .bz2 install tar file)
+                if category in packages[p].vermap[vr]:
+                    logging.error("package '%s' has more than one %s tar file for version '%s'" % (p, category, vr))
+                    error = True
 
-            # store tarfile corresponding to this version and category
-            packages[p].vermap[v][category] = t
-            packages[p].vermap[v]['mtime'] = tar.mtime
+                # store tarfile corresponding to this version and category
+                packages[p].vermap[vr][category] = t
+                packages[p].vermap[vr]['mtime'] = tar.mtime
 
         obsolete = any(['_obsolete' in packages[p].version_hints[vr].get('category', '') for vr in packages[p].version_hints])
 
@@ -601,7 +629,7 @@ def validate_packages(args, packages):
                 if '_obsolete' not in packages[p].version_hints[vr].get('category', ''):
                     if 'source' not in packages[p].vermap[vr]:
                         if 'install' in packages[p].vermap[vr]:
-                            if packages[p].tars[packages[p].vermap[vr]['install']].is_empty:
+                            if packages[p].tar(vr, 'install').is_empty:
                                 if p in past_mistakes.empty_but_not_obsolete:
                                     lvl = logging.DEBUG
                                 else:
@@ -609,6 +637,12 @@ def validate_packages(args, packages):
                                     error = True
                                 logging.log(lvl, "package '%s' version '%s' has empty install tar file and no source, but it's not in the _obsolete category" % (p, vr))
 
+        for vr in packages[p].version_hints:
+            if 'build-depends' in packages[p].version_hints[vr]:
+                if 'source' not in packages[p].vermap[vr]:
+                    logging.error("package '%s' version '%s' has build-depends but no source" % (p, vr))
+                    error = True
+
     # make another pass to verify a source tarfile exists for every install
     # tarfile version
     for p in sorted(packages.keys()):
@@ -617,7 +651,7 @@ def validate_packages(args, packages):
                 continue
 
             # unless the install tarfile is empty
-            if packages[p].tars[packages[p].vermap[v]['install']].is_empty:
+            if packages[p].tar(v, 'install').is_empty:
                 continue
 
             # source tarfile may be either in this package or in the
@@ -625,7 +659,7 @@ def validate_packages(args, packages):
             #
             # mark the source tarfile as being used by an install tarfile
             if 'source' in packages[p].vermap[v]:
-                packages[p].tars[packages[p].vermap[v]['source']].is_used = True
+                packages[p].tar(v, 'source').is_used = True
                 packages[p].is_used_by.add(p)
                 continue
 
@@ -633,7 +667,7 @@ def validate_packages(args, packages):
                 es_p = packages[p].version_hints[v]['external-source']
                 if es_p in packages:
                     if 'source' in packages[es_p].vermap[v]:
-                        packages[es_p].tars[packages[es_p].vermap[v]['source']].is_used = True
+                        packages[es_p].tar(v, 'source').is_used = True
                         packages[es_p].is_used_by.add(p)
                         continue
 
@@ -651,13 +685,13 @@ def validate_packages(args, packages):
             if 'source' not in packages[p].vermap[v]:
                 continue
 
-            if packages[p].tars[packages[p].vermap[v]['source']].is_empty:
+            if packages[p].tar(v, 'source').is_empty:
                 continue
 
             if '_obsolete' in packages[p].version_hints[v].get('category', ''):
                 continue
 
-            if not packages[p].tars[packages[p].vermap[v]['source']].is_used:
+            if not packages[p].tar(v, 'source').is_used:
                 logging.error("package '%s' version '%s' source has no non-empty install tarfiles" % (p, v))
                 error = True
 
@@ -736,8 +770,6 @@ def write_setup_ini(args, packages, arch):
     logging.debug('writing %s' % (args.inifile))
 
     with open(args.inifile, 'w') as f:
-        os.fchmod(f.fileno(), 0o644)
-
         tz = time.time()
         # write setup.ini header
         print(textwrap.dedent('''\
@@ -849,13 +881,11 @@ def write_setup_ini(args, packages, arch):
                 print("version: %s" % version, file=f)
 
                 if 'install' in packages[p].vermap[version]:
-                    t = packages[p].vermap[version]['install']
-                    tar_line('install', packages[p], t, f)
+                    tar_line(packages[p], 'install', version, f)
 
                 # look for corresponding source in this package first
                 if 'source' in packages[p].vermap[version]:
-                    t = packages[p].vermap[version]['source']
-                    tar_line('source', packages[p], t, f)
+                    tar_line(packages[p], 'source', version, f)
                 # if that doesn't exist, follow external-source
                 elif 'external-source' in packages[p].version_hints[version]:
                     s = packages[p].version_hints[version]['external-source']
@@ -865,17 +895,35 @@ def write_setup_ini(args, packages, arch):
                     # external-source points to a source file in another package
                     else:
                         if 'source' in packages[s].vermap[version]:
-                            t = packages[s].vermap[version]['source']
-                            tar_line('source', packages[s], t, f)
+                            tar_line(packages[s], 'source', version, f)
                         else:
                             logging.warning("package '%s' version '%s' has no source in external-source '%s'" % (p, version, s))
 
+                if 'depends' in packages[p].version_hints[version]:
+                    print("depends: %s" % packages[p].version_hints[version]['depends'], file=f)
+
+                if 'obsoletes' in packages[p].version_hints[version]:
+                    print("obsoletes: %s" % packages[p].version_hints[version]['obsoletes'], file=f)
+
+                if 'build-depends' in packages[p].version_hints[version]:
+                    bd = packages[p].version_hints[version]['build-depends']
+
+                    # Ideally, we'd transform dependency atoms which aren't
+                    # cygwin package names into package names. For the moment,
+                    # we don't have the information to do that, so filter them
+                    # all out.
+                    bd = [atom for atom in bd.split() if '(' not in atom]
+
+                    if bd:
+                        print("build-depends: %s" % ', '.join(bd), file=f)
+
 
 # helper function to output details for a particular tar file
-def tar_line(category, p, t, f):
+def tar_line(p, category, v, f):
+    t = p.vermap[v][category]
     fn = os.path.join(p.path, t)
-    sha512 = p.tars[t].sha512
-    size = p.tars[t].size
+    sha512 = p.tar(v, category).sha512
+    size = p.tar(v, category).size
     print("%s: %s %d %s" % (category, fn, size, sha512), file=f)
 
 
@@ -913,12 +961,16 @@ def merge(a, *l):
                     logging.error("package '%s' is at paths %s and %s" % (p, a[p].path, b[p].path))
                     return None
                 else:
-                    for t in b[p].tars:
-                        if t in c[p].tars:
-                            logging.error("package '%s' has duplicate tarfile %s" % (p, t))
-                            return None
+                    for vr in b[p].tars:
+                        if vr in c[p].tars:
+                            for t in b[p].tars[vr]:
+                                if t in c[p].tars[vr]:
+                                    logging.error("package '%s' has duplicate tarfile %s for version %s" % (p, t, vr))
+                                    return None
+                                else:
+                                    c[p].tars[vr][t] = b[p].tars[vr][t]
                         else:
-                            c[p].tars[t] = b[p].tars[t]
+                            c[p].tars[vr] = b[p].tars[vr]
 
                     # hints from b override hints from a, but warn if they have
                     # changed
@@ -962,11 +1014,12 @@ def merge(a, *l):
 def delete(packages, path, fn):
     for p in packages:
         if packages[p].path == path:
-            for t in packages[p].tars:
-                if t == fn:
-                    del packages[p].tars[t]
+            for vr in packages[p].tars:
+                for t in packages[p].tars[vr]:
+                    if t == fn:
+                        del packages[p].tars[vr][t]
                     # XXX: should also remove from vermap
-                    break
+                        break
 
             for h in packages[p].hint_files:
                 if packages[p].hint_files[h] == fn:
@@ -1010,17 +1063,17 @@ def is_in_package_list(ppath, plist):
 
 def mark_package_fresh(packages, p, v):
     if 'install' in packages[p].vermap[v]:
-        packages[p].tars[packages[p].vermap[v]['install']].fresh = True
+        packages[p].tar(v, 'install').fresh = True
 
     if 'source' in packages[p].vermap[v]:
-        packages[p].tars[packages[p].vermap[v]['source']].fresh = True
+        packages[p].tar(v, 'source').fresh = True
         return
 
     # unless the install tarfile is empty ...
     if 'install' not in packages[p].vermap[v]:
         return
 
-    if packages[p].tars[packages[p].vermap[v]['install']].is_empty:
+    if packages[p].tar(v, 'install').is_empty:
         return
 
     # ... mark any corresponding external-source package version as also fresh
@@ -1028,7 +1081,7 @@ def mark_package_fresh(packages, p, v):
         es_p = packages[p].version_hints[v]['external-source']
         if es_p in packages:
             if 'source' in packages[es_p].vermap[v]:
-                packages[es_p].tars[packages[es_p].vermap[v]['source']].fresh = True
+                packages[es_p].tar(v, 'source').fresh = True
 
 
 #
@@ -1065,7 +1118,7 @@ def stale_packages(packages):
         for v in sorted(po.vermap.keys(), key=lambda v: SetupVersion(v)):
             if not newer:
                 if 'install' in po.vermap[v]:
-                    if po.tars[po.vermap[v]['install']].mtime > (time.time() - (keep_days * 24 * 60 * 60)):
+                    if po.tar(v, 'install').mtime > (time.time() - (keep_days * 24 * 60 * 60)):
                         newer = True
 
             if newer:
@@ -1078,7 +1131,7 @@ def stale_packages(packages):
             all_stale = True
             for category in ['source', 'install']:
                 if category in po.vermap[v]:
-                    if not getattr(po.tars[po.vermap[v][category]], 'fresh', False):
+                    if not getattr(po.tar(v, category), 'fresh', False):
                         stale[po.path].append(po.vermap[v][category])
                         logging.debug("package '%s' version '%s' %s is stale" % (pn, v, category))
                     else:
@@ -1095,15 +1148,17 @@ def stale_packages(packages):
         for v in po.vermap:
             for c in ['source', 'install']:
                 try:
-                    delattr(po.tars[po.vermap[v][c]], 'fresh')
+                    delattr(po.tar(v, c), 'fresh')
                 except (KeyError, AttributeError):
                     pass
 
     return stale
 
+
 #
 #
 #
+
 if __name__ == "__main__":
     for arch in common_constants.ARCHES:
         packages = read_packages(common_constants.FTP, arch)
diff --git a/calm/pkg2html.py b/calm/pkg2html.py
index 83308b6..d591b64 100755
--- a/calm/pkg2html.py
+++ b/calm/pkg2html.py
@@ -42,6 +42,7 @@ from collections import defaultdict
 import argparse
 import glob
 import html
+import itertools
 import logging
 import os
 import re
@@ -139,8 +140,7 @@ def update_package_listings(args, packages, arch):
         # for each tarfile, write tarfile listing
         #
 
-        for t in packages[p].tars:
-
+        for t in itertools.chain.from_iterable([packages[p].tars[vr] for vr in packages[p].tars]):
             fver = re.sub(r'\.tar.*$', '', t)
             listing = os.path.join(dir, fver)
 
@@ -172,7 +172,7 @@ def update_package_listings(args, packages, arch):
                         tf = os.path.join(args.rel_area, packages[p].path, t)
                         if not os.path.exists(tf):
                             # this shouldn't happen with a full mirror
-                            logging.error("tarfile %s not found %s" % (tf))
+                            logging.error("tarfile %s not found" % (tf))
                         elif os.path.getsize(tf) <= 32:
                             # compressed empty files aren't a valid tar file,
                             # but we can just ignore them
diff --git a/calm/setup_exe.py b/calm/setup_exe.py
index d9d85be..dd09bbd 100755
--- a/calm/setup_exe.py
+++ b/calm/setup_exe.py
@@ -56,6 +56,7 @@ def extract_version(fn):
     else:
         return None
 
+
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(description='Extract version from setup executable')
     parser.add_argument('exe', action='store', nargs='?', metavar='filename', help='executable file')
diff --git a/calm/uploads.py b/calm/uploads.py
index 4b4725d..dc95d48 100644
--- a/calm/uploads.py
+++ b/calm/uploads.py
@@ -116,6 +116,36 @@ def scan(m, all_packages, arch, args):
             logging.warning("package '%s' is not in the package list for maintainer %s" % (dirpath, m.name))
             continue
 
+        # see if we can fix-up any setup.hint files
+        pvr = None
+        ambiguous = False
+        seen = False
+
+        for f in sorted(files):
+            # warn about legacy setup.hint uploads
+            if f == 'setup.hint':
+                logging.warning("'%s' seen, please update to cygport >= 0.23.0" % f)
+                seen = True
+
+            match = re.match(r'^([^-].*?)(-src|)\.tar\.(bz2|gz|lzma|xz)$', f)
+            if match:
+                if (pvr is not None) and (pvr != match.group(1)):
+                    ambiguous = True
+
+                pvr = match.group(1)
+
+        if seen:
+            if ambiguous or (pvr is None):
+                error = True
+                logging.error("'setup.hint' seen in %s, and couldn't determine what version it applies to", dirpath)
+            else:
+                old = "setup.hint"
+                new = pvr + ".hint"
+                logging.warning("renaming '%s' to '%s'" % (old, new))
+                os.rename(os.path.join(dirpath, old), os.path.join(dirpath, new))
+                files.remove(old)
+                files.append(new)
+
         # filter out files we don't need to consider
         for f in sorted(files):
             fn = os.path.join(dirpath, f)
@@ -171,10 +201,6 @@ def scan(m, all_packages, arch, args):
                 files.remove(f)
                 continue
 
-            # warn about legacy setup.hint uploads
-            if f == 'setup.hint':
-                logging.warning("'%s' seen, please update to cygport >= 0.23.0" % fn)
-
             # verify compressed archive files are valid
             if re.search(r'\.tar\.(bz2|gz|lzma|xz)$', f):
                 valid = True
diff --git a/pep8 b/pep8
index d52ae5b..2729252 100755
--- a/pep8
+++ b/pep8
@@ -1,2 +1,2 @@
 #!/bin/sh
-grep -s -l '^#!/usr/bin/env python' calm/* tests/* | xargs python3 -m pep8 --count --show-source --max-line-length=240 --ignore=E129
+grep -s -l '^#!/usr/bin/env python' calm/* tests/* | xargs python3 -m pycodestyle --count --show-source --max-line-length=240 --ignore=E129,E741
diff --git a/test/testdata/htdocs.expected/x86/obs-a/.htaccess b/test/testdata/htdocs.expected/x86/obs-a/.htaccess
new file mode 100644
index 0000000..3196d64
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-a/.htaccess
@@ -0,0 +1,3 @@
+Options Indexes
+IndexOptions -FancyIndexing
+AddType text/html 1 2 3 4 5 6 7 8 9
diff --git a/test/testdata/htdocs.expected/x86/obs-a/obs-a-1.0-1 b/test/testdata/htdocs.expected/x86/obs-a/obs-a-1.0-1
new file mode 100644
index 0000000..5fd9229
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-a/obs-a-1.0-1
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>obs-a: obsolete package A</title>
+</head>
+<body>
+<h1>obs-a: obsolete package A</h1>
+<pre>
+    2017-05-22 13:04          82 test-a-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/obs-a/obs-a-1.0-1-src b/test/testdata/htdocs.expected/x86/obs-a/obs-a-1.0-1-src
new file mode 100644
index 0000000..9bb71f6
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-a/obs-a-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>obs-a: obsolete package A (source code)</title>
+</head>
+<body>
+<h1>obs-a: obsolete package A (source code)</h1>
+<pre>
+    2017-05-22 13:04          82 test-a-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/obs-b/.htaccess b/test/testdata/htdocs.expected/x86/obs-b/.htaccess
new file mode 100644
index 0000000..3196d64
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-b/.htaccess
@@ -0,0 +1,3 @@
+Options Indexes
+IndexOptions -FancyIndexing
+AddType text/html 1 2 3 4 5 6 7 8 9
diff --git a/test/testdata/htdocs.expected/x86/obs-b/obs-b-1.0-1 b/test/testdata/htdocs.expected/x86/obs-b/obs-b-1.0-1
new file mode 100644
index 0000000..d7f6187
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-b/obs-b-1.0-1
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>obs-b: obsolete package B</title>
+</head>
+<body>
+<h1>obs-b: obsolete package B</h1>
+<pre>
+    2017-05-22 13:04          82 test-a-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/obs-b/obs-b-1.0-1-src b/test/testdata/htdocs.expected/x86/obs-b/obs-b-1.0-1-src
new file mode 100644
index 0000000..7862528
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/obs-b/obs-b-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>obs-b: obsolete package B (source code)</title>
+</head>
+<body>
+<h1>obs-b: obsolete package B (source code)</h1>
+<pre>
+    2017-05-22 13:04          82 test-a-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/packages.inc b/test/testdata/htdocs.expected/x86/packages.inc
index 346b7f6..fa8b66b 100755
--- a/test/testdata/htdocs.expected/x86/packages.inc
+++ b/test/testdata/htdocs.expected/x86/packages.inc
@@ -15,12 +15,17 @@
 <tr><td><a href="x86/libdns_sd-devel">libdns_sd-devel</a></td><td>Bonjour Zeroconf implementation</td></tr>
 <tr><td><a href="x86/libdns_sd1">libdns_sd1</a></td><td>Bonjour Zeroconf implementation</td></tr>
 <tr><td><a href="x86/mDNSResponder">mDNSResponder</a></td><td>Bonjour Zeroconf implementation</td></tr>
+<tr><td><a href="x86/obs-a">obs-a</a></td><td>obsolete package A</td></tr>
+<tr><td><a href="x86/obs-b">obs-b</a></td><td>obsolete package B</td></tr>
 <tr><td><a href="x86/openssh">openssh</a></td><td>The OpenSSH server and client programs</td></tr>
 <tr><td><a href="x86/per-version">per-version</a></td><td>Per-version hint test package</td></tr>
 <tr><td><a href="x86/per-version-replacement-hint-only">per-version-replacement-hint-only</a></td><td>Per-version hint test package</td></tr>
 <tr><td><a href="x86/perl-Net-SMTP-SSL">perl-Net-SMTP-SSL</a></td><td>Perl distribution Net-SMTP-SSL</td></tr>
 <tr><td><a href="x86/rpm-doc">rpm-doc</a></td><td>Obsolete package for RPM package management system manual pages</td></tr>
 <tr><td><a href="x86/staleversion">staleversion</a></td><td>Test package for stale version removal</td></tr>
+<tr><td><a href="x86/test-c">test-c</a></td><td>test package C</td></tr>
+<tr><td><a href="x86/test-d">test-d</a></td><td>test package D</td></tr>
+<tr><td><a href="x86/test-e">test-e</a></td><td>test package E</td></tr>
 <tr><td><a href="x86/testpackage">testpackage</a></td><td>A test package (stuff &amp; other stuff)</td></tr>
 </table>
 </div>
diff --git a/test/testdata/htdocs.expected/x86/test-c/.htaccess b/test/testdata/htdocs.expected/x86/test-c/.htaccess
new file mode 100644
index 0000000..3196d64
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-c/.htaccess
@@ -0,0 +1,3 @@
+Options Indexes
+IndexOptions -FancyIndexing
+AddType text/html 1 2 3 4 5 6 7 8 9
diff --git a/test/testdata/htdocs.expected/x86/test-c/test-c-1.0-1 b/test/testdata/htdocs.expected/x86/test-c/test-c-1.0-1
new file mode 100644
index 0000000..f5730e6
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-c/test-c-1.0-1
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-c: test package C</title>
+</head>
+<body>
+<h1>test-c: test package C</h1>
+<pre>
+    2017-05-22 13:27          82 test-c-0.5-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-c/test-c-1.0-1-src b/test/testdata/htdocs.expected/x86/test-c/test-c-1.0-1-src
new file mode 100644
index 0000000..ae1aaff
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-c/test-c-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-c: test package C (source code)</title>
+</head>
+<body>
+<h1>test-c: test package C (source code)</h1>
+<pre>
+    2017-05-22 13:27          82 test-c-0.5-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-d/.htaccess b/test/testdata/htdocs.expected/x86/test-d/.htaccess
new file mode 100644
index 0000000..3196d64
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-d/.htaccess
@@ -0,0 +1,3 @@
+Options Indexes
+IndexOptions -FancyIndexing
+AddType text/html 1 2 3 4 5 6 7 8 9
diff --git a/test/testdata/htdocs.expected/x86/test-d/test-d-1.0-1 b/test/testdata/htdocs.expected/x86/test-d/test-d-1.0-1
new file mode 100644
index 0000000..e964dd5
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-d/test-d-1.0-1
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-d: test package D</title>
+</head>
+<body>
+<h1>test-d: test package D</h1>
+<pre>
+    2017-05-22 18:17          40 test-d-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-d/test-d-1.0-1-src b/test/testdata/htdocs.expected/x86/test-d/test-d-1.0-1-src
new file mode 100644
index 0000000..655f79c
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-d/test-d-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-d: test package D (source code)</title>
+</head>
+<body>
+<h1>test-d: test package D (source code)</h1>
+<pre>
+    2017-05-22 18:17          40 test-d-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-e/.htaccess b/test/testdata/htdocs.expected/x86/test-e/.htaccess
new file mode 100644
index 0000000..3196d64
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-e/.htaccess
@@ -0,0 +1,3 @@
+Options Indexes
+IndexOptions -FancyIndexing
+AddType text/html 1 2 3 4 5 6 7 8 9
diff --git a/test/testdata/htdocs.expected/x86/test-e/test-e-1.0-1 b/test/testdata/htdocs.expected/x86/test-e/test-e-1.0-1
new file mode 100644
index 0000000..86b1132
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-e/test-e-1.0-1
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-e: test package E</title>
+</head>
+<body>
+<h1>test-e: test package E</h1>
+<pre>
+    2017-05-22 18:17          40 test-d-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/htdocs.expected/x86/test-e/test-e-1.0-1-src b/test/testdata/htdocs.expected/x86/test-e/test-e-1.0-1-src
new file mode 100644
index 0000000..8e0d879
--- /dev/null
+++ b/test/testdata/htdocs.expected/x86/test-e/test-e-1.0-1-src
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test-e: test package E (source code)</title>
+</head>
+<body>
+<h1>test-e: test package E (source code)</h1>
+<pre>
+    2017-05-22 18:17          40 test-d-1.0-1.hint
+</pre>
+</body>
+</html>
diff --git a/test/testdata/inifile/setup.ini.expected b/test/testdata/inifile/setup.ini.expected
index 64281f8..ced8b29 100644
--- a/test/testdata/inifile/setup.ini.expected
+++ b/test/testdata/inifile/setup.ini.expected
@@ -180,6 +180,24 @@
  'source: x86/release/mDNSResponder/mDNSResponder-379.32.1-1-src.tar.bz2 195 '
  'aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83\n'
  '\n'
+ '@ obs-a\n'
+ 'sdesc: "obsolete package A"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/obs-a/obs-a-1.0-1.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ 'source: noarch/release/obs-a/obs-a-1.0-1-src.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ '\n'
+ '@ obs-b\n'
+ 'sdesc: "obsolete package B"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/obs-b/obs-b-1.0-1.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ 'source: noarch/release/obs-b/obs-b-1.0-1-src.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ '\n'
  '@ openssh\n'
  'sdesc: "The OpenSSH server and client programs"\n'
  'ldesc: "OpenSSH is a program for logging into a remote machine and for\n'
@@ -284,6 +302,36 @@
  'source: x86/release/staleversion/staleversion-260-0-src.tar.xz 228 '
  'e675b0ac4bc2c3e1c4971bc56d77b0cd53a9bdf5632873a235d7582e29dfd3e8a7bb04b28f6cdee3e6b3d14c25ed39392538e3f628a9bfda6c905646ebc3c225\n'
  '\n'
+ '@ test-c\n'
+ 'sdesc: "test package C"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/test-c/test-c-1.0-1.tar.xz 256 '
+ 'ef15790d8dc8163ed15dfca37565558203ed8b7569d586e0bc949f25282f44a1c059a60a7502863312b41cda649e3a9e2516d354eec9d54829e3ac1a3547097c\n'
+ 'source: noarch/release/test-c/test-c-1.0-1-src.tar.xz 256 '
+ 'ef15790d8dc8163ed15dfca37565558203ed8b7569d586e0bc949f25282f44a1c059a60a7502863312b41cda649e3a9e2516d354eec9d54829e3ac1a3547097c\n'
+ 'depends: test-d (>= 1.0), test-e\n'
+ 'obsoletes: obs-a, obs-b\n'
+ '\n'
+ '@ test-d\n'
+ 'sdesc: "test package D"\n'
+ 'category: Devel\n'
+ 'version: 1.0.42590-1\n'
+ 'install: noarch/release/test-d/test-d-1.0-1.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ 'source: noarch/release/test-d/test-d-1.0-1-src.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ '\n'
+ '@ test-e\n'
+ 'sdesc: "test package E"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/test-e/test-e-1.0-1.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ 'source: noarch/release/test-e/test-e-1.0-1-src.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ 'build-depends: libtextcat-devel\n'
+ '\n'
  '@ testpackage\n'
  'sdesc: "A test package (stuff & other stuff)"\n'
  'ldesc: "A test package (stuff & other stuff)"\n'
diff --git a/test/testdata/process_arch/htdocs.expected b/test/testdata/process_arch/htdocs.expected
index 235021c..b0fb21c 100644
--- a/test/testdata/process_arch/htdocs.expected
+++ b/test/testdata/process_arch/htdocs.expected
@@ -19,6 +19,8 @@
  'x86/libdns_sd-devel': ['.htaccess', 'libdns_sd-devel-379.32.1-1'],
  'x86/libdns_sd1': ['.htaccess', 'libdns_sd1-379.32.1-1'],
  'x86/mDNSResponder': ['.htaccess', 'mDNSResponder-379.32.1-1', 'mDNSResponder-379.32.1-1-src'],
+ 'x86/obs-a': ['.htaccess', 'obs-a-1.0-1', 'obs-a-1.0-1-src'],
+ 'x86/obs-b': ['.htaccess', 'obs-b-1.0-1', 'obs-b-1.0-1-src'],
  'x86/openssh': ['.htaccess', 'openssh-7.2p2-1', 'openssh-7.2p2-1-src'],
  'x86/per-version': ['.htaccess',
                      'per-version-4.0-1',
@@ -43,5 +45,8 @@
                       'staleversion-250-0-src',
                       'staleversion-260-0',
                       'staleversion-260-0-src'],
+ 'x86/test-c': ['.htaccess', 'test-c-1.0-1', 'test-c-1.0-1-src'],
+ 'x86/test-d': ['.htaccess', 'test-d-1.0-1', 'test-d-1.0-1-src'],
+ 'x86/test-e': ['.htaccess', 'test-e-1.0-1', 'test-e-1.0-1-src'],
  'x86/testpackage': ['.htaccess', 'testpackage-1.0-1', 'testpackage-1.0-1-src'],
  'x86/testpackage-subpackage': ['.htaccess', 'testpackage-subpackage-1.0-1']}
diff --git a/test/testdata/process_arch/rel_area.expected b/test/testdata/process_arch/rel_area.expected
index 51f4a24..a105cdc 100644
--- a/test/testdata/process_arch/rel_area.expected
+++ b/test/testdata/process_arch/rel_area.expected
@@ -1,12 +1,18 @@
 {'.': ['setup.ini'],
  'noarch': ['sha512.sum'],
  'noarch/release': ['sha512.sum'],
+ 'noarch/release/obs-a': ['obs-a-1.0-1-src.tar.xz', 'obs-a-1.0-1.hint', 'obs-a-1.0-1.tar.xz', 'sha512.sum'],
+ 'noarch/release/obs-b': ['obs-b-1.0-1-src.tar.xz', 'obs-b-1.0-1.hint', 'obs-b-1.0-1.tar.xz', 'sha512.sum'],
  'noarch/release/perl-Net-SMTP-SSL': ['perl-Net-SMTP-SSL-1.03-1-src.tar.xz',
                                       'perl-Net-SMTP-SSL-1.03-1.tar.xz',
                                       'perl-Net-SMTP-SSL-1.03-2-src.tar.xz',
+                                      'perl-Net-SMTP-SSL-1.03-2.hint',
                                       'perl-Net-SMTP-SSL-1.03-2.tar.xz',
                                       'setup.hint',
                                       'sha512.sum'],
+ 'noarch/release/test-c': ['sha512.sum', 'test-c-1.0-1-src.tar.xz', 'test-c-1.0-1.hint', 'test-c-1.0-1.tar.xz'],
+ 'noarch/release/test-d': ['sha512.sum', 'test-d-1.0-1-src.tar.xz', 'test-d-1.0-1.hint', 'test-d-1.0-1.tar.xz'],
+ 'noarch/release/test-e': ['sha512.sum', 'test-e-1.0-1-src.tar.xz', 'test-e-1.0-1.hint', 'test-e-1.0-1.tar.xz'],
  'x86': ['sha512.sum'],
  'x86/release': ['sha512.sum'],
  'x86/release/arc': ['arc-4.32.7-10-src.tar.bz2', 'arc-4.32.7-10.tar.bz2', 'setup.hint'],
@@ -93,5 +99,10 @@
                               'staleversion-250-0.tar.xz',
                               'staleversion-260-0-src.tar.xz',
                               'staleversion-260-0.tar.xz'],
- 'x86/release/testpackage': ['setup.hint', 'sha512.sum', 'testpackage-1.0-1-src.tar.bz2', 'testpackage-1.0-1.tar.bz2'],
- 'x86/release/testpackage/testpackage-subpackage': ['setup.hint', 'testpackage-subpackage-1.0-1.tar.bz2']}
+ 'x86/release/testpackage': ['setup.hint',
+                             'sha512.sum',
+                             'testpackage-1.0-1-src.tar.bz2',
+                             'testpackage-1.0-1.hint',
+                             'testpackage-1.0-1.tar.bz2'],
+ 'x86/release/testpackage/testpackage-subpackage': ['testpackage-subpackage-1.0-1.hint',
+                                                    'testpackage-subpackage-1.0-1.tar.bz2']}
diff --git a/test/testdata/process_arch/setup.ini.expected b/test/testdata/process_arch/setup.ini.expected
index 652e48f..01766f0 100644
--- a/test/testdata/process_arch/setup.ini.expected
+++ b/test/testdata/process_arch/setup.ini.expected
@@ -180,6 +180,24 @@
  'source: x86/release/mDNSResponder/mDNSResponder-379.32.1-1-src.tar.bz2 195 '
  'aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83\n'
  '\n'
+ '@ obs-a\n'
+ 'sdesc: "obsolete package A"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/obs-a/obs-a-1.0-1.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ 'source: noarch/release/obs-a/obs-a-1.0-1-src.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ '\n'
+ '@ obs-b\n'
+ 'sdesc: "obsolete package B"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/obs-b/obs-b-1.0-1.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ 'source: noarch/release/obs-b/obs-b-1.0-1-src.tar.xz 256 '
+ '060b37ee31b74d1abca5cf3c3ac787195b0af4bb140b9e7f59537a22a6bdbe77fb75cdc77cc839e9650e20d8a9665f4edf22de5c34864ffa4814068cad7925c9\n'
+ '\n'
  '@ openssh\n'
  'sdesc: "The OpenSSH server and client programs"\n'
  'ldesc: "OpenSSH is a program for logging into a remote machine and for\n'
@@ -278,6 +296,36 @@
  'source: x86/release/staleversion/staleversion-260-0-src.tar.xz 228 '
  'e675b0ac4bc2c3e1c4971bc56d77b0cd53a9bdf5632873a235d7582e29dfd3e8a7bb04b28f6cdee3e6b3d14c25ed39392538e3f628a9bfda6c905646ebc3c225\n'
  '\n'
+ '@ test-c\n'
+ 'sdesc: "test package C"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/test-c/test-c-1.0-1.tar.xz 256 '
+ 'ef15790d8dc8163ed15dfca37565558203ed8b7569d586e0bc949f25282f44a1c059a60a7502863312b41cda649e3a9e2516d354eec9d54829e3ac1a3547097c\n'
+ 'source: noarch/release/test-c/test-c-1.0-1-src.tar.xz 256 '
+ 'ef15790d8dc8163ed15dfca37565558203ed8b7569d586e0bc949f25282f44a1c059a60a7502863312b41cda649e3a9e2516d354eec9d54829e3ac1a3547097c\n'
+ 'depends: test-d (>= 1.0), test-e\n'
+ 'obsoletes: obs-a, obs-b\n'
+ '\n'
+ '@ test-d\n'
+ 'sdesc: "test package D"\n'
+ 'category: Devel\n'
+ 'version: 1.0.42590-1\n'
+ 'install: noarch/release/test-d/test-d-1.0-1.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ 'source: noarch/release/test-d/test-d-1.0-1-src.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ '\n'
+ '@ test-e\n'
+ 'sdesc: "test package E"\n'
+ 'category: Devel\n'
+ 'version: 1.0-1\n'
+ 'install: noarch/release/test-e/test-e-1.0-1.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ 'source: noarch/release/test-e/test-e-1.0-1-src.tar.xz 236 '
+ '2e47817b620a7dbfe6810d47aee0adbcdf4663dc57932bcc51fc4b9bcfafa00dbbc2bfbefb1692dd5f6cc5333f23aea324d7c4d9b2ca091b917811fcdb70ca1f\n'
+ 'build-depends: libtextcat-devel\n'
+ '\n'
  '@ testpackage\n'
  'sdesc: "A test package"\n'
  'ldesc: "A test package\n'
diff --git a/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1-src.tar.xz b/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1-src.tar.xz
new file mode 100755
index 0000000..0f77b59
Binary files /dev/null and b/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1-src.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1.hint b/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1.hint
new file mode 100755
index 0000000..1e7ec26
--- /dev/null
+++ b/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1.hint
@@ -0,0 +1,2 @@
+category: Devel
+sdesc: "obsolete package A"
diff --git a/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1.tar.xz b/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1.tar.xz
new file mode 100755
index 0000000..0f77b59
Binary files /dev/null and b/test/testdata/relarea/noarch/release/obs-a/obs-a-1.0-1.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1-src.tar.xz b/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1-src.tar.xz
new file mode 100755
index 0000000..0f77b59
Binary files /dev/null and b/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1-src.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1.hint b/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1.hint
new file mode 100755
index 0000000..cc7c23c
--- /dev/null
+++ b/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1.hint
@@ -0,0 +1,2 @@
+category: Devel
+sdesc: "obsolete package B"
diff --git a/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1.tar.xz b/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1.tar.xz
new file mode 100755
index 0000000..0f77b59
Binary files /dev/null and b/test/testdata/relarea/noarch/release/obs-b/obs-b-1.0-1.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1-src.tar.xz b/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1-src.tar.xz
new file mode 100755
index 0000000..a2956b0
Binary files /dev/null and b/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1-src.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1.hint b/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1.hint
new file mode 100755
index 0000000..9db4f40
--- /dev/null
+++ b/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1.hint
@@ -0,0 +1,4 @@
+category: Devel
+sdesc: "test package C"
+obsoletes: obs-a, obs-b
+depends: test-d (>= 1.0), test-e
diff --git a/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1.tar.xz b/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1.tar.xz
new file mode 100755
index 0000000..a2956b0
Binary files /dev/null and b/test/testdata/relarea/noarch/release/test-c/test-c-1.0-1.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1-src.tar.xz b/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1-src.tar.xz
new file mode 100755
index 0000000..9f47642
Binary files /dev/null and b/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1-src.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1.hint b/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1.hint
new file mode 100755
index 0000000..39cc9d0
--- /dev/null
+++ b/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1.hint
@@ -0,0 +1,3 @@
+category: Devel
+sdesc: "test package D"
+version: 1.0.42590-1
diff --git a/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1.tar.xz b/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1.tar.xz
new file mode 100755
index 0000000..9f47642
Binary files /dev/null and b/test/testdata/relarea/noarch/release/test-d/test-d-1.0-1.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1-src.tar.xz b/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1-src.tar.xz
new file mode 100755
index 0000000..9f47642
Binary files /dev/null and b/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1-src.tar.xz differ
diff --git a/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1.hint b/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1.hint
new file mode 100755
index 0000000..d151b15
--- /dev/null
+++ b/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1.hint
@@ -0,0 +1,3 @@
+category: Devel
+sdesc: "test package E"
+build-depends: libtextcat-devel
diff --git a/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1.tar.xz b/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1.tar.xz
new file mode 100755
index 0000000..9f47642
Binary files /dev/null and b/test/testdata/relarea/noarch/release/test-e/test-e-1.0-1.tar.xz differ
diff --git a/test/testdata/uploads/move.expected b/test/testdata/uploads/move.expected
index 47cb0af..fda6084 100644
--- a/test/testdata/uploads/move.expected
+++ b/test/testdata/uploads/move.expected
@@ -1,3 +1,5 @@
-{'x86/release/testpackage': ['setup.hint', 'testpackage-1.0-1-src.tar.bz2', 'testpackage-1.0-1.tar.bz2'],
- 'x86/release/testpackage/testpackage-subpackage': ['setup.hint', 'testpackage-subpackage-1.0-1.tar.bz2'],
- 'x86/release/testpackage2/testpackage2-subpackage': ['setup.hint', 'testpackage2-subpackage-1.0-1.tar.bz2']}
+{'x86/release/testpackage': ['testpackage-1.0-1-src.tar.bz2', 'testpackage-1.0-1.hint', 'testpackage-1.0-1.tar.bz2'],
+ 'x86/release/testpackage/testpackage-subpackage': ['testpackage-subpackage-1.0-1.hint',
+                                                    'testpackage-subpackage-1.0-1.tar.bz2'],
+ 'x86/release/testpackage2/testpackage2-subpackage': ['testpackage2-subpackage-1.0-1.hint',
+                                                      'testpackage2-subpackage-1.0-1.tar.bz2']}
diff --git a/test/testdata/uploads/pkglist.expected b/test/testdata/uploads/pkglist.expected
index 0e4c587..aec9b6f 100644
--- a/test/testdata/uploads/pkglist.expected
+++ b/test/testdata/uploads/pkglist.expected
@@ -1,16 +1,16 @@
-{'testpackage': Package('x86/release/testpackage', {'testpackage-1.0-1-src.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False),
- 'testpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}, {'1.0-1': {'sdesc': '"A test package"',
+{'testpackage': Package('x86/release/testpackage', {'1.0-1': {'testpackage-1.0-1-src.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False),
+           'testpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}}, {'1.0-1': {'sdesc': '"A test package"',
            'ldesc': '"A test package\n'
                     "It's description might contains some unicode "
                     'gibberish\n'
                     'Like it’s you’re Markup Language™ Nokogiri’s tool―that '
                     'Bézier."',
            'category': 'Devel',
-           'requires': 'cygwin'}}, {'curr': None, 'prev': None, 'test': None}, False),
- 'testpackage-subpackage': Package('x86/release/testpackage/testpackage-subpackage', {'testpackage-subpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}, {'1.0-1': {'sdesc': '"A test subpackage"',
+           'requires': 'cygwin'}}, {}, False),
+ 'testpackage-subpackage': Package('x86/release/testpackage/testpackage-subpackage', {'1.0-1': {'testpackage-subpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}}, {'1.0-1': {'sdesc': '"A test subpackage"',
            'ldesc': '"A test subpackage"',
            'category': 'Devel',
-           'external-source': 'testpackage'}}, {'curr': None, 'prev': None, 'test': None}, False),
- 'testpackage2-subpackage': Package('x86/release/testpackage2/testpackage2-subpackage', {'testpackage2-subpackage-1.0-1.tar.bz2': Tar('6de201dfed1d45412509c65deb34690dc2d09c6aafccfe491fd2f440f92842b9c755b61dc7bcdd4cc0c9f18cf46c2b3a1241e99c4c2a33fff5555e7b2f0b6348', 14, True)}, {'1.0-1': {'sdesc': '"A test subpackage 2"',
+           'external-source': 'testpackage'}}, {}, False),
+ 'testpackage2-subpackage': Package('x86/release/testpackage2/testpackage2-subpackage', {'1.0-1': {'testpackage2-subpackage-1.0-1.tar.bz2': Tar('6de201dfed1d45412509c65deb34690dc2d09c6aafccfe491fd2f440f92842b9c755b61dc7bcdd4cc0c9f18cf46c2b3a1241e99c4c2a33fff5555e7b2f0b6348', 14, True)}}, {'1.0-1': {'sdesc': '"A test subpackage 2"',
            'ldesc': '"A test subpackage 2"',
-           'category': 'Devel'}}, {'curr': None, 'prev': None, 'test': None}, False)}
+           'category': 'Devel'}}, {}, False)}


                 reply	other threads:[~2017-11-09 18:22 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20171109182231.14833.qmail@sourceware.org \
    --to=jturney@sourceware.org \
    --cc=cygwin-apps-cvs@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).