Skip to content

Commit a66faa9

Browse files
committed
Respect SOURCE_DATE_EPOCH when taring sdist
This pulls just enough of distutils' and modify the make_tarball function in order to respect SOURCE_DATE_EPOCH; this will ensure that _when set_ no timestamp in the final archive is greater than timestamp. This allows (but is not always sufficient), to make bytes for bytes reproducible build for example: - This does not work with `gztar`, and zip does embed a timestamp in the header which currently is `time.time()` in the standard library. - if some fields passed to setup.py have on determinstic ordering (for example using sets for dependencies). Partial work toward #2133, with this I was able to make two bytes-identical sdist of IPython. You will see three types of modifications: - Referring explicitly to some of distutils namespace in a couple of places, to avoid duplicating more code. Note that despite some names _not_ changing as the name resolution is with respect to current module, unchanged functions will now use our modified version. - overwrite `make_archive` in sdist to use our patched version of the functions in archive_utils. - update make_tarball to look for SOURCE_DATE_EPOCH in environment and setup a filter to modify mtime while taring.
1 parent f047962 commit a66faa9

File tree

2 files changed

+29
-5
lines changed

2 files changed

+29
-5
lines changed

setuptools/archive_util.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import shutil
77
import posixpath
88
import contextlib
9+
import distutils.archive_util
910
from distutils.errors import DistutilsError
1011
from distutils.dir_util import mkpath
1112
from distutils import log
@@ -177,6 +178,7 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
177178
extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile
178179

179180

181+
# Modified version fo distutils' to support SOURCE_DATE_EPOCH
180182
def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
181183
owner=None, group=None):
182184
"""Create a (possibly compressed) tar file from all the files under
@@ -216,8 +218,8 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
216218

217219
log.info('Creating tar archive')
218220

219-
uid = _get_uid(owner)
220-
gid = _get_gid(group)
221+
uid = distutils.archive_util._get_uid(owner)
222+
gid = distutils.archive_util._get_gid(group)
221223

222224
def _set_uid_gid(tarinfo):
223225
if gid is not None:
@@ -228,10 +230,26 @@ def _set_uid_gid(tarinfo):
228230
tarinfo.uname = owner
229231
return tarinfo
230232

233+
_filter = _set_uid_gid
234+
235+
# SOURCE_DATE EPOCH is defined there
236+
# https://reproducible-builds.org/specs/source-date-epoch/
237+
# we are at least sure that when it is set no timestamp can be later than
238+
# this.
239+
timestamp = None
240+
sde = os.environ.get('SOURCE_DATE_EPOCH')
241+
if sde:
242+
timestamp = int(sde)
243+
244+
def _filter(tarinfo):
245+
tarinfo = _set_uid_gid(tarinfo)
246+
tarinfo.mtime = min(tarinfo.mtime, timestamp)
247+
return tarinfo
248+
231249
if not dry_run:
232250
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
233251
try:
234-
tar.add(base_dir, filter=_set_uid_gid)
252+
tar.add(base_dir, filter=_filter)
235253
finally:
236254
tar.close()
237255

@@ -256,7 +274,7 @@ def _set_uid_gid(tarinfo):
256274
'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"),
257275
'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"),
258276
'tar': (make_tarball, [('compress', None)], "uncompressed tar file"),
259-
'zip': (make_zipfile, [], "ZIP file")
277+
'zip': (distutils.archive_util.make_zipfile, [], "ZIP file")
260278
}
261279

262280

@@ -309,5 +327,4 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
309327
if root_dir is not None:
310328
log.debug("changing back to '%s'", save_cwd)
311329
os.chdir(save_cwd)
312-
313330
return filename

setuptools/command/sdist.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from setuptools.extern import six, ordered_set
99

1010
from .py36compat import sdist_add_defaults
11+
from .. import archive_util
1112

1213
import pkg_resources
1314

@@ -77,6 +78,12 @@ def make_distribution(self):
7778
with self._remove_os_link():
7879
orig.sdist.make_distribution(self)
7980

81+
def make_archive(self, base_name, format, root_dir=None, base_dir=None,
82+
owner=None, group=None):
83+
return archive_util.make_archive(base_name, format, root_dir, base_dir,
84+
dry_run=self.dry_run,
85+
owner=owner, group=group)
86+
8087
@staticmethod
8188
@contextlib.contextmanager
8289
def _remove_os_link():

0 commit comments

Comments
 (0)