This is the mail archive of the cygwin-apps-cvs mailing list for the cygwin-apps project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[calm - Cygwin server-side packaging maintenance script] branch master, updated. df7ae0d55a584fdd03ed01f3f073e8d4e0abf826




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

commit df7ae0d55a584fdd03ed01f3f073e8d4e0abf826
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Mon Mar 21 11:40:37 2016 +0000

    Fix buffering_smtp_handler for non-ascii log content

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

commit 45d1a06888bb883f2857670e04f720339b4883db
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Fri Mar 18 01:17:41 2016 +0000

    Test directory trees are as expected after moving uploads/vaultings
    
    Add a test which verifies that directory trees are as expected after moving
    files from uploads to rel_area and from rel_area to vault

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

commit 52f986d0d1a253204c2a5234a47baa46ddc1be27
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Mon Mar 21 11:29:40 2016 +0000

    Move calm -> calm.py and add wrapper script
    
    This puts everything into a module, so we can access it easily for testing.

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

commit 01e3d61d719934b94acb019c6eb601e736088d89
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Fri Mar 18 13:52:50 2016 +0000

    Fix testdata so validate_packages() can succeed
    
    - add a mock openssh package
    - make all libtext packages fail parsing with ldesc embedded quote
    - remove proj-debuginfo, since all other proj packages fail parsing
    - add rpm-doc-4.1-2-src mock package
    - remove unmocked requires of naim packge
    
    Assert that validate_package succeeds

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

commit 2e8d0f77349fe9e3a7eeaed744954dcadc2c25c9
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Fri Mar 18 00:21:53 2016 +0000

    Add a test of deletion cookies

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

commit ad0dfa20d33c32d4b12a5c08ed7fa6a576b601cc
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Thu Mar 17 23:10:57 2016 +0000

    Sort paths, don't repeat full path for every file moved
    
    This is a cosmetic improvement to reporting

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

commit a04d6b677f047f91620e2de7c3156fdb3020d8b3
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sat Mar 19 11:51:50 2016 +0000

    Fix warning about no setup.hint not always being issued
    
    We want to issue this warning in the case of a path like 'release/mintty',
    which has one path.sep


Diff:
---
 buffering_smtp_handler.py                          |   18 ++-
 calm                                               |  198 +-------------------
 calm.py                                            |  195 +++++++++++++++++++
 package.py                                         |    2 +-
 .../testpackage/testpackage-1.0-1-src.tar.bz2      |  Bin 0 -> 195 bytes
 .../testpackage/testpackage-subpackage/setup.hint  |    2 +-
 testdata/htdocs.expected/x86/openssh/.htaccess     |    3 +
 .../htdocs.expected/x86/openssh/openssh-7.2p2-1    |    7 +
 .../x86/openssh/openssh-7.2p2-1-src                |    7 +
 testdata/htdocs.expected/x86/packages.inc          |    4 +-
 .../htdocs.expected/x86/rpm-doc/rpm-doc-4.1-2-src  |    5 +
 testdata/inifile/setup.ini.expected                |   16 ++-
 testdata/pkglist/cygwin-pkg-maint                  |    1 +
 testdata/pkglist/expected                          |    2 +-
 testdata/process_arch/homedir.expected             |   14 ++
 testdata/process_arch/htdocs.expected              |   24 +++
 testdata/process_arch/rel_area.expected            |   57 ++++++
 testdata/process_arch/vault.expected               |    1 +
 testdata/uploads/move.expected                     |    2 +-
 testdata/uploads/pkglist.expected                  |    6 +-
 .../release/libtextcat/libtextcat-devel/expected   |   10 +-
 .../release/libtextcat/libtextcat0/expected        |   10 +-
 testdata/x86.hints/release/naim/expected           |    2 +-
 testdata/x86.hints/release/openssh/expected        |    5 +
 .../release/libtextcat/libtextcat-devel/setup.hint |    7 +
 .../x86/release/libtextcat/libtextcat0/setup.hint  |    7 +
 testdata/x86/release/naim/setup.hint               |    2 +-
 .../x86/release/openssh/openssh-7.2p2-1-src.tar.xz |  Bin 0 -> 228 bytes
 .../x86/release/openssh/openssh-7.2p2-1.tar.xz     |  Bin 0 -> 228 bytes
 testdata/x86/release/openssh/setup.hint            |    5 +
 .../proj-debuginfo/proj-debuginfo-4.8.0-1.tar.xz   |  Bin 195 -> 0 bytes
 .../x86/release/proj/proj-debuginfo/setup.hint     |    6 -
 .../x86/release/rpm-doc/rpm-doc-4.1-2-src.tar.bz2  |  Bin 0 -> 42 bytes
 tests.py                                           |   64 ++++++-
 uploads.py                                         |    7 +-
 35 files changed, 463 insertions(+), 226 deletions(-)

diff --git a/buffering_smtp_handler.py b/buffering_smtp_handler.py
index 16d9bc8..7720588 100644
--- a/buffering_smtp_handler.py
+++ b/buffering_smtp_handler.py
@@ -23,6 +23,7 @@
 from contextlib import ExitStack
 import logging
 import logging.handlers
+import email.message
 
 import common_constants
 
@@ -46,11 +47,24 @@ class BufferingSMTPHandler(logging.handlers.BufferingHandler):
 
     def flush(self):
         if len(self.buffer) > 0:
-            msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % (self.fromaddr, ','.join(self.toaddrs), self.subject)
+            msg = ""
             for record in self.buffer:
                 s = self.format(record)
                 msg = msg + s + "\r\n"
 
+            m = email.message.Message()
+            m['From'] = self.fromaddr
+            m['To'] = ','.join(self.toaddrs)
+            m['Subject'] = self.subject
+
+            # use utf-8 only if the message can't be ascii encoded
+            charset = 'ascii'
+            try:
+                msg.encode('ascii')
+            except UnicodeError:
+                charset = 'utf-8'
+            m.set_payload(msg, charset=charset)
+
             # if toaddrs consists of the single address 'debug', just dump the mail we would have sent
             if self.toaddrs == ['debug']:
                 print('-' * 40)
@@ -63,7 +77,7 @@ class BufferingSMTPHandler(logging.handlers.BufferingHandler):
                     if not port:
                         port = smtplib.SMTP_PORT
                     smtp = smtplib.SMTP(self.mailhost, port)
-                    smtp.sendmail(self.fromaddr, self.toaddrs, msg)
+                    smtp.send_message(m)
                     smtp.quit()
                 except:
                     self.handleError(self.buffer[0])  # first record
diff --git a/calm b/calm
index 9638d9e..ac1524e 100755
--- a/calm
+++ b/calm
@@ -1,195 +1,3 @@
-#!/usr/bin/env python3
-#
-# Copyright (c) 2015 Jon Turney
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-
-#
-# calm - better than being upset
-#
-
-#
-# read packages from release area
-# for each maintainer
-# - read and validate any package uploads
-# - build a list of files to move and remove
-# - merge package sets
-# - remove from the package set files which are to be removed
-# - validate merged package set
-# - process remove list
-# - on failure
-# -- mail maintainer with errors
-# -- empty move list
-# -- discard merged package set
-# - on success
-# -- process move list
-# -- mail maintainer with movelist
-# -- continue with merged package set
-# write setup.ini file
-#
-
-import argparse
-import logging
-import os
-import sys
-
-from buffering_smtp_handler import mail_logs
-import common_constants
-import maintainers
-import package
-import pkg2html
-import uploads
-
-
-#
-#
-#
-
-def main(args):
-    details = '%s%s' % (args.arch, ',dry-run' if args.dryrun else '')
-
-    # send one email per run to leads
-    with mail_logs(args.email, toaddrs=args.email, subject='calm messages [%s]' % (details)) as leads_email:
-        if args.dryrun:
-            logging.warning("--dry-run in effect, nothing will really be done")
-
-        # build package list
-        packages = package.read_packages(args.rel_area, args.arch)
-
-        # validate the package set
-        if not package.validate_packages(args, packages):
-            logging.error("existing package set has errors, not processing uploads or writing setup.ini")
-            return 1
-
-        # read maintainer list
-        mlist = maintainers.Maintainer.read(args)
-
-        # make the list of all packages
-        all_packages = maintainers.Maintainer.all_packages(mlist)
-
-        # for each maintainer
-        for name in sorted(mlist.keys()):
-            m = mlist[name]
-
-            # also send a mail to each maintainer about their packages
-            with mail_logs(args.email, toaddrs=m.email, subject='calm messages for %s [%s]' % (name, details)) as maint_email:
-
-                (error, mpackages, to_relarea, to_vault, remove_always, remove_success) = uploads.scan(m, all_packages, args)
-
-                uploads.remove(args, remove_always)
-
-                # if there are no uploaded packages for this maintainer, we
-                # don't have anything to do
-                if not mpackages:
-                    logging.info("nothing to do for maintainer %s" % (name))
-                    continue
-
-                if not error:
-                    # merge package set
-                    merged_packages = package.merge(packages, mpackages)
-
-                    # remove file which are to be removed
-                    #
-                    # XXX: this doesn't properly account for removing setup.hint
-                    # files
-                    for p in to_vault:
-                        for f in to_vault[p]:
-                            package.delete(merged_packages, p, f)
-
-                    # validate the package set
-                    if package.validate_packages(args, merged_packages):
-                        # process the move list
-                        uploads.move_to_vault(args, to_vault)
-                        uploads.remove(args, remove_success)
-                        uploads.move_to_relarea(m, args, to_relarea)
-                        # use merged package list
-                        packages = merged_packages
-                        logging.info("added %d packages from maintainer %s" % (len(mpackages), name))
-                    else:
-                        # otherwise we discard move list and merged_packages
-                        logging.error("error while merging uploaded packages for %s" % (name))
-
-        # write setup.ini
-        package.write_setup_ini(args, packages)
-
-        # update packages listings
-        # XXX: perhaps we need a --[no]listing command line option to disable this from being run?
-        pkg2html.update_package_listings(args, packages)
-
-        return 0
-
-#
-#
-#
-
-if __name__ == "__main__":
-    htdocs_default = os.path.join(common_constants.HTDOCS, 'packages')
-    homedir_default = common_constants.HOMEDIR
-    orphanmaint_default = common_constants.ORPHANMAINT
-    pkglist_default = common_constants.PKGMAINT
-    relarea_default = common_constants.FTP
-    vault_default = common_constants.VAULT
-    logdir_default = '/sourceware/cygwin-staging/logs'
-
-    parser = argparse.ArgumentParser(description='Upset replacement')
-    parser.add_argument('--arch', action='store', required=True, choices=common_constants.ARCHES)
-    parser.add_argument('--email', action='store', dest='email', nargs='?', const=common_constants.EMAILS, help='email output to maintainer and ADDRS (default: ' + common_constants.EMAILS + ')', metavar='ADDRS')
-    parser.add_argument('--force', action='store_true', help="overwrite existing files")
-    parser.add_argument('--homedir', action='store', metavar='DIR', help="maintainer home directory (default: " + homedir_default + ")", default=homedir_default)
-    parser.add_argument('--htdocs', action='store', metavar='DIR', help="htdocs output directory (default: " + htdocs_default + ")", default=htdocs_default)
-    parser.add_argument('--inifile', '-u', action='store', help='output filename', required=True)
-    parser.add_argument('--logdir', action='store', metavar='DIR', help="log directory (default: '" + logdir_default + "')", default=logdir_default)
-    parser.add_argument('--orphanmaint', action='store', metavar='NAMES', help="orphan package maintainers (default: '" + orphanmaint_default + "')", default=orphanmaint_default)
-    parser.add_argument('--pkglist', action='store', metavar='FILE', help="package maintainer list (default: " + pkglist_default + ")", default=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')
-    parser.add_argument('--setup-version', action='store', metavar='VERSION', help='value for setup-version key')
-    parser.add_argument('-n', '--dry-run', action='store_true', dest='dryrun', help="don't do anything")
-    parser.add_argument('--vault', action='store', metavar='DIR', help="vault directory (default: " + vault_default + ")", default=vault_default, dest='vault')
-    parser.add_argument('-v', '--verbose', action='count', dest='verbose', help='verbose output')
-    (args) = parser.parse_args()
-
-    # set up logging to a file
-    try:
-        os.makedirs(args.logdir, exist_ok=True)
-    except FileExistsError:
-        pass
-    rfh = logging.handlers.RotatingFileHandler(os.path.join(args.logdir, 'calm.log'), backupCount=24)
-    rfh.doRollover()  # force a rotate on every run
-    rfh.setFormatter(logging.Formatter('%(asctime)s - %(levelname)-8s - %(message)s'))
-    rfh.setLevel(logging.INFO)
-    logging.getLogger().addHandler(rfh)
-
-    # setup logging to stdout, of WARNING messages or higher (INFO if verbose)
-    ch = logging.StreamHandler(sys.stdout)
-    ch.setFormatter(logging.Formatter(os.path.basename(sys.argv[0])+': %(message)s'))
-    if args.verbose:
-        ch.setLevel(logging.INFO)
-    else:
-        ch.setLevel(logging.WARNING)
-    logging.getLogger().addHandler(ch)
-
-    # change root logger level from the default of WARNING
-    logging.getLogger().setLevel(logging.INFO)
-
-    if args.email:
-        args.email = args.email.split(',')
-
-    sys.exit(main(args))
+#!/usr/bin/env bash
+base_dir=$(dirname "$0")
+exec python3 "$base_dir/calm.py" "$@"
diff --git a/calm.py b/calm.py
new file mode 100755
index 0000000..9638d9e
--- /dev/null
+++ b/calm.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2015 Jon Turney
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+#
+# calm - better than being upset
+#
+
+#
+# read packages from release area
+# for each maintainer
+# - read and validate any package uploads
+# - build a list of files to move and remove
+# - merge package sets
+# - remove from the package set files which are to be removed
+# - validate merged package set
+# - process remove list
+# - on failure
+# -- mail maintainer with errors
+# -- empty move list
+# -- discard merged package set
+# - on success
+# -- process move list
+# -- mail maintainer with movelist
+# -- continue with merged package set
+# write setup.ini file
+#
+
+import argparse
+import logging
+import os
+import sys
+
+from buffering_smtp_handler import mail_logs
+import common_constants
+import maintainers
+import package
+import pkg2html
+import uploads
+
+
+#
+#
+#
+
+def main(args):
+    details = '%s%s' % (args.arch, ',dry-run' if args.dryrun else '')
+
+    # send one email per run to leads
+    with mail_logs(args.email, toaddrs=args.email, subject='calm messages [%s]' % (details)) as leads_email:
+        if args.dryrun:
+            logging.warning("--dry-run in effect, nothing will really be done")
+
+        # build package list
+        packages = package.read_packages(args.rel_area, args.arch)
+
+        # validate the package set
+        if not package.validate_packages(args, packages):
+            logging.error("existing package set has errors, not processing uploads or writing setup.ini")
+            return 1
+
+        # read maintainer list
+        mlist = maintainers.Maintainer.read(args)
+
+        # make the list of all packages
+        all_packages = maintainers.Maintainer.all_packages(mlist)
+
+        # for each maintainer
+        for name in sorted(mlist.keys()):
+            m = mlist[name]
+
+            # also send a mail to each maintainer about their packages
+            with mail_logs(args.email, toaddrs=m.email, subject='calm messages for %s [%s]' % (name, details)) as maint_email:
+
+                (error, mpackages, to_relarea, to_vault, remove_always, remove_success) = uploads.scan(m, all_packages, args)
+
+                uploads.remove(args, remove_always)
+
+                # if there are no uploaded packages for this maintainer, we
+                # don't have anything to do
+                if not mpackages:
+                    logging.info("nothing to do for maintainer %s" % (name))
+                    continue
+
+                if not error:
+                    # merge package set
+                    merged_packages = package.merge(packages, mpackages)
+
+                    # remove file which are to be removed
+                    #
+                    # XXX: this doesn't properly account for removing setup.hint
+                    # files
+                    for p in to_vault:
+                        for f in to_vault[p]:
+                            package.delete(merged_packages, p, f)
+
+                    # validate the package set
+                    if package.validate_packages(args, merged_packages):
+                        # process the move list
+                        uploads.move_to_vault(args, to_vault)
+                        uploads.remove(args, remove_success)
+                        uploads.move_to_relarea(m, args, to_relarea)
+                        # use merged package list
+                        packages = merged_packages
+                        logging.info("added %d packages from maintainer %s" % (len(mpackages), name))
+                    else:
+                        # otherwise we discard move list and merged_packages
+                        logging.error("error while merging uploaded packages for %s" % (name))
+
+        # write setup.ini
+        package.write_setup_ini(args, packages)
+
+        # update packages listings
+        # XXX: perhaps we need a --[no]listing command line option to disable this from being run?
+        pkg2html.update_package_listings(args, packages)
+
+        return 0
+
+#
+#
+#
+
+if __name__ == "__main__":
+    htdocs_default = os.path.join(common_constants.HTDOCS, 'packages')
+    homedir_default = common_constants.HOMEDIR
+    orphanmaint_default = common_constants.ORPHANMAINT
+    pkglist_default = common_constants.PKGMAINT
+    relarea_default = common_constants.FTP
+    vault_default = common_constants.VAULT
+    logdir_default = '/sourceware/cygwin-staging/logs'
+
+    parser = argparse.ArgumentParser(description='Upset replacement')
+    parser.add_argument('--arch', action='store', required=True, choices=common_constants.ARCHES)
+    parser.add_argument('--email', action='store', dest='email', nargs='?', const=common_constants.EMAILS, help='email output to maintainer and ADDRS (default: ' + common_constants.EMAILS + ')', metavar='ADDRS')
+    parser.add_argument('--force', action='store_true', help="overwrite existing files")
+    parser.add_argument('--homedir', action='store', metavar='DIR', help="maintainer home directory (default: " + homedir_default + ")", default=homedir_default)
+    parser.add_argument('--htdocs', action='store', metavar='DIR', help="htdocs output directory (default: " + htdocs_default + ")", default=htdocs_default)
+    parser.add_argument('--inifile', '-u', action='store', help='output filename', required=True)
+    parser.add_argument('--logdir', action='store', metavar='DIR', help="log directory (default: '" + logdir_default + "')", default=logdir_default)
+    parser.add_argument('--orphanmaint', action='store', metavar='NAMES', help="orphan package maintainers (default: '" + orphanmaint_default + "')", default=orphanmaint_default)
+    parser.add_argument('--pkglist', action='store', metavar='FILE', help="package maintainer list (default: " + pkglist_default + ")", default=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')
+    parser.add_argument('--setup-version', action='store', metavar='VERSION', help='value for setup-version key')
+    parser.add_argument('-n', '--dry-run', action='store_true', dest='dryrun', help="don't do anything")
+    parser.add_argument('--vault', action='store', metavar='DIR', help="vault directory (default: " + vault_default + ")", default=vault_default, dest='vault')
+    parser.add_argument('-v', '--verbose', action='count', dest='verbose', help='verbose output')
+    (args) = parser.parse_args()
+
+    # set up logging to a file
+    try:
+        os.makedirs(args.logdir, exist_ok=True)
+    except FileExistsError:
+        pass
+    rfh = logging.handlers.RotatingFileHandler(os.path.join(args.logdir, 'calm.log'), backupCount=24)
+    rfh.doRollover()  # force a rotate on every run
+    rfh.setFormatter(logging.Formatter('%(asctime)s - %(levelname)-8s - %(message)s'))
+    rfh.setLevel(logging.INFO)
+    logging.getLogger().addHandler(rfh)
+
+    # setup logging to stdout, of WARNING messages or higher (INFO if verbose)
+    ch = logging.StreamHandler(sys.stdout)
+    ch.setFormatter(logging.Formatter(os.path.basename(sys.argv[0])+': %(message)s'))
+    if args.verbose:
+        ch.setLevel(logging.INFO)
+    else:
+        ch.setLevel(logging.WARNING)
+    logging.getLogger().addHandler(ch)
+
+    # change root logger level from the default of WARNING
+    logging.getLogger().setLevel(logging.INFO)
+
+    if args.email:
+        args.email = args.email.split(',')
+
+    sys.exit(main(args))
diff --git a/package.py b/package.py
index 1213a67..ef2832c 100755
--- a/package.py
+++ b/package.py
@@ -203,7 +203,7 @@ def read_package(packages, basedir, dirpath, files, strict=False):
         if p in past_mistakes.self_source:
             packages[p].hints['self-source'] = ''
 
-    elif (len(files) > 0) and (relpath.count(os.path.sep) > 1):
+    elif (len(files) > 0) and (relpath.count(os.path.sep) > 0):
         logging.warning("no setup.hint in %s but files: %s" % (dirpath, ', '.join(files)))
 
     if strict:
diff --git a/testdata/homes/Blooey McFooey/x86/release/not-ready/-not-ready-0.9-1.tar.bz2 b/testdata/homes/Blooey McFooey/x86/release/not-ready/-not-ready-0.9-1.tar.bz2
new file mode 100644
index 0000000..e69de29
diff --git a/testdata/homes/Blooey McFooey/x86/release/testpackage/-testpackage-0.1-1.tar.bz2 b/testdata/homes/Blooey McFooey/x86/release/testpackage/-testpackage-0.1-1.tar.bz2
new file mode 100644
index 0000000..e69de29
diff --git a/testdata/homes/Blooey McFooey/x86/release/testpackage/testpackage-1.0-1-src.tar.bz2 b/testdata/homes/Blooey McFooey/x86/release/testpackage/testpackage-1.0-1-src.tar.bz2
new file mode 100644
index 0000000..a1145fb
Binary files /dev/null and b/testdata/homes/Blooey McFooey/x86/release/testpackage/testpackage-1.0-1-src.tar.bz2 differ
diff --git a/testdata/homes/Blooey McFooey/x86/release/testpackage/testpackage-subpackage/setup.hint b/testdata/homes/Blooey McFooey/x86/release/testpackage/testpackage-subpackage/setup.hint
index 74f5a24..90f7384 100644
--- a/testdata/homes/Blooey McFooey/x86/release/testpackage/testpackage-subpackage/setup.hint	
+++ b/testdata/homes/Blooey McFooey/x86/release/testpackage/testpackage-subpackage/setup.hint	
@@ -1,4 +1,4 @@
 sdesc: "A test subpackage"
 ldesc: "A test subpackage"
 category: Devel
-
+external-source: testpackage
diff --git a/testdata/htdocs.expected/x86/openssh/.htaccess b/testdata/htdocs.expected/x86/openssh/.htaccess
new file mode 100644
index 0000000..3196d64
--- /dev/null
+++ b/testdata/htdocs.expected/x86/openssh/.htaccess
@@ -0,0 +1,3 @@
+Options Indexes
+IndexOptions -FancyIndexing
+AddType text/html 1 2 3 4 5 6 7 8 9
diff --git a/testdata/htdocs.expected/x86/openssh/openssh-7.2p2-1 b/testdata/htdocs.expected/x86/openssh/openssh-7.2p2-1
new file mode 100644
index 0000000..cb2b272
--- /dev/null
+++ b/testdata/htdocs.expected/x86/openssh/openssh-7.2p2-1
@@ -0,0 +1,7 @@
+<html>
+<h1>openssh: The OpenSSH server and client programs (installed binaries and support files)</h1>
+<tt><pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre></tt>
+</html>
diff --git a/testdata/htdocs.expected/x86/openssh/openssh-7.2p2-1-src b/testdata/htdocs.expected/x86/openssh/openssh-7.2p2-1-src
new file mode 100644
index 0000000..e85cbee
--- /dev/null
+++ b/testdata/htdocs.expected/x86/openssh/openssh-7.2p2-1-src
@@ -0,0 +1,7 @@
+<html>
+<h1>openssh: The OpenSSH server and client programs (source code)</h1>
+<tt><pre>
+    2015-10-11 14:45          26 test/test.1
+    2015-10-11 14:45          31 test/test.2
+</pre></tt>
+</html>
diff --git a/testdata/htdocs.expected/x86/packages.inc b/testdata/htdocs.expected/x86/packages.inc
index b9b99d6..484f797 100755
--- a/testdata/htdocs.expected/x86/packages.inc
+++ b/testdata/htdocs.expected/x86/packages.inc
@@ -14,10 +14,8 @@
 <tr><td><a href="x86/keychain">keychain</a></td><td>Key manager for OpenSSH</td></tr>
 <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/libtextcat-devel">libtextcat-devel</a></td><td>Helper, header and library for libtextcat</td></tr>
-<tr><td><a href="x86/libtextcat0">libtextcat0</a></td><td>Text Classification Library dll</td></tr>
 <tr><td><a href="x86/mDNSResponder">mDNSResponder</a></td><td>Bonjour Zeroconf implementation</td></tr>
-<tr><td><a href="x86/proj-debuginfo">proj-debuginfo</a></td><td>Debug info for proj</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/rpm-doc">rpm-doc</a></td><td>Obsolete package for RPM package management system manual pages</td></tr>
 </table>
 </div>
diff --git a/testdata/htdocs.expected/x86/rpm-doc/rpm-doc-4.1-2-src b/testdata/htdocs.expected/x86/rpm-doc/rpm-doc-4.1-2-src
new file mode 100644
index 0000000..3566e39
--- /dev/null
+++ b/testdata/htdocs.expected/x86/rpm-doc/rpm-doc-4.1-2-src
@@ -0,0 +1,5 @@
+<html>
+<h1>rpm-doc: Obsolete package for RPM package management system manual pages (source code)</h1>
+<tt><pre>
+</pre></tt>
+</html>
diff --git a/testdata/inifile/setup.ini.expected b/testdata/inifile/setup.ini.expected
index 0cdb520..6140c5d 100644
--- a/testdata/inifile/setup.ini.expected
+++ b/testdata/inifile/setup.ini.expected
@@ -170,6 +170,18 @@
  'a number of popular Windows programs), then you can download it at\n'
  'http://support.apple.com/kb/DL999"\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'
+ '\texecuting commands on a remote machine.  It can replace rlogin and rsh,\n'
+ '\tproviding encrypted communication between two machines."\n'
+ 'category: Net\n'
+ 'version: 7.2p2-1\n'
+ 'install: x86/release/openssh/openssh-7.2p2-1.tar.xz 228 '
+ 'e675b0ac4bc2c3e1c4971bc56d77b0cd53a9bdf5632873a235d7582e29dfd3e8a7bb04b28f6cdee3e6b3d14c25ed39392538e3f628a9bfda6c905646ebc3c225\n'
+ 'source: x86/release/openssh/openssh-7.2p2-1-src.tar.xz 228 '
+ 'e675b0ac4bc2c3e1c4971bc56d77b0cd53a9bdf5632873a235d7582e29dfd3e8a7bb04b28f6cdee3e6b3d14c25ed39392538e3f628a9bfda6c905646ebc3c225\n'
+ '\n'
  '@ rpm-doc\n'
  'sdesc: "Obsolete package for RPM package management system manual pages"\n'
  'category: _obsolete\n'
@@ -179,4 +191,6 @@
  '[prev]\n'
  'version: 4.1-2\n'
  'install: x86/release/rpm-doc/rpm-doc-4.1-2.tar.bz2 50941 '
- '7cc9db802364252e3206ce9f75c8ca53813d8308a22a425b50ef695dd8e51568740b06739d3aa3399a83fb3d3e1345ab7e2ad03a1e9d47c02dded3363bf4f493\n',)
+ '7cc9db802364252e3206ce9f75c8ca53813d8308a22a425b50ef695dd8e51568740b06739d3aa3399a83fb3d3e1345ab7e2ad03a1e9d47c02dded3363bf4f493\n'
+ 'source: x86/release/rpm-doc/rpm-doc-4.1-2-src.tar.bz2 42 '
+ '28c70b843fe01d90a3eeab4a3617551d236cd0b7d69668d1b1b6c8b14a9fd050e4039c192894c93bdf31575771c58c1fea2a41c24c8da22d10080d8b032b6369\n',)
diff --git a/testdata/pkglist/cygwin-pkg-maint b/testdata/pkglist/cygwin-pkg-maint
index fa639e0..d2375af 100644
--- a/testdata/pkglist/cygwin-pkg-maint
+++ b/testdata/pkglist/cygwin-pkg-maint
@@ -2168,6 +2168,7 @@ telepathy-mission-control                    Yaakov Selkowitz
 telepathy-qt                                 Yaakov Selkowitz
 terminus-fonts                               Yaakov Selkowitz
 tesseract-ocr                                Marco Atzeri
+testpackage                                  Blooey McFooey
 tetzle                                       Yaakov Selkowitz
 texi2html                                    Dr. Volker Zell
 texinfo                                      Ken Brown
diff --git a/testdata/pkglist/expected b/testdata/pkglist/expected
index 199533c..4e8db92 100644
--- a/testdata/pkglist/expected
+++ b/testdata/pkglist/expected
@@ -3,7 +3,7 @@
  'Adam Dinwoodie': maintainers.Maintainer('Adam Dinwoodie', [], ['git']),
  'Alexey Sokolov': maintainers.Maintainer('Alexey Sokolov', [], ['znc']),
  'Andrew Schulman': maintainers.Maintainer('Andrew Schulman', [], ['atool', 'autossh', 'bc', 'discus', 'fish', 'lftp', 'libargp', 'nosleep', 'orpie', 'pinfo', 'ploticus', 'ploticus-doc', 'screen', 'sitecopy', 'sng', 'socat', 'stow', 'stunnel', 'time', 'unison2.27', 'unison2.32', 'unison2.40', 'unison2.45', 'unison2.48']),
- 'Blooey McFooey': maintainers.Maintainer('Blooey McFooey', [], []),
+ 'Blooey McFooey': maintainers.Maintainer('Blooey McFooey', [], ['testpackage']),
  'Bob Heckel': maintainers.Maintainer('Bob Heckel', [], ['libgc', 'w3m']),
  'Chris J. Breisch': maintainers.Maintainer('Chris J. Breisch', [], ['man-db']),
  'Chris LeBlanc': maintainers.Maintainer('Chris LeBlanc', [], ['python-h5py', 'python3-h5py']),
diff --git a/testdata/process_arch/homedir.expected b/testdata/process_arch/homedir.expected
new file mode 100644
index 0000000..39fb742
--- /dev/null
+++ b/testdata/process_arch/homedir.expected
@@ -0,0 +1,14 @@
+{'.': [],
+ 'Blooey McFooey': [],
+ 'Blooey McFooey/x86': [],
+ 'Blooey McFooey/x86/release': [],
+ 'Blooey McFooey/x86/release/after-ready': ['after-ready-1.0-1.tar.bz2', 'setup.hint'],
+ 'Blooey McFooey/x86/release/not-on-maintainer-list': ['not-on-maintainer-list-1.0-1.tar.bz2', 'setup.hint'],
+ 'Blooey McFooey/x86/release/not-on-package-list': ['not-on-package-list-1.0-1.tar.bz2', 'setup.hint'],
+ 'Blooey McFooey/x86/release/not-ready': ['-not-ready-0.9-1.tar.bz2', 'not-ready-1.0-1.tar.bz2', 'setup.hint'],
+ 'Blooey McFooey/x86/release/testpackage': [],
+ 'Blooey McFooey/x86/release/testpackage/testpackage-subpackage': [],
+ 'Blooey McFooey/x86/release/testpackage2': ['setup.hint', 'testpackage2-1.0-1.tar.bz2'],
+ 'Blooey McFooey/x86/release/testpackage2/testpackage2-subpackage': ['setup.hint',
+                                                                     'testpackage2-subpackage-1.0-1.tar.bz2'],
+ 'Jon Turney': ['!email']}
diff --git a/testdata/process_arch/htdocs.expected b/testdata/process_arch/htdocs.expected
new file mode 100644
index 0000000..174081f
--- /dev/null
+++ b/testdata/process_arch/htdocs.expected
@@ -0,0 +1,24 @@
+{'.': [],
+ 'x86': ['.htaccess', 'packages.inc'],
+ 'x86/arc': ['.htaccess', 'arc-4.32.7-10', 'arc-4.32.7-10-src'],
+ 'x86/base-cygwin': ['.htaccess', 'base-cygwin-3.6-1', 'base-cygwin-3.8-1'],
+ 'x86/cygwin': ['.htaccess',
+                'cygwin-2.2.0-1',
+                'cygwin-2.2.0-1-src',
+                'cygwin-2.2.1-1',
+                'cygwin-2.2.1-1-src',
+                'cygwin-2.3.0-0.3',
+                'cygwin-2.3.0-0.3-src'],
+ 'x86/cygwin-debuginfo': ['.htaccess',
+                          'cygwin-debuginfo-2.2.0-1',
+                          'cygwin-debuginfo-2.2.1-1',
+                          'cygwin-debuginfo-2.3.0-0.3'],
+ 'x86/cygwin-devel': ['.htaccess', 'cygwin-devel-2.2.0-1', 'cygwin-devel-2.2.1-1', 'cygwin-devel-2.3.0-0.3'],
+ 'x86/keychain': ['.htaccess', 'keychain-3.1.5-1', 'keychain-3.1.5-1-src', 'keychain-3.2.0-1', 'keychain-3.2.0-1-src'],
+ '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/openssh': ['.htaccess', 'openssh-7.2p2-1', 'openssh-7.2p2-1-src'],
+ 'x86/rpm-doc': ['.htaccess', 'rpm-doc-4.1-2', 'rpm-doc-4.1-2-src', 'rpm-doc-999-1'],
+ 'x86/testpackage': ['.htaccess', 'testpackage-1.0-1', 'testpackage-1.0-1-src'],
+ 'x86/testpackage-subpackage': ['.htaccess', 'testpackage-subpackage-1.0-1']}
diff --git a/testdata/process_arch/rel_area.expected b/testdata/process_arch/rel_area.expected
new file mode 100644
index 0000000..1770b4b
--- /dev/null
+++ b/testdata/process_arch/rel_area.expected
@@ -0,0 +1,57 @@
+{'.': ['setup.ini'],
+ 'x86': ['sha512.sum'],
+ 'x86/release': ['.gitignore', 'sha512.sum'],
+ 'x86/release/arc': ['arc-4.32.7-10-src.tar.bz2', 'arc-4.32.7-10.tar.bz2', 'setup.hint', 'sha512.sum'],
+ 'x86/release/base-cygwin': ['base-cygwin-3.6-1.tar.xz', 'base-cygwin-3.8-1.tar.xz', 'setup.hint', 'sha512.sum'],
+ 'x86/release/cygwin': ['cygwin-2.2.0-1-src.tar.xz',
+                        'cygwin-2.2.0-1.tar.xz',
+                        'cygwin-2.2.1-1-src.tar.xz',
+                        'cygwin-2.2.1-1.tar.xz',
+                        'cygwin-2.3.0-0.3-src.tar.xz',
+                        'cygwin-2.3.0-0.3.tar.xz',
+                        'setup.hint',
+                        'sha512.sum'],
+ 'x86/release/cygwin/cygwin-debuginfo': ['cygwin-debuginfo-2.2.0-1.tar.xz',
+                                         'cygwin-debuginfo-2.2.1-1.tar.xz',
+                                         'cygwin-debuginfo-2.3.0-0.3.tar.xz',
+                                         'setup.hint',
+                                         'sha512.sum'],
+ 'x86/release/cygwin/cygwin-devel': ['cygwin-devel-2.2.0-1.tar.xz',
+                                     'cygwin-devel-2.2.1-1.tar.xz',
+                                     'cygwin-devel-2.3.0-0.3.tar.xz',
+                                     'setup.hint',
+                                     'sha512.sum'],
+ 'x86/release/invalid': ['setup.hint', 'sha512.sum'],
+ 'x86/release/keychain': ['keychain-3.1.5-1-src.tar.xz',
+                          'keychain-3.1.5-1.tar.xz',
+                          'keychain-3.2.0-1-src.tar.xz',
+                          'keychain-3.2.0-1.tar.xz',
+                          'setup.hint',
+                          'sha512.sum'],
+ 'x86/release/libspiro': ['setup.hint', 'sha512.sum'],
+ 'x86/release/libspiro/libspiro-devel': ['setup.hint', 'sha512.sum'],
+ 'x86/release/libspiro/libspiro0': ['setup.hint', 'sha512.sum'],
+ 'x86/release/libtextcat': ['libtextcat-2.2-2-src.tar.bz2', 'libtextcat-2.2-2.tar.bz2', 'setup.hint', 'sha512.sum'],
+ 'x86/release/libtextcat/libtextcat-devel': ['libtextcat-devel-2.2-2.tar.bz2', 'setup.hint', 'sha512.sum'],
+ 'x86/release/libtextcat/libtextcat0': ['libtextcat0-2.2-2.tar.bz2', 'setup.hint', 'sha512.sum'],
+ 'x86/release/mDNSResponder': ['mDNSResponder-379.32.1-1-src.tar.bz2',
+                               'mDNSResponder-379.32.1-1.tar.bz2',
+                               'setup.hint',
+                               'sha512.sum'],
+ 'x86/release/mDNSResponder/libdns_sd-devel': ['libdns_sd-devel-379.32.1-1.tar.bz2', 'setup.hint', 'sha512.sum'],
+ 'x86/release/mDNSResponder/libdns_sd1': ['libdns_sd1-379.32.1-1.tar.bz2', 'setup.hint', 'sha512.sum'],
+ 'x86/release/mingw64-i686-binutils': ['setup.hint', 'sha512.sum'],
+ 'x86/release/mingw64-i686-binutils/mingw64-i686-binutils-debuginfo': ['setup.hint', 'sha512.sum'],
+ 'x86/release/naim': ['setup.hint'],
+ 'x86/release/openssh': ['openssh-7.2p2-1-src.tar.xz', 'openssh-7.2p2-1.tar.xz', 'setup.hint', 'sha512.sum'],
+ 'x86/release/proj': ['setup.hint', 'sha512.sum'],
+ 'x86/release/proj/libproj-devel': ['setup.hint', 'sha512.sum'],
+ 'x86/release/proj/libproj1': ['setup.hint', 'sha512.sum'],
+ 'x86/release/rpm-doc': ['rpm-doc-4.1-2-src.tar.bz2',
+                         'rpm-doc-4.1-2.tar.bz2',
+                         'rpm-doc-999-1.tar.bz2',
+                         'setup.hint',
+                         'sha512.sum'],
+ 'x86/release/splint': ['setup.hint', 'sha512.sum'],
+ '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']}
diff --git a/testdata/process_arch/vault.expected b/testdata/process_arch/vault.expected
new file mode 100644
index 0000000..a87938b
--- /dev/null
+++ b/testdata/process_arch/vault.expected
@@ -0,0 +1 @@
+{'.': [], 'x86': [], 'x86/release': [], 'x86/release/testpackage': ['testpackage-0.1-1.tar.bz2']}
diff --git a/testdata/uploads/move.expected b/testdata/uploads/move.expected
index 4353d4f..b0caaf3 100644
--- a/testdata/uploads/move.expected
+++ b/testdata/uploads/move.expected
@@ -1,3 +1,3 @@
-{'release/testpackage': ['setup.hint', 'testpackage-1.0-1.tar.bz2'],
+{'release/testpackage': ['setup.hint', 'testpackage-1.0-1-src.tar.bz2', 'testpackage-1.0-1.tar.bz2'],
  'release/testpackage/testpackage-subpackage': ['setup.hint', 'testpackage-subpackage-1.0-1.tar.bz2'],
  'release/testpackage2/testpackage2-subpackage': ['setup.hint', 'testpackage2-subpackage-1.0-1.tar.bz2']}
diff --git a/testdata/uploads/pkglist.expected b/testdata/uploads/pkglist.expected
index 3ab6f49..8caa9aa 100644
--- a/testdata/uploads/pkglist.expected
+++ b/testdata/uploads/pkglist.expected
@@ -1,9 +1,11 @@
-{'testpackage': Package('release/testpackage', {'testpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}, {'sdesc': '"A test package"',
+{'testpackage': Package('release/testpackage', {'testpackage-1.0-1-src.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False),
+ 'testpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}, {'sdesc': '"A test package"',
  'ldesc': '"A test package"',
  'category': 'Devel'}),
  'testpackage-subpackage': Package('release/testpackage/testpackage-subpackage', {'testpackage-subpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}, {'sdesc': '"A test subpackage"',
  'ldesc': '"A test subpackage"',
- 'category': 'Devel'}),
+ 'category': 'Devel',
+ 'external-source': 'testpackage'}),
  'testpackage2-subpackage': Package('release/testpackage2/testpackage2-subpackage', {'testpackage2-subpackage-1.0-1.tar.bz2': Tar('aff488008bee3486e25b539fe6ccd1397bd3c5c0ba2ee2cf34af279554baa195af7493ee51d6f8510735c9a2ea54436d776a71e768165716762aec286abbbf83', 195, False)}, {'sdesc': '"A test subpackage 2"',
  'ldesc': '"A test subpackage 2"',
  'category': 'Devel'})}
diff --git a/testdata/x86.hints/release/libtextcat/libtextcat-devel/expected b/testdata/x86.hints/release/libtextcat/libtextcat-devel/expected
index f79513f..f60d34d 100644
--- a/testdata/x86.hints/release/libtextcat/libtextcat-devel/expected
+++ b/testdata/x86.hints/release/libtextcat/libtextcat-devel/expected
@@ -1,4 +1,12 @@
 {'sdesc': '"Helper, header and library for libtextcat"',
+ 'ldesc': '"Libtextcat is a library with functions that implement the\n'
+          'classification technique described in Cavnar & Trenkle, "N-Gram-Based\n'
+          'Text Categorization". It was primarily developed for language\n'
+          'guessing, a task on which it is known to perform with near-perfect\n'
+          'accuracy.\n'
+          'BSD License.\n'
+          'http://software.wise-guys.nl/libtextcat/";',
  'category': 'Devel Text',
  'requires': 'libtextcat0 libtextcat',
- 'external-source': 'libtextcat'}
+ 'external-source': 'libtextcat',
+ 'parse-errors': ['embedded quote at line 7']}
diff --git a/testdata/x86.hints/release/libtextcat/libtextcat0/expected b/testdata/x86.hints/release/libtextcat/libtextcat0/expected
index 45bcd21..7f186d6 100644
--- a/testdata/x86.hints/release/libtextcat/libtextcat0/expected
+++ b/testdata/x86.hints/release/libtextcat/libtextcat0/expected
@@ -1,4 +1,12 @@
 {'sdesc': '"Text Classification Library dll"',
+ 'ldesc': '"Libtextcat is a library with functions that implement the\n'
+          'classification technique described in Cavnar & Trenkle, "N-Gram-Based\n'
+          'Text Categorization". It was primarily developed for language\n'
+          'guessing, a task on which it is known to perform with near-perfect\n'
+          'accuracy.\n'
+          'BSD License.\n'
+          'http://software.wise-guys.nl/libtextcat/";',
  'category': 'Text',
  'requires': 'cygwin',
- 'external-source': 'libtextcat'}
+ 'external-source': 'libtextcat',
+ 'parse-errors': ['embedded quote at line 7']}
diff --git a/testdata/x86.hints/release/naim/expected b/testdata/x86.hints/release/naim/expected
index 8519f73..9f7710c 100644
--- a/testdata/x86.hints/release/naim/expected
+++ b/testdata/x86.hints/release/naim/expected
@@ -1,5 +1,5 @@
 {'category': 'Net',
- 'requires': 'libgcc1 libncursesw10',
+ 'requires': '',
  'sdesc': '"Console AIM, ICQ, IRC, and Lily client"',
  'ldesc': '" naim is a console client for AOL Instant Messenger (AIM),\n'
           'AOL I Seek You (ICQ), Internet Relay Chat (IRC), and The lily CMC."',
diff --git a/testdata/x86.hints/release/openssh/expected b/testdata/x86.hints/release/openssh/expected
new file mode 100644
index 0000000..8eeefad
--- /dev/null
+++ b/testdata/x86.hints/release/openssh/expected
@@ -0,0 +1,5 @@
+{'category': 'Net',
+ 'sdesc': '"The OpenSSH server and client programs"',
+ 'ldesc': '"OpenSSH is a program for logging into a remote machine and for\n'
+          '\texecuting commands on a remote machine.  It can replace rlogin and rsh,\n'
+          '\tproviding encrypted communication between two machines."'}
diff --git a/testdata/x86/release/libtextcat/libtextcat-devel/setup.hint b/testdata/x86/release/libtextcat/libtextcat-devel/setup.hint
index 5c07d50..f509126 100644
--- a/testdata/x86/release/libtextcat/libtextcat-devel/setup.hint
+++ b/testdata/x86/release/libtextcat/libtextcat-devel/setup.hint
@@ -1,4 +1,11 @@
 sdesc: "Helper, header and library for libtextcat"
+ldesc: "Libtextcat is a library with functions that implement the
+classification technique described in Cavnar & Trenkle, "N-Gram-Based
+Text Categorization". It was primarily developed for language
+guessing, a task on which it is known to perform with near-perfect
+accuracy.
+BSD License.
+http://software.wise-guys.nl/libtextcat/";
 category: Devel Text
 requires: libtextcat0 libtextcat
 external-source: libtextcat
diff --git a/testdata/x86/release/libtextcat/libtextcat0/setup.hint b/testdata/x86/release/libtextcat/libtextcat0/setup.hint
index d1475a7..f3ae673 100644
--- a/testdata/x86/release/libtextcat/libtextcat0/setup.hint
+++ b/testdata/x86/release/libtextcat/libtextcat0/setup.hint
@@ -1,4 +1,11 @@
 sdesc: "Text Classification Library dll"
+ldesc: "Libtextcat is a library with functions that implement the
+classification technique described in Cavnar & Trenkle, "N-Gram-Based
+Text Categorization". It was primarily developed for language
+guessing, a task on which it is known to perform with near-perfect
+accuracy.
+BSD License.
+http://software.wise-guys.nl/libtextcat/";
 category: Text
 requires: cygwin
 external-source: libtextcat
diff --git a/testdata/x86/release/naim/setup.hint b/testdata/x86/release/naim/setup.hint
index d24a6ce..7de6e7b 100644
--- a/testdata/x86/release/naim/setup.hint
+++ b/testdata/x86/release/naim/setup.hint
@@ -1,5 +1,5 @@
 category: Net
-requires: libgcc1 libncursesw10 
+requires:
 sdesc: "Console AIM, ICQ, IRC, and Lily client"
 ldesc: " naim is a console client for AOL Instant Messenger (AIM),
 AOL I Seek You (ICQ), Internet Relay Chat (IRC), and The lily CMC."
diff --git a/testdata/x86/release/openssh/openssh-7.2p2-1-src.tar.xz b/testdata/x86/release/openssh/openssh-7.2p2-1-src.tar.xz
new file mode 100644
index 0000000..0e6f1e8
Binary files /dev/null and b/testdata/x86/release/openssh/openssh-7.2p2-1-src.tar.xz differ
diff --git a/testdata/x86/release/openssh/openssh-7.2p2-1.tar.xz b/testdata/x86/release/openssh/openssh-7.2p2-1.tar.xz
new file mode 100644
index 0000000..0e6f1e8
Binary files /dev/null and b/testdata/x86/release/openssh/openssh-7.2p2-1.tar.xz differ
diff --git a/testdata/x86/release/openssh/setup.hint b/testdata/x86/release/openssh/setup.hint
new file mode 100644
index 0000000..83aa11a
--- /dev/null
+++ b/testdata/x86/release/openssh/setup.hint
@@ -0,0 +1,5 @@
+category: Net
+sdesc: "The OpenSSH server and client programs"
+ldesc: "OpenSSH is a program for logging into a remote machine and for
+	executing commands on a remote machine.  It can replace rlogin and rsh,
+	providing encrypted communication between two machines."
diff --git a/testdata/x86/release/proj/proj-debuginfo/proj-debuginfo-4.8.0-1.tar.xz b/testdata/x86/release/proj/proj-debuginfo/proj-debuginfo-4.8.0-1.tar.xz
deleted file mode 100644
index a1145fb..0000000
Binary files a/testdata/x86/release/proj/proj-debuginfo/proj-debuginfo-4.8.0-1.tar.xz and /dev/null differ
diff --git a/testdata/x86/release/proj/proj-debuginfo/setup.hint b/testdata/x86/release/proj/proj-debuginfo/setup.hint
deleted file mode 100644
index ac6fb1e..0000000
--- a/testdata/x86/release/proj/proj-debuginfo/setup.hint
+++ /dev/null
@@ -1,6 +0,0 @@
-category: Debug
-requires: cygwin-debuginfo
-external-source: proj
-sdesc: "Debug info for proj"
-ldesc: "This package contains files necessary for debugging the
-proj package with gdb."
diff --git a/testdata/x86/release/rpm-doc/rpm-doc-4.1-2-src.tar.bz2 b/testdata/x86/release/rpm-doc/rpm-doc-4.1-2-src.tar.bz2
new file mode 100644
index 0000000..cbf838c
Binary files /dev/null and b/testdata/x86/release/rpm-doc/rpm-doc-4.1-2-src.tar.bz2 differ
diff --git a/testdata/x86/release/testpackage/testpackage-0.1-1.tar.bz2 b/testdata/x86/release/testpackage/testpackage-0.1-1.tar.bz2
new file mode 100644
index 0000000..e69de29
diff --git a/tests.py b/tests.py
index 5b97297..6f0ff19 100755
--- a/tests.py
+++ b/tests.py
@@ -30,10 +30,13 @@ import logging
 import os
 import pprint
 import re
+import shutil
+import tempfile
 import types
 import unittest
 
 from version import SetupVersion
+import calm
 import hint
 import maintainers
 import package
@@ -42,7 +45,7 @@ import uploads
 
 
 #
-# helper function
+# helper functions
 #
 # write results to the file 'results'
 # read expected from the file 'expected'
@@ -71,6 +74,19 @@ def compare_with_expected_file(test, dirpath, results, basename=None):
 
 
 #
+# capture a directory tree as a dict 'tree', where each key is a directory path
+# and the value is a sorted list of filenames
+#
+
+def capture_dirtree(basedir):
+    tree = {}
+    for dirpath, dirnames, filenames in os.walk(basedir):
+        tree[os.path.relpath(dirpath, basedir)] = sorted(filenames)
+
+    return tree
+
+
+#
 #
 #
 
@@ -189,7 +205,7 @@ class TestMain(unittest.TestCase):
         self.assertEqual(error, False)
         compare_with_expected_file(self, 'testdata/uploads', to_relarea, 'move')
         self.assertCountEqual(remove_always, [f for (f, t) in ready_fns])
-        self.assertEqual(remove_success, [])
+        self.assertEqual(remove_success, ['testdata/homes/Blooey McFooey/x86/release/testpackage/-testpackage-0.1-1.tar.bz2'])
         compare_with_expected_file(self, 'testdata/uploads', packages, 'pkglist')
 
     def test_package_set(self):
@@ -207,10 +223,7 @@ class TestMain(unittest.TestCase):
 
         packages = package.read_packages(args.rel_area, args.arch)
         package.delete(packages, 'release/nonexistent', 'nosuchfile-1.0.0.tar.xz')
-        package.delete(packages, 'release/libtextcat/libtextcat-devel', 'libtextcat-devel-2.2-2.tar.bz2')
-        package.delete(packages, 'release/libtextcat/libtextcat0', 'libtextcat0-2.2-2.tar.bz2')
-        package.delete(packages, 'release/proj/proj-debuginfo', 'proj-debuginfo-4.8.0-1.tar.xz')
-        package.validate_packages(args, packages)
+        self.assertEqual(package.validate_packages(args, packages), True)
         package.write_setup_ini(args, packages)
         with open(args.inifile) as inifile:
             results = inifile.read()
@@ -218,6 +231,45 @@ class TestMain(unittest.TestCase):
             results = re.sub('setup-timestamp: .*', 'setup-timestamp: 1458221800', results, 1)
             compare_with_expected_file(self, 'testdata/inifile', (results,), 'setup.ini')
 
+        # XXX: delete a needed package, and check validate fails
+
+    def test_process_arch(self):
+        self.maxDiff = None
+
+        args = types.SimpleNamespace()
+
+        for d in ['rel_area', 'homedir', 'htdocs', 'vault']:
+            setattr(args, d, tempfile.mktemp())
+            logging.info('%s = %s', d, getattr(args, d))
+
+        setattr(args, 'arch', 'x86')
+        setattr(args, 'dryrun', False)
+        setattr(args, 'email', None)
+        setattr(args, 'force', False)
+        setattr(args, 'inifile', os.path.join(getattr(args, 'rel_area'), 'setup.ini'))
+        setattr(args, 'pkglist', 'testdata/pkglist/cygwin-pkg-maint')
+        setattr(args, 'release', 'trial')
+        setattr(args, 'setup_version', '3.1415')
+
+        shutil.copytree('testdata/x86', os.path.join(getattr(args, 'rel_area'), 'x86'))
+        shutil.copytree('testdata/homes', getattr(args, 'homedir'))
+
+        # set appropriate !readys
+        m_homedir = os.path.join(getattr(args, 'homedir'), 'Blooey McFooey')
+        ready_fns = [(os.path.join(m_homedir, 'x86', 'release', 'testpackage', '!ready'), ''),
+                     (os.path.join(m_homedir, 'x86', 'release', 'testpackage2', 'testpackage2-subpackage', '!ready'), ''),
+                     (os.path.join(m_homedir, 'x86', 'release', 'after-ready', '!ready'), '-t 198709011700')]
+        for (f, t) in ready_fns:
+            os.system('touch %s "%s"' % (t, f))
+
+        self.assertEqual(calm.main(args), 0)
+
+        for d in ['rel_area', 'homedir', 'htdocs', 'vault']:
+            with self.subTest(directory=d):
+                dirlist = capture_dirtree(getattr(args, d))
+                compare_with_expected_file(self, 'testdata/process_arch', dirlist, d)
+                shutil.rmtree(getattr(args, d))
+
 if __name__ == '__main__':
     # ensure sha512.sum files exist
     os.system("find testdata/x86 -type d -exec sh -c 'cd {} ; sha512sum * >sha512.sum 2>/dev/null' \;")
diff --git a/uploads.py b/uploads.py
index 33ae511..050c494 100644
--- a/uploads.py
+++ b/uploads.py
@@ -171,16 +171,17 @@ def remove(args, remove):
 #
 
 def move(args, movelist, fromdir, todir):
-    for p in movelist:
+    for p in sorted(movelist):
         logging.info("mkdir %s" % os.path.join(todir, p))
         if not args.dryrun:
             try:
                 os.makedirs(os.path.join(todir, p), exist_ok=True)
             except FileExistsError:
                 pass
-        for f in movelist[p]:
+        logging.warning("move from '%s' to '%s':" % (os.path.join(fromdir, p), os.path.join(todir, p)))
+        for f in sorted(movelist[p]):
             if os.path.exists(os.path.join(fromdir, p, f)):
-                logging.warning("move %s to %s" % (os.path.join(fromdir, p, f), os.path.join(todir, p, f)))
+                logging.warning("%s" % (f))
                 if not args.dryrun:
                     os.rename(os.path.join(fromdir, p, f), os.path.join(todir, p, f))
             else:


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]