Source: sphinx
Version: 1.3.1-4
Severity: wishlist
Tags: patch
User: reproducible-builds@lists.alioth.debian.org
Usertags: timestamps randomness
X-Debbugs-Cc: reproducible-builds@lists.alioth.debian.org

Hi!

While working on the “reproducible builds” effort [1], we have noticed
that sphinx could not be built reproducibly.

The attached patch removes build timestamp from the output
documentation, makes domains sorted in HTML documentation, and makes
generated automata (and their pickle dump) deterministic. Once applied,
sphinx (and packages using sphinx) can be built reproducibly in our
current experimental framework.

 [1]: https://wiki.debian.org/ReproducibleBuilds

Regards,
Val
diff -ru sphinx-1.3.1.old/debian/rules sphinx-1.3.1/debian/rules
--- sphinx-1.3.1.old/debian/rules	2015-08-17 17:41:44.557345555 +0000
+++ sphinx-1.3.1/debian/rules	2015-08-18 12:26:01.040804815 +0000
@@ -3,11 +3,14 @@
 
 include /usr/share/python/python.mk
 
+export SOURCE_DATE_EPOCH = $(shell date -d "$$(dpkg-parsechangelog --count 1 -SDate)" +%s)
 export NO_PKG_MANGLE=1
 export PYTHONWARNINGS=d
-export PYTHONHASHSEED=random
 export http_proxy=http://127.0.0.1:9/
 
+# For deterministic pickling
+export PYTHONHASHSEED=0
+
 here = $(dir $(firstword $(MAKEFILE_LIST)))/..
 debian_version = $(word 2,$(shell cd $(here) && dpkg-parsechangelog | grep ^Version:))
 upstream_version = $(subst ~,,$(firstword $(subst -, ,$(debian_version))))
diff -ru sphinx-1.3.1.old/setup.py sphinx-1.3.1/setup.py
--- sphinx-1.3.1.old/setup.py	2015-08-17 17:41:44.557345555 +0000
+++ sphinx-1.3.1/setup.py	2015-08-18 11:41:20.000000000 +0000
@@ -162,7 +162,7 @@
                         messages=jscatalog,
                         plural_expr=catalog.plural_expr,
                         locale=str(catalog.locale)
-                    ), outfile)
+                    ), outfile, sort_keys=True)
                     outfile.write(');')
                 finally:
                     outfile.close()
diff -ru sphinx-1.3.1.old/sphinx/builders/html.py sphinx-1.3.1/sphinx/builders/html.py
--- sphinx-1.3.1.old/sphinx/builders/html.py	2015-08-17 17:41:44.565345555 +0000
+++ sphinx-1.3.1/sphinx/builders/html.py	2015-08-17 19:46:48.000000000 +0000
@@ -824,7 +824,7 @@
                      u'# The remainder of this file is compressed using zlib.\n'
                      % (self.config.project, self.config.version)).encode('utf-8'))
             compressor = zlib.compressobj(9)
-            for domainname, domain in iteritems(self.env.domains):
+            for domainname, domain in sorted(self.env.domains.items()):
                 for name, dispname, type, docname, anchor, prio in \
                         sorted(domain.get_objects()):
                     if anchor.endswith(name):
diff -ru sphinx-1.3.1.old/sphinx/pycode/pgen2/pgen.py sphinx-1.3.1/sphinx/pycode/pgen2/pgen.py
--- sphinx-1.3.1.old/sphinx/pycode/pgen2/pgen.py	2015-08-17 17:41:44.561345555 +0000
+++ sphinx-1.3.1/sphinx/pycode/pgen2/pgen.py	2015-08-18 12:06:30.000000000 +0000
@@ -4,6 +4,7 @@
 from __future__ import print_function
 
 from six import iteritems
+from collections import OrderedDict
 
 # Pgen imports
 
@@ -57,7 +58,7 @@
     def make_first(self, c, name):
         rawfirst = self.first[name]
         first = {}
-        for label in rawfirst:
+        for label in sorted(rawfirst):
             ilabel = self.make_label(c, label)
             ##assert ilabel not in first # X X X failed on <> ... !=
             first[ilabel] = 1
@@ -138,8 +139,8 @@
                 totalset[label] = 1
                 overlapcheck[label] = {label: 1}
         inverse = {}
-        for label, itsfirst in iteritems(overlapcheck):
-            for symbol in itsfirst:
+        for label, itsfirst in sorted(overlapcheck.items()):
+            for symbol in sorted(itsfirst):
                 if symbol in inverse:
                     raise ValueError("rule %s is ambiguous; %s is in the"
                                      " first sets of %s as well as %s" %
@@ -349,6 +350,9 @@
         assert isinstance(next, NFAState)
         self.arcs.append((label, next))
 
+    def __hash__(self):
+        return hash(tuple(x[0] for x in self.arcs))
+
 class DFAState(object):
 
     def __init__(self, nfaset, final):
@@ -357,7 +361,10 @@
         assert isinstance(final, NFAState)
         self.nfaset = nfaset
         self.isfinal = final in nfaset
-        self.arcs = {} # map from label to DFAState
+        self.arcs = OrderedDict() # map from label to DFAState
+
+    def __hash__(self):
+        return hash(tuple(self.arcs))
 
     def addarc(self, next, label):
         assert isinstance(label, str)

Attachment: signature.asc
Description: OpenPGP digital signature

_______________________________________________
Reproducible-builds mailing list
Reproducible-builds@lists.alioth.debian.org
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/reproducible-builds

Reply via email to