Hi Paul & James,

Thanks for the review :)

Updated patch attached:

  * Moved away from help2man.

  * Changed cache filename suffix to .bz2. (It was a mistake)

  * Added a missing python3-xdg to the packages "required" list in
    debian/control.)

  * Dropped the rather verbose "2017-08-27 09:48:02 " prefix from
    log messages.

  * Dropped aliasing of "architecture" to prevent an unused variable
    warning from static analysis tools.

  * Wrapped some long lines.


  commit 381f7070f28940796ab795c600764cfdc0429924
  Author: Chris Lamb <ch...@chris-lamb.co.uk>
  Date:   Sun Aug 20 11:18:29 2017 -0700
  
      reproducible-check: New script to report on reproducibility status of 
installed packages. (Closes: #872514)
      
      Signed-off-by: Chris Lamb <la...@debian.org>
  
   README                       |   3 +
   debian/control               |   8 ++
   debian/copyright             |   6 ++
   scripts/Makefile             |   2 +-
   scripts/reproducible-check   | 188 
+++++++++++++++++++++++++++++++++++++++++++
   scripts/reproducible-check.1 |  22 +++++
   scripts/setup.py             |   2 +-
   7 files changed, 229 insertions(+), 2 deletions(-)


Regards,

-- 
      ,''`.
     : :'  :     Chris Lamb
     `. `'`      la...@debian.org / chris-lamb.co.uk
       `-
From 381f7070f28940796ab795c600764cfdc0429924 Mon Sep 17 00:00:00 2001
From: Chris Lamb <ch...@chris-lamb.co.uk>
Date: Sun, 20 Aug 2017 11:18:29 -0700
Subject: [PATCH] reproducible-check: New script to report on reproducibility
 status of installed packages. (Closes: #872514)

Signed-off-by: Chris Lamb <la...@debian.org>
---
 README                       |   3 +
 debian/control               |   8 ++
 debian/copyright             |   6 ++
 scripts/Makefile             |   2 +-
 scripts/reproducible-check   | 188 +++++++++++++++++++++++++++++++++++++++++++
 scripts/reproducible-check.1 |  22 +++++
 scripts/setup.py             |   2 +-
 7 files changed, 229 insertions(+), 2 deletions(-)
 create mode 100755 scripts/reproducible-check
 create mode 100644 scripts/reproducible-check.1

diff --git a/README b/README
index ba8b265b..e54c9eab 100644
--- a/README
+++ b/README
@@ -226,6 +226,9 @@ And now, in mostly alphabetical order, the scripts:
 - rc-alert: list installed packages which have release-critical bugs
   [wget | curl]
 
+- repoducible-check: reports on the reproducibility status of installed
+  packages. [python3-apt, python3-requests, python3-xdg]
+
 - rmadison: remotely query the Debian archive database about packages
   [liburi-perl, wget | curl]
 
diff --git a/debian/control b/debian/control
index 82165b80..6b308d19 100644
--- a/debian/control
+++ b/debian/control
@@ -29,11 +29,14 @@ Build-Depends: bash-completion,
                po4a (>= 0.40),
                pylint,
                python3-all,
+               python3-apt,
                python3-debian (>= 0.1.15),
                python3-flake8,
                python3-magic,
+               python3-requests,
                python3-setuptools,
                python3-unidiff <!nocheck>,
+               python3-xdg,
                shunit2 (>= 2.1.6),
                unzip,
                wdiff,
@@ -72,9 +75,12 @@ Recommends: apt,
             man-db,
             patch,
             patchutils,
+            python3-apt,
             python3-debian (>= 0.1.15),
             python3-magic,
+            python3-requests,
             python3-unidiff,
+            python3-xdg,
             sensible-utils,
             strace,
             unzip,
@@ -215,6 +221,8 @@ Description: scripts to make the life of a Debian Package maintainer easier
     [libtimedate-perl, gnuplot]
   - pts-subscribe: subscribe to the PTS for a limited period of time
     [bsd-mailx | mailx, at]
+  - reproducible-check: reports on the reproducible status of installed
+    packages [python3-apt, python3-requests, python3-xdg]
   - rc-alert: list installed packages which have release-critical bugs
     [wget | curl]
   - rmadison: remotely query the Debian archive database about packages
diff --git a/debian/copyright b/debian/copyright
index c22addad..95550dc7 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -68,6 +68,12 @@ Copyright: 2009,      Jonathan Patrick Davies <j...@ubuntu.com>
            2006-2008, Kees Cook <k...@ubuntu.com>
            2007-2008, Siegfried-Angel Gevatter Pujals <rai...@ubuntu.com>
            2013,      Rafael Laboissiere <raf...@laboissiere.net>
+License: GPL-3+
+
+Files: scripts/reproducible-check
+Copyright: © 2017 Chris Lamb <la...@debian.org>
+License: GPL-3+
+
 License: GPL-3+
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
diff --git a/scripts/Makefile b/scripts/Makefile
index ece5455a..47206edf 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -24,7 +24,7 @@ COMPLETION = $(patsubst %.bash_completion,$(BC_BUILD_DIR)/%,$(COMPL_FILES))
 COMPL_DIR := $(shell pkg-config --variable=completionsdir bash-completion)
 PKGNAMES:=wnpp-alert wnpp-check mk-build-deps rmadison mass-bug debsnap dd-list build-rdeps who-uploads transition-check getbuildlog dcontrol grep-excuses rc-alert whodepends dget pts-subscribe pts-unsubscribe debcheckout
 # also update the list in setup.py
-PYTHON3_SCRIPTS:=debdiff-apply sadt suspicious-source wrap-and-sort
+PYTHON3_SCRIPTS:=debdiff-apply sadt suspicious-source wrap-and-sort reproducible-check
 
 GEN_MAN1S += debrepro.1 devscripts.1 mk-origtargz.1 uscan.1
 
diff --git a/scripts/reproducible-check b/scripts/reproducible-check
new file mode 100755
index 00000000..d99d041f
--- /dev/null
+++ b/scripts/reproducible-check
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2017 Chris Lamb <la...@debian.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at
+# your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import bz2
+import apt
+import sys
+import json
+import time
+import logging
+import requests
+import argparse
+import collections
+
+from xdg.BaseDirectory import xdg_cache_home
+
+
+class ReproducibleCheck(object):
+    HELP = """
+        Reports on the reproducible status of installed packages.
+        For more details please see <https://reproducible-builds.org>.
+    """
+
+    NAME = os.path.basename(__file__)
+    VERSION = 1
+    DATA_URL = 'https://tests.reproducible-builds.org/debian/reproducible.json.bz2'
+    CACHE_FILENAME = os.path.join(xdg_cache_home, NAME, 'reproducible.json.bz2')
+    CACHE_DURATION_SECS = 86400
+
+    @classmethod
+    def parse(cls):
+        parser = argparse.ArgumentParser(description=cls.HELP)
+
+        parser.add_argument(
+            '-d',
+            '--debug',
+            help="show debugging messages",
+            default=False,
+            action='store_true',
+        )
+
+        parser.add_argument(
+            '-r',
+            '--raw',
+            help="print unreproducible binary packages only (for dd-list -i)",
+            default=False,
+            action='store_true',
+        )
+
+        parser.add_argument(
+            '--version',
+            help="print version and exit",
+            default=False,
+            action='store_true',
+        )
+
+        return cls(parser.parse_args())
+
+    def __init__(self, args):
+        self.args = args
+
+        logging.basicConfig(
+            format='%(levelname).1s: %(message)s',
+            level=logging.DEBUG if args.debug else logging.INFO,
+        )
+
+        self.log = logging.getLogger()
+
+    def main(self):
+        if self.args.version:
+            print("{} version {}".format(self.NAME, self.VERSION))
+            return 0
+
+        if self.should_update_cache():
+            self.update_cache()
+
+        data = self.get_data()
+        installed = self.get_installed_packages()
+        unreproducible = {x: y for x, y in installed.items() if x in data}
+
+        if self.args.raw:
+            self.output_raw(unreproducible, installed)
+        else:
+            self.output_by_source(unreproducible, installed)
+
+        return 0
+
+    def should_update_cache(self):
+        self.log.debug("Checking cache file %s ...", self.CACHE_FILENAME)
+
+        if not os.path.exists(self.CACHE_FILENAME):
+            self.log.debug("Cache file does not exist")
+            return True
+
+        last_update = os.path.getmtime(self.CACHE_FILENAME)
+        if time.time() - last_update >= self.CACHE_DURATION_SECS:
+            self.log.debug("Cache file is stale")
+            return True
+
+        return False
+
+    def update_cache(self):
+        response = requests.get(self.DATA_URL, headers={
+            'User-Agent': '{}/{} ({})'.format(
+                self.NAME,
+                self.VERSION,
+                requests.utils.default_user_agent(),
+            ),
+        })
+
+        os.makedirs(os.path.dirname(self.CACHE_FILENAME), exist_ok=True)
+
+        with open(self.CACHE_FILENAME, 'wb+') as f:
+            f.write(response.content)
+
+    def get_data(self):
+        self.log.debug("Loading data from cache %s", self.CACHE_FILENAME)
+
+        with bz2.open(self.CACHE_FILENAME) as f:
+            return {
+                (x['package'], x['architecture'], x['version'])
+                for x in json.loads(f.read().decode('utf-8'))
+                if x['status'] == 'unreproducible'
+            }
+
+    def get_installed_packages(self):
+        result = collections.defaultdict(list)
+
+        for x in apt.Cache():
+            for y in x.versions:
+                if not y.is_installed:
+                    continue
+
+                key = (y.source_name, y.architecture, y.version)
+                result[key].append(x.shortname)
+
+        return result
+
+    def output_by_source(self, unreproducible, installed):
+        num_installed = sum(len(x) for x in installed.keys())
+        num_unreproducible = sum(len(x) for x in unreproducible.keys())
+
+        for key, binaries in sorted(unreproducible.items()):
+            source, _, version = key
+
+            binaries_fmt = '({}) '.format(', '.join(binaries)) \
+                if binaries != [source] else ''
+
+            print("{} ({}) is unreproducible {}".format(
+                source,
+                version,
+                binaries_fmt,
+            ), end='')
+
+            print("<https://tests.reproducible-builds.org/debian/{}>".format(
+                source,
+            ))
+
+        print("{}/{} ({:.2f}%) of installed binary packages are unreproducible.".format(
+            num_unreproducible,
+            num_installed,
+            100. * num_unreproducible / num_installed,
+        ))
+
+    def output_raw(self, unreproducible, installed):
+        for x in sorted(x for xs in unreproducible.values() for x in xs):
+            print(x)
+
+
+if __name__ == '__main__':
+    try:
+        sys.exit(ReproducibleCheck.parse().main())
+    except (KeyboardInterrupt, BrokenPipeError):
+        sys.exit(1)
diff --git a/scripts/reproducible-check.1 b/scripts/reproducible-check.1
new file mode 100644
index 00000000..db76d680
--- /dev/null
+++ b/scripts/reproducible-check.1
@@ -0,0 +1,22 @@
+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.4.
+.TH REPRODUCIBLE-CHECK "1" "August 2017" "reproducible-check" "User Commands"
+.SH NAME
+reproducible-check \- report on the reproducible status of packages
+.SH DESCRIPTION
+usage: reproducible\-check [\-h] [\-d] [\-r] [\-\-version]
+.PP
+Reports on the reproducible status of installed packages. For more details
+please see <https://reproducible\-builds.org>.
+.SS "optional arguments:"
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+show this help message and exit
+.TP
+\fB\-d\fR, \fB\-\-debug\fR
+show debugging messages
+.TP
+\fB\-r\fR, \fB\-\-raw\fR
+print unreproducible binary packages only (for dd\-list \fB\-i\fR)
+.TP
+\fB\-\-version\fR
+print version and exit
diff --git a/scripts/setup.py b/scripts/setup.py
index 69884a5c..091e4d32 100755
--- a/scripts/setup.py
+++ b/scripts/setup.py
@@ -12,7 +12,7 @@ if os.path.exists(changelog):
     if match:
         version = match.group(1)
 
-scripts = "debdiff-apply sadt suspicious-source wrap-and-sort".split()
+scripts = "debdiff-apply sadt suspicious-source wrap-and-sort reproducible-check".split()
 
 if __name__ == '__main__':
     setup(
-- 
2.14.1

Reply via email to