[Reproducible-commits] [debbindiff] 03/06: Improve optional usage of external commands

Jérémy Bobbio lunar at moszumanska.debian.org
Thu Mar 19 20:56:48 UTC 2015


This is an automated email from the git hooks/post-receive script.

lunar pushed a commit to branch master
in repository debbindiff.

commit ce420f6f5e88f152a8281fa18d44d09b31adc6e5
Author: Jérémy Bobbio <lunar at debian.org>
Date:   Wed Mar 18 18:51:18 2015 +0100

    Improve optional usage of external commands
    
    The `tool_required` decorator now raises an exception when
    the command cannot be found. This enables more flexible handling.
    Associated Debian package is now suggested in the comment.
    
    The list of external tools is now available through the `--list-tools`
    command-line option. We also use this output to generate the Recommends field.
---
 README                              | 30 ++----------------
 debbindiff.py                       | 14 +++++++++
 debbindiff/comparators/bzip2.py     |  2 +-
 debbindiff/comparators/cpio.py      |  3 +-
 debbindiff/comparators/directory.py | 54 +++++++++++++++++++-------------
 debbindiff/comparators/elf.py       |  7 ++---
 debbindiff/comparators/fonts.py     |  2 +-
 debbindiff/comparators/gettext.py   |  2 +-
 debbindiff/comparators/gzip.py      |  4 +--
 debbindiff/comparators/haskell.py   |  2 +-
 debbindiff/comparators/pdf.py       |  4 +--
 debbindiff/comparators/png.py       |  2 +-
 debbindiff/comparators/rpm.py       |  2 +-
 debbindiff/comparators/squashfs.py  |  3 +-
 debbindiff/comparators/utils.py     | 61 +++++++++++++++++++++++++++++++++----
 debbindiff/comparators/xz.py        |  2 +-
 debbindiff/comparators/zip.py       |  2 +-
 debian/control                      | 18 +----------
 debian/rules                        |  4 +++
 19 files changed, 129 insertions(+), 89 deletions(-)

diff --git a/README b/README
index 3425664..2603ec0 100644
--- a/README
+++ b/README
@@ -23,34 +23,10 @@ This will compare `build1.changes` and `build2.changes` and create
 External dependencies
 ---------------------
 
-The various comparators rely on these external commands being available
-in the path:
+The various comparators rely on external commands being available. To
+get a list of them, please run:
 
-| command    | Debian package     |
-+------------|--------------------|
-| ar         | binutils-multiarch |
-| bzip2      | bzip2              |
-| cpio       | cpio               |
-| file       | file               |
-| getfacl    | acl                |
-| ghc        | ghc                |
-| gpg        | gnupg              |
-| gzip       | gzip               |
-| ls         | coreutils          |
-| lsattr     | e2fsprogs          |
-| msgunfmt   | gettext            |
-| objdump    | binutils-multiarch |
-| pdftk      | pdftk              |
-| pdftotext  | poppler-utils      |
-| readelf    | binutils-multiarch |
-| rpm2cpio   | rpm2cpio           |
-| showttf    | fontforge-extras   |
-| sng        | sng                |
-| unsquashfs | squashfs-tools     |
-| vim        | vim                |
-| xxd        | vim-common         |
-| xz         | xz-utils           |
-| zipinfo    | unzip              |
+    $ ./debbindiff.py --list-tools
 
 Authors
 -------
diff --git a/debbindiff.py b/debbindiff.py
index 76b6b64..f02ebbb 100755
--- a/debbindiff.py
+++ b/debbindiff.py
@@ -37,6 +37,8 @@ def create_parser():
                     'of Debian packages')
     parser.add_argument('--version', action='version',
                         version='debbindiff %s' % VERSION)
+    parser.add_argument('--list-tools', nargs=0, action=ListToolsAction,
+                        help='show external tools required and exit')
     parser.add_argument('--debug', dest='debug', action='store_true',
                         default=False, help='display debug messages')
     parser.add_argument('--html', metavar='output', dest='html_output',
@@ -52,6 +54,7 @@ def create_parser():
     parser.add_argument('file2', help='second file to compare')
     return parser
 
+
 @contextmanager
 def make_printer(path):
     if path == '-':
@@ -66,6 +69,17 @@ def make_printer(path):
         output.close()
 
 
+class ListToolsAction(argparse.Action):
+    def __call__(self, parser, namespace, values, option_string=None):
+        from debbindiff.comparators.utils import tool_required, RequiredToolNotFound
+        print("External tools required:")
+        print(', '.join(tool_required.all))
+        print()
+        print("Available in packages:")
+        print(', '.join(sorted(set([RequiredToolNotFound.PROVIDERS[k]["debian"] for k in tool_required.all]))))
+        sys.exit(0)
+
+
 def main():
     parser = create_parser()
     parsed_args = parser.parse_args(sys.argv[1:])
diff --git a/debbindiff/comparators/bzip2.py b/debbindiff/comparators/bzip2.py
index 7e04dcf..c86cb01 100644
--- a/debbindiff/comparators/bzip2.py
+++ b/debbindiff/comparators/bzip2.py
@@ -26,6 +26,7 @@ from debbindiff.difference import get_source
 
 
 @contextmanager
+ at tool_required('bzip2')
 def decompress_bzip2(path):
     with make_temp_directory() as temp_dir:
         if path.endswith('.bz2'):
@@ -40,7 +41,6 @@ def decompress_bzip2(path):
 
 
 @binary_fallback
- at tool_required('bzip2')
 def compare_bzip2_files(path1, path2, source=None):
     with decompress_bzip2(path1) as new_path1:
         with decompress_bzip2(path2) as new_path2:
diff --git a/debbindiff/comparators/cpio.py b/debbindiff/comparators/cpio.py
index 2f2d6e9..3561c48 100644
--- a/debbindiff/comparators/cpio.py
+++ b/debbindiff/comparators/cpio.py
@@ -25,6 +25,7 @@ from debbindiff.comparators.utils import binary_fallback, make_temp_directory, t
 from debbindiff.difference import Difference
 
 
+ at tool_required('cpio')
 def get_cpio_content(path, verbose=False):
     cmd = ['cpio', '--quiet', '-tF', path]
     if verbose:
@@ -32,6 +33,7 @@ def get_cpio_content(path, verbose=False):
     return subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=False)
 
 
+ at tool_required('cpio')
 def extract_cpio_archive(path, destdir):
     cmd = ['cpio', '--no-absolute-filenames', '--quiet', '-idF',
             os.path.abspath(path)]
@@ -44,7 +46,6 @@ def extract_cpio_archive(path, destdir):
 
 
 @binary_fallback
- at tool_required('cpio')
 def compare_cpio_files(path1, path2, source=None):
     differences = []
 
diff --git a/debbindiff/comparators/directory.py b/debbindiff/comparators/directory.py
index 2022069..fd7fe54 100644
--- a/debbindiff/comparators/directory.py
+++ b/debbindiff/comparators/directory.py
@@ -30,6 +30,7 @@ def ls(path):
     return subprocess.check_output(['ls', path], shell=False).decode('utf-8')
 
 
+ at tool_required('stat')
 def stat(path):
     output = subprocess.check_output(['stat', path], shell=False).decode('utf-8')
     output = re.sub(r'^\s*File:.*$', '', output, flags=re.MULTILINE)
@@ -37,6 +38,7 @@ def stat(path):
     return output
 
 
+ at tool_required('lsattr')
 def lsattr(path):
     try:
         output = subprocess.check_output(['lsattr', '-d', path], shell=False, stderr=subprocess.STDOUT).decode('utf-8')
@@ -47,34 +49,44 @@ def lsattr(path):
             return ''
 
 
+ at tool_required('getfacl')
 def getfacl(path):
     return subprocess.check_output(['getfacl', '-p', '-c', path], shell=False).decode('utf-8')
 
 
- at tool_required('stat')
- at tool_required('lsattr')
- at tool_required('getfacl')
 def compare_meta(path1, path2):
     logger.debug('compare_meta(%s, %s)' % (path1, path2))
     differences = []
-    stat1 = stat(path1)
-    stat2 = stat(path2)
-    if stat1 != stat2:
-        differences.append(Difference(
-            stat1.splitlines(1), stat2.splitlines(1),
-            path1, path2, source="stat"))
-    lsattr1 = lsattr(path1)
-    lsattr2 = lsattr(path2)
-    if lsattr1 != lsattr2:
-        differences.append(Difference(
-            lsattr1.splitlines(1), lsattr2.splitlines(1),
-            path1, path2, source="lattr"))
-    acl1 = getfacl(path1)
-    acl2 = getfacl(path2)
-    if acl1 != acl2:
-        differences.append(Difference(
-            acl1.splitlines(1), acl2.splitlines(1),
-            path1, path2, source="getfacl"))
+
+    try:
+        stat1 = stat(path1)
+        stat2 = stat(path2)
+        if stat1 != stat2:
+            differences.append(Difference(
+                stat1.splitlines(1), stat2.splitlines(1),
+                path1, path2, source="stat"))
+    except RequiredToolNotFound:
+        logger.warn("'stat' not found! Is PATH wrong?")
+
+    try:
+        lsattr1 = lsattr(path1)
+        lsattr2 = lsattr(path2)
+        if lsattr1 != lsattr2:
+            differences.append(Difference(
+                lsattr1.splitlines(1), lsattr2.splitlines(1),
+                path1, path2, source="lattr"))
+    except RequiredToolNotFound:
+        logger.info("Unable to find 'lsattr'.")
+
+    try:
+        acl1 = getfacl(path1)
+        acl2 = getfacl(path2)
+        if acl1 != acl2:
+            differences.append(Difference(
+                acl1.splitlines(1), acl2.splitlines(1),
+                path1, path2, source="getfacl"))
+    except RequiredToolNotFound:
+        logger.info("Unable to find 'getfacl'.")
     return differences
 
 
diff --git a/debbindiff/comparators/elf.py b/debbindiff/comparators/elf.py
index 496ddf7..6e7c8ac 100644
--- a/debbindiff/comparators/elf.py
+++ b/debbindiff/comparators/elf.py
@@ -24,6 +24,7 @@ from debbindiff.comparators.utils import binary_fallback, get_ar_content, tool_r
 from debbindiff.difference import Difference
 
 
+ at tool_required('readelf')
 def readelf_all(path):
     output = subprocess.check_output(
         ['readelf', '--all', path],
@@ -32,6 +33,7 @@ def readelf_all(path):
     return re.sub(re.escape(path), os.path.basename(path), output)
 
 
+ at tool_required('readelf')
 def readelf_debug_dump(path):
     output = subprocess.check_output(
         ['readelf', '--debug-dump', path],
@@ -40,6 +42,7 @@ def readelf_debug_dump(path):
     return re.sub(re.escape(path), os.path.basename(path), output)
 
 
+ at tool_required('objdump')
 def objdump_disassemble(path):
     output = subprocess.check_output(
         ['objdump', '--disassemble', path],
@@ -74,15 +77,11 @@ def _compare_elf_data(path1, path2, source=None):
 
 
 @binary_fallback
- at tool_required('readelf')
- at tool_required('objdump')
 def compare_elf_files(path1, path2, source=None):
     return _compare_elf_data(path1, path2, source=None)
 
 
 @binary_fallback
- at tool_required('readelf')
- at tool_required('objdump')
 def compare_static_lib_files(path1, path2, source=None):
     differences = []
     # look up differences in metadata
diff --git a/debbindiff/comparators/fonts.py b/debbindiff/comparators/fonts.py
index 90b318e..eac2d2b 100644
--- a/debbindiff/comparators/fonts.py
+++ b/debbindiff/comparators/fonts.py
@@ -22,12 +22,12 @@ from debbindiff.comparators.utils import binary_fallback, tool_required
 from debbindiff.difference import Difference
 
 
+ at tool_required('showttf')
 def show_ttf(path):
     return subprocess.check_output(['showttf', path], shell=False)
 
 
 @binary_fallback
- at tool_required('showttf')
 def compare_ttf_files(path1, path2, source=None):
     ttf1 = show_ttf(path1)
     ttf2 = show_ttf(path2)
diff --git a/debbindiff/comparators/gettext.py b/debbindiff/comparators/gettext.py
index 1d6a419..b152555 100644
--- a/debbindiff/comparators/gettext.py
+++ b/debbindiff/comparators/gettext.py
@@ -22,12 +22,12 @@ from debbindiff.comparators.utils import binary_fallback, tool_required
 from debbindiff.difference import Difference
 
 
+ at tool_required('msgunfmt')
 def msgunfmt(path):
     return subprocess.check_output(['msgunfmt', path], shell=False)
 
 
 @binary_fallback
- at tool_required('msgunfmt')
 def compare_mo_files(path1, path2, source=None):
     mo1 = msgunfmt(path1)
     mo2 = msgunfmt(path2)
diff --git a/debbindiff/comparators/gzip.py b/debbindiff/comparators/gzip.py
index 413c538..c0dbf4a 100644
--- a/debbindiff/comparators/gzip.py
+++ b/debbindiff/comparators/gzip.py
@@ -26,6 +26,7 @@ from debbindiff.difference import Difference, get_source
 
 
 @contextmanager
+ at tool_required('gzip')
 def decompress_gzip(path):
     with make_temp_directory() as temp_dir:
         if path.endswith('.gz'):
@@ -39,13 +40,12 @@ def decompress_gzip(path):
             yield temp_path
 
 
+ at tool_required('file')
 def get_gzip_metadata(path):
     return subprocess.check_output(['file', '--brief', path])
 
 
 @binary_fallback
- at tool_required('gzip')
- at tool_required('file')
 def compare_gzip_files(path1, path2, source=None):
     differences = []
     # check metadata
diff --git a/debbindiff/comparators/haskell.py b/debbindiff/comparators/haskell.py
index 3e45013..a0db51d 100644
--- a/debbindiff/comparators/haskell.py
+++ b/debbindiff/comparators/haskell.py
@@ -22,12 +22,12 @@ from debbindiff.comparators.utils import binary_fallback, tool_required
 from debbindiff.difference import Difference
 
 
+ at tool_required('ghc')
 def show_iface(path):
     return subprocess.check_output(['ghc', '--show-iface', path], shell=False)
 
 
 @binary_fallback
- at tool_required('ghc')
 def compare_hi_files(path1, path2, source=None):
     iface1 = show_iface(path1)
     iface2 = show_iface(path2)
diff --git a/debbindiff/comparators/pdf.py b/debbindiff/comparators/pdf.py
index eddd3cc..5e18fb0 100644
--- a/debbindiff/comparators/pdf.py
+++ b/debbindiff/comparators/pdf.py
@@ -22,6 +22,7 @@ from debbindiff.comparators.utils import binary_fallback, tool_required
 from debbindiff.difference import Difference, get_source
 
 
+ at tool_required('pdftk')
 def uncompress(path):
     output = subprocess.check_output(
         ['pdftk', path, 'output', '-', 'uncompress'],
@@ -29,6 +30,7 @@ def uncompress(path):
     return output.decode('latin-1').encode('ascii', 'backslashreplace')
 
 
+ at tool_required('pdftotext')
 def pdftotext(path):
     return subprocess.check_output(
         ['pdftotext', path, '-'],
@@ -36,8 +38,6 @@ def pdftotext(path):
 
 
 @binary_fallback
- at tool_required('pdftk')
- at tool_required('pdftotext')
 def compare_pdf_files(path1, path2, source=None):
     differences = []
     src = get_source(path1, path2) or 'FILE'
diff --git a/debbindiff/comparators/png.py b/debbindiff/comparators/png.py
index fabfa88..bfeb39b 100644
--- a/debbindiff/comparators/png.py
+++ b/debbindiff/comparators/png.py
@@ -22,6 +22,7 @@ from debbindiff.comparators.utils import binary_fallback, tool_required
 from debbindiff.difference import Difference
 
 
+ at tool_required('sng')
 def sng(path):
     with open(path) as f:
         p = subprocess.Popen(['sng'], shell=False, close_fds=True,
@@ -33,7 +34,6 @@ def sng(path):
         return out
 
 @binary_fallback
- at tool_required('sng')
 def compare_png_files(path1, path2, source=None):
     sng1 = sng(path1)
     sng2 = sng(path2)
diff --git a/debbindiff/comparators/rpm.py b/debbindiff/comparators/rpm.py
index 9f0bf6c..9391ee4 100644
--- a/debbindiff/comparators/rpm.py
+++ b/debbindiff/comparators/rpm.py
@@ -48,6 +48,7 @@ def get_rpm_header(path, ts):
 
 
 @contextmanager
+ at tool_required('rpm2cpio')
 def extract_rpm_payload(path):
     cmd = ['rpm2cpio', path]
     with make_temp_directory() as temp_dir:
@@ -63,7 +64,6 @@ def extract_rpm_payload(path):
 
 
 @binary_fallback
- at tool_required('rpm2cpio')
 def compare_rpm_files(path1, path2, source=None):
     try:
         import rpm
diff --git a/debbindiff/comparators/squashfs.py b/debbindiff/comparators/squashfs.py
index 0cfc926..7d457ea 100644
--- a/debbindiff/comparators/squashfs.py
+++ b/debbindiff/comparators/squashfs.py
@@ -25,6 +25,7 @@ from debbindiff.comparators.utils import binary_fallback, make_temp_directory, t
 from debbindiff.difference import Difference
 
 
+ at tool_required('unsquashfs')
 def get_squashfs_content(path, verbose=True):
     cmd = ['unsquashfs', '-d', '', '-ls', path]
     content = ''
@@ -37,6 +38,7 @@ def get_squashfs_content(path, verbose=True):
     return content + subprocess.check_output(cmd, shell=False)
 
 
+ at tool_required('unsquashfs')
 def extract_squashfs(path, destdir):
     cmd = ['unsquashfs', '-n', '-f', '-d', destdir, path]
     logger.debug("extracting %s into %s", path, destdir)
@@ -48,7 +50,6 @@ def extract_squashfs(path, destdir):
 
 
 @binary_fallback
- at tool_required('unsquashfs')
 def compare_squashfs_files(path1, path2, source=None):
     differences = []
 
diff --git a/debbindiff/comparators/utils.py b/debbindiff/comparators/utils.py
index 1d735cc..8b2f63c 100644
--- a/debbindiff/comparators/utils.py
+++ b/debbindiff/comparators/utils.py
@@ -72,18 +72,67 @@ def binary_fallback(original_function):
             difference.comment = \
                 "Command `%s` exited with %d. Output:\n%s" \
                 % (cmd, e.returncode, output)
+        except RequiredToolNotFound as e:
+            difference = compare_binary_files(path1, path2, source=source)[0]
+            difference.comment = \
+                "'%s' not available in path. Falling back to binary comparison." % e.command
+            package = e.get_package()
+            if package:
+                difference.comment += "\nInstall '%s' to get a better output." % package
         return [difference]
     return with_fallback
 
 
+class RequiredToolNotFound(Exception):
+    PROVIDERS = { 'ar':         { 'debian': 'binutils-multiarch' }
+                , 'bzip2':      { 'debian': 'bzip2' }
+                , 'cpio':       { 'debian': 'cpio' }
+                , 'file':       { 'debian': 'file' }
+                , 'getfacl':    { 'debian': 'acl' }
+                , 'ghc':        { 'debian': 'ghc' }
+                , 'gpg':        { 'debian': 'gnupg' }
+                , 'gzip':       { 'debian': 'gzip' }
+                , 'ls':         { 'debian': 'coreutils' }
+                , 'lsattr':     { 'debian': 'e2fsprogs' }
+                , 'msgunfmt':   { 'debian': 'gettext' }
+                , 'objdump':    { 'debian': 'binutils-multiarch' }
+                , 'pdftk':      { 'debian': 'pdftk' }
+                , 'pdftotext':  { 'debian': 'poppler-utils' }
+                , 'readelf':    { 'debian': 'binutils-multiarch' }
+                , 'rpm2cpio':   { 'debian': 'rpm2cpio' }
+                , 'showttf':    { 'debian': 'fontforge-extras' }
+                , 'sng':        { 'debian': 'sng' }
+                , 'stat':       { 'debian': 'coreutils' }
+                , 'unsquashfs': { 'debian': 'squashfs-tools' }
+                , 'vim':        { 'debian': 'vim' }
+                , 'xxd':        { 'debian': 'vim-common' }
+                , 'xz':         { 'debian': 'xz-utils' }
+                , 'zipinfo':    { 'debian': 'unzip' }
+              }
+
+    def __init__(self, command):
+        self.command = command
+
+    def get_package(self):
+        providers = RequiredToolNotFound.PROVIDERS.get(self.command, None)
+        if not providers:
+            return None
+        # XXX: hardcode Debian for now
+        return providers['debian']
+
+
 # decorator that checks if the specified tool is installed
-def tool_required(filename):
+def tool_required(command):
+    if not hasattr(tool_required, 'all'):
+        tool_required.all = set()
+    tool_required.all.add(command)
     def wrapper(original_function):
-        def tool_check(*args):
-            if not find_executable(filename):
-                logger.info("Tool '%s' not found." % filename)
-                return []
-            return original_function(*args)
+        if find_executable(command):
+            def tool_check(*args):
+                return original_function(*args)
+        else:
+            def tool_check(*args):
+                raise RequiredToolNotFound(command)
         return tool_check
     return wrapper
 
diff --git a/debbindiff/comparators/xz.py b/debbindiff/comparators/xz.py
index f2cc0ef..09bc10a 100644
--- a/debbindiff/comparators/xz.py
+++ b/debbindiff/comparators/xz.py
@@ -26,6 +26,7 @@ from debbindiff.difference import get_source
 
 
 @contextmanager
+ at tool_required('xz')
 def decompress_xz(path):
     with make_temp_directory() as temp_dir:
         if path.endswith('.xz'):
@@ -40,7 +41,6 @@ def decompress_xz(path):
 
 
 @binary_fallback
- at tool_required('xz')
 def compare_xz_files(path1, path2, source=None):
     with decompress_xz(path1) as new_path1:
         with decompress_xz(path2) as new_path2:
diff --git a/debbindiff/comparators/zip.py b/debbindiff/comparators/zip.py
index 1c2ada1..d96008b 100644
--- a/debbindiff/comparators/zip.py
+++ b/debbindiff/comparators/zip.py
@@ -27,6 +27,7 @@ import debbindiff.comparators
 from debbindiff.comparators.utils import binary_fallback, make_temp_directory, tool_required
 
 
+ at tool_required('zipinfo')
 def get_zipinfo(path, verbose=False):
     if verbose:
         cmd = ['zipinfo', '-v', path]
@@ -38,7 +39,6 @@ def get_zipinfo(path, verbose=False):
 
 
 @binary_fallback
- at tool_required('zipinfo')
 def compare_zip_files(path1, path2, source=None):
     differences = []
     with ZipFile(path1, 'r') as zip1:
diff --git a/debian/control b/debian/control
index 5b7f228..076e18c 100644
--- a/debian/control
+++ b/debian/control
@@ -25,23 +25,7 @@ Depends: diffutils,
          vim-common,
          ${misc:Depends},
          ${python:Depends},
-Recommends: acl,
-            binutils-multiarch,
-            bzip2,
-            cpio,
-            e2fsprogs,
-            file,
-            fontforge-extras,
-            gettext,
-            ghc,
-            gzip,
-            pdftk,
-            poppler-utils,
-            rpm2cpio,
-            sng,
-            squashfs-tools,
-            unzip,
-            xz-utils
+Recommends: ${debbindiff:Recommends}
 Description: highlight differences between two builds of Debian packages
  debbindiff was designed to easily compare two builds of the same Debian
  package, and understand their differences.
diff --git a/debian/rules b/debian/rules
index 2d0f518..ac7b730 100755
--- a/debian/rules
+++ b/debian/rules
@@ -5,6 +5,10 @@ VERSION = $(shell dpkg-parsechangelog --show-field Version)
 %:
 	dh $@ --with python2 --buildsystem=python_distutils
 
+override_dh_auto_build:
+	dh_auto_build -O--buildsystem=python_distutils
+	echo "debbindiff:Recommends=$$(./debbindiff.py --list-tools | tail -n 1)" >> debian/substvars
+
 override_dh_install:
 	mv debian/debbindiff/usr/bin/debbindiff.py debian/debbindiff/usr/bin/debbindiff
 	dh_install -O--buildsystem=python_distutils

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/debbindiff.git



More information about the Reproducible-commits mailing list