Source: sphinx
Version: 1.3.6-2
Severity: wishlist
Tags: patch upstream
User: reproducible-builds@lists.alioth.debian.org
Usertags: toolchain
X-Debbugs-Cc: reproducible-builds@lists.alioth.debian.org

Dear Maintainer,

While working on the “reproducible builds” effort [1], we have noticed
that, even though sphinx honours the SOURCE_DATE_EPOCH environment
variable [2], this support is still incomplete: default copyright year
and gettext don't use it.

Various packages (eg. fabric, guidata) that build-depend on sphinx use a
conf.py that sets the copyright year from current time, like

  copyright = u'2006-%s, Author' % time.strftime('%Y')

This also breaks reproducibility of the building process.

The attached patch extends the SOURCE_DATE_EPOCH support in copyright
year and gettext, and corrects copyright strings that does not
corresponds to SOURCE_DATE_EPOCH, so that affected packages can be built
reproducibly without any change.

Regards,
Alexis Bienvenüe.

[1] https://wiki.debian.org/ReproducibleBuilds
[2] https://reproducible-builds.org/specs/source-date-epoch/


diff -Nru sphinx-1.3.6/debian/changelog sphinx-1.3.6/debian/changelog
--- sphinx-1.3.6/debian/changelog	2016-03-03 18:22:21.000000000 +0100
+++ sphinx-1.3.6/debian/changelog	2016-04-13 14:05:28.000000000 +0200
@@ -1,3 +1,9 @@
+sphinx (1.3.6-2.0~reproducible1) unstable; urgency=medium
+
+  * Extends SOURCE_DATE_EPOCH support to copyright year.
+
+ -- Alexis Bienvenüe <p...@passoire.fr>  Wed, 13 Apr 2016 09:45:47 +0200
+
 sphinx (1.3.6-2) unstable; urgency=medium
 
   * Use implementation of jstest from Iain Lane in hope it succeeds on
diff -Nru sphinx-1.3.6/debian/patches/correct_copyright_year_from_source_date_epoch.patch sphinx-1.3.6/debian/patches/correct_copyright_year_from_source_date_epoch.patch
--- sphinx-1.3.6/debian/patches/correct_copyright_year_from_source_date_epoch.patch	1970-01-01 01:00:00.000000000 +0100
+++ sphinx-1.3.6/debian/patches/correct_copyright_year_from_source_date_epoch.patch	2016-04-13 14:36:48.000000000 +0200
@@ -0,0 +1,45 @@
+Description: Correct copyright year from SOURCE_DATE_EPOCH
+ If the environment variable SOURCE_DATE_EPOCH is set, use it to correct
+ uncoherent copyright years that are set using strftime, which don't honour
+ SOURCE_DATE_EPOCH.
+ This helps reproducibility of packages that build-depends on sphinx.
+Author: Alexis Bienvenüe <p...@passoire.fr>
+
+Index: sphinx-1.3.6/sphinx/config.py
+===================================================================
+--- sphinx-1.3.6.orig/sphinx/config.py
++++ sphinx-1.3.6/sphinx/config.py
+@@ -10,14 +10,14 @@
+ """
+ 
+ import re
+-from os import path, environ
++from os import path, environ, getenv
+ import shlex
+ 
+ from six import PY3, iteritems, string_types, binary_type, integer_types
+ 
+ from sphinx.errors import ConfigError
+ from sphinx.locale import l_
+-from sphinx.util.osutil import make_filename, cd
++from sphinx.util.osutil import make_filename, cd, ustrftime
+ from sphinx.util.pycompat import execfile_
+ 
+ nonascii_re = re.compile(br'[\x80-\xff]')
+@@ -286,6 +286,16 @@ class Config(object):
+         self.setup = config.get('setup', None)
+         self.extensions = config.get('extensions', [])
+ 
++        # correct values of copyright year that are not coherent with
++        # the SOURCE_DATE_EPOCH environment variable:
++        source_date_epoch = getenv('SOURCE_DATE_EPOCH')
++        if source_date_epoch is not None:
++            for k in ['copyright','epub_copyright']:
++                if k in config:
++                    config[k] = re.sub('^((\d{4}-)?)(\d{4})(?=[ ,])',
++                                       '\g<1>%s' % ustrftime('%Y'),
++                                       config[k])
++
+     def check_types(self, warn):
+         # check all values for deviation from the default value's type, since
+         # that can result in TypeErrors all over the place
diff -Nru sphinx-1.3.6/debian/patches/series sphinx-1.3.6/debian/patches/series
--- sphinx-1.3.6/debian/patches/series	2016-03-03 18:22:21.000000000 +0100
+++ sphinx-1.3.6/debian/patches/series	2016-04-13 14:02:26.000000000 +0200
@@ -7,3 +7,4 @@
 reproducible_inventory.diff
 reproducible_js_locale.diff
 reproducible_searchindex.diff
+correct_copyright_year_from_source_date_epoch.patch
diff -Nru sphinx-1.3.6/debian/patches/source_date_epoch.diff sphinx-1.3.6/debian/patches/source_date_epoch.diff
--- sphinx-1.3.6/debian/patches/source_date_epoch.diff	2016-03-03 18:22:21.000000000 +0100
+++ sphinx-1.3.6/debian/patches/source_date_epoch.diff	2016-04-13 10:03:43.000000000 +0200
@@ -11,10 +11,10 @@
  sphinx/util/osutil.py | 15 +++++++++++----
  1 file changed, 11 insertions(+), 4 deletions(-)
 
-diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py
-index 70d5cf5..e1d29a9 100644
---- a/sphinx/util/osutil.py
-+++ b/sphinx/util/osutil.py
+Index: sphinx-1.3.6/sphinx/util/osutil.py
+===================================================================
+--- sphinx-1.3.6.orig/sphinx/util/osutil.py
++++ sphinx-1.3.6/sphinx/util/osutil.py
 @@ -151,15 +151,22 @@ no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
  def make_filename(string):
      return no_fn_re.sub('', string) or 'sphinx'
@@ -42,3 +42,102 @@
          # On Windows, time.strftime() and Unicode characters will raise UnicodeEncodeError.
          # http://bugs.python.org/issue8304
          try:
+Index: sphinx-1.3.6/sphinx/builders/epub.py
+===================================================================
+--- sphinx-1.3.6.orig/sphinx/builders/epub.py
++++ sphinx-1.3.6/sphinx/builders/epub.py
+@@ -29,7 +29,7 @@ from docutils import nodes
+ 
+ from sphinx import addnodes
+ from sphinx.builders.html import StandaloneHTMLBuilder
+-from sphinx.util.osutil import ensuredir, copyfile, EEXIST
++from sphinx.util.osutil import ensuredir, copyfile, EEXIST, ustrftime
+ from sphinx.util.smartypants import sphinx_smarty_pants as ssp
+ from sphinx.util.console import brown
+ 
+@@ -511,7 +511,7 @@ class EpubBuilder(StandaloneHTMLBuilder)
+         metadata['copyright'] = self.esc(self.config.epub_copyright)
+         metadata['scheme'] = self.esc(self.config.epub_scheme)
+         metadata['id'] = self.esc(self.config.epub_identifier)
+-        metadata['date'] = self.esc(time.strftime('%Y-%m-%d'))
++        metadata['date'] = self.esc(ustrftime('%Y-%m-%d'))
+         metadata['files'] = files
+         metadata['spine'] = spine
+         metadata['guide'] = guide
+Index: sphinx-1.3.6/sphinx/builders/gettext.py
+===================================================================
+--- sphinx-1.3.6.orig/sphinx/builders/gettext.py
++++ sphinx-1.3.6/sphinx/builders/gettext.py
+@@ -11,7 +11,7 @@
+ 
+ from __future__ import unicode_literals
+ 
+-from os import path, walk
++from os import path, walk, getenv
+ from codecs import open
+ from time import time
+ from datetime import datetime, tzinfo, timedelta
+@@ -130,7 +130,10 @@ class I18nBuilder(Builder):
+ timestamp = time()
+ tzdelta = datetime.fromtimestamp(timestamp) - \
+     datetime.utcfromtimestamp(timestamp)
+-
++source_date_epoch = getenv('SOURCE_DATE_EPOCH')
++if source_date_epoch is not None:
++    timestamp = float(source_date_epoch)
++    tzdelta = 0
+ 
+ class LocalTimeZone(tzinfo):
+ 
+Index: sphinx-1.3.6/sphinx/quickstart.py
+===================================================================
+--- sphinx-1.3.6.orig/sphinx/quickstart.py
++++ sphinx-1.3.6/sphinx/quickstart.py
+@@ -35,7 +35,7 @@ from six.moves.urllib.parse import quote
+ from docutils.utils import column_width
+ 
+ from sphinx import __display_version__
+-from sphinx.util.osutil import make_filename
++from sphinx.util.osutil import make_filename, ustrftime
+ from sphinx.util.console import purple, bold, red, turquoise, \
+     nocolor, color_terminal
+ from sphinx.util import texescape
+@@ -1335,7 +1335,7 @@ def generate(d, overwrite=True, silent=F
+         d['extensions'] = '\n' + indent + extensions + ',\n'
+     else:
+         d['extensions'] = extensions
+-    d['copyright'] = time.strftime('%Y') + ', ' + d['author']
++    d['copyright'] = ustrftime('%Y') + ', ' + d['author']
+     d['author_texescaped'] = text_type(d['author']).\
+         translate(texescape.tex_escape_map)
+     d['project_doc'] = d['project'] + ' Documentation'
+Index: sphinx-1.3.6/tests/test_quickstart.py
+===================================================================
+--- sphinx-1.3.6.orig/tests/test_quickstart.py
++++ sphinx-1.3.6/tests/test_quickstart.py
+@@ -21,6 +21,7 @@ from sphinx import application
+ from sphinx import quickstart as qs
+ from sphinx.util.console import nocolor, coloron
+ from sphinx.util.pycompat import execfile_
++from sphinx.util.osutil import ustrftime
+ 
+ 
+ warnfile = StringIO()
+@@ -147,7 +148,7 @@ def test_quickstart_defaults(tempdir):
+     assert ns['source_suffix'] == '.rst'
+     assert ns['master_doc'] == 'index'
+     assert ns['project'] == 'Sphinx Test'
+-    assert ns['copyright'] == '%s, Georg Brandl' % time.strftime('%Y')
++    assert ns['copyright'] == '%s, Georg Brandl' % ustrftime('%Y')
+     assert ns['version'] == '0.1'
+     assert ns['release'] == '0.1'
+     assert ns['todo_include_todos'] is False
+@@ -207,7 +208,7 @@ def test_quickstart_all_answers(tempdir)
+     assert ns['master_doc'] == 'contents'
+     assert ns['project'] == u'STASI™'
+     assert ns['copyright'] == u'%s, Wolfgang Schäuble & G\'Beckstein' % \
+-        time.strftime('%Y')
++        ustrftime('%Y')
+     assert ns['version'] == '2.0'
+     assert ns['release'] == '2.0.1'
+     assert ns['todo_include_todos'] is True
_______________________________________________
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