Skip to content

Commit

Permalink
Respect SOURCE_DATE_EPOCH when taring sdist
Browse files Browse the repository at this point in the history
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 pypa#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.
  • Loading branch information
Carreau committed May 25, 2020
1 parent f047962 commit a66faa9
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 5 deletions.
27 changes: 22 additions & 5 deletions setuptools/archive_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import shutil
import posixpath
import contextlib
import distutils.archive_util
from distutils.errors import DistutilsError
from distutils.dir_util import mkpath
from distutils import log
Expand Down Expand Up @@ -177,6 +178,7 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile


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

log.info('Creating tar archive')

uid = _get_uid(owner)
gid = _get_gid(group)
uid = distutils.archive_util._get_uid(owner)
gid = distutils.archive_util._get_gid(group)

def _set_uid_gid(tarinfo):
if gid is not None:
Expand All @@ -228,10 +230,26 @@ def _set_uid_gid(tarinfo):
tarinfo.uname = owner
return tarinfo

_filter = _set_uid_gid

# SOURCE_DATE EPOCH is defined there
# https://reproducible-builds.org/specs/source-date-epoch/
# we are at least sure that when it is set no timestamp can be later than
# this.
timestamp = None
sde = os.environ.get('SOURCE_DATE_EPOCH')
if sde:
timestamp = int(sde)

def _filter(tarinfo):
tarinfo = _set_uid_gid(tarinfo)
tarinfo.mtime = min(tarinfo.mtime, timestamp)
return tarinfo

if not dry_run:
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
try:
tar.add(base_dir, filter=_set_uid_gid)
tar.add(base_dir, filter=_filter)
finally:
tar.close()

Expand All @@ -256,7 +274,7 @@ def _set_uid_gid(tarinfo):
'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"),
'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"),
'tar': (make_tarball, [('compress', None)], "uncompressed tar file"),
'zip': (make_zipfile, [], "ZIP file")
'zip': (distutils.archive_util.make_zipfile, [], "ZIP file")
}


Expand Down Expand Up @@ -309,5 +327,4 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
if root_dir is not None:
log.debug("changing back to '%s'", save_cwd)
os.chdir(save_cwd)

return filename
7 changes: 7 additions & 0 deletions setuptools/command/sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from setuptools.extern import six, ordered_set

from .py36compat import sdist_add_defaults
from .. import archive_util

import pkg_resources

Expand Down Expand Up @@ -77,6 +78,12 @@ def make_distribution(self):
with self._remove_os_link():
orig.sdist.make_distribution(self)

def make_archive(self, base_name, format, root_dir=None, base_dir=None,
owner=None, group=None):
return archive_util.make_archive(base_name, format, root_dir, base_dir,
dry_run=self.dry_run,
owner=owner, group=group)

@staticmethod
@contextlib.contextmanager
def _remove_os_link():
Expand Down

0 comments on commit a66faa9

Please sign in to comment.