summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw <barry@python.org>2015-06-09 15:28:55 -0400
committerBarry Warsaw <barry@python.org>2015-06-09 15:28:55 -0400
commitf7e751759340246ef00f6fd31d6991c8150cc511 (patch)
tree72591f1843fc49d04c9f93c8d601336aa8a9ad4a
parent9ae55eea1a70044e482190a46dae3aff568ad9d1 (diff)
downloadwheel-f7e751759340246ef00f6fd31d6991c8150cc511.tar.gz
Apply the Debian patch for reproducible wheel files, but instead of hardcoding
the timestamp, allow the environment variable WHEEL_FORCE_TIMESTAMP to be used to force the TarInfo timestamps.
-rw-r--r--wheel/archive.py9
-rw-r--r--wheel/bdist_wheel.py2
-rw-r--r--wheel/metadata.py9
-rw-r--r--wheel/test/test_wheelfile.py56
4 files changed, 73 insertions, 3 deletions
diff --git a/wheel/archive.py b/wheel/archive.py
index 225d295..71dcbd4 100644
--- a/wheel/archive.py
+++ b/wheel/archive.py
@@ -2,6 +2,7 @@
Archive tools for wheel.
"""
+import time
import logging
import os.path
import zipfile
@@ -56,6 +57,14 @@ def make_wheelfile_inner(base_name, base_dir='.'):
for score, path in deferred:
writefile(path)
+ # Before we close the zip file, see if the caller is forcing the timestamp
+ # of the individual TarInfo objects. See issue #143.
+ timestamp = os.environ.get('WHEEL_FORCE_TIMESTAMP')
+ if timestamp is not None:
+ date_time = time.localtime(int(timestamp))[0:6]
+ for info in zip.infolist():
+ info.date_time = date_time
+
zip.close()
return zip_filename
diff --git a/wheel/bdist_wheel.py b/wheel/bdist_wheel.py
index ac770e9..bcf63fb 100644
--- a/wheel/bdist_wheel.py
+++ b/wheel/bdist_wheel.py
@@ -410,7 +410,7 @@ class bdist_wheel(Command):
pymeta['extensions']['python.details']['document_names']['license'] = license_filename
with open(metadata_json_path, "w") as metadata_json:
- json.dump(pymeta, metadata_json)
+ json.dump(pymeta, metadata_json, sort_keys=True)
adios(egginfo_path)
diff --git a/wheel/metadata.py b/wheel/metadata.py
index 02c0663..d6a7627 100644
--- a/wheel/metadata.py
+++ b/wheel/metadata.py
@@ -74,7 +74,14 @@ def handle_requires(metadata, pkg_info, key):
if may_requires:
metadata['run_requires'] = []
- for key, value in may_requires.items():
+ def sort_key(item):
+ # Both condition and extra could be None, which can't be compared
+ # against strings in Python 3.
+ key, value = item
+ if key.condition is None:
+ return ''
+ return key.condition
+ for key, value in sorted(may_requires.items(), key=sort_key):
may_requirement = OrderedDict((('requires', value),))
if key.extra:
may_requirement['extra'] = key.extra
diff --git a/wheel/test/test_wheelfile.py b/wheel/test/test_wheelfile.py
index e362ceb..561f578 100644
--- a/wheel/test/test_wheelfile.py
+++ b/wheel/test/test_wheelfile.py
@@ -1,11 +1,48 @@
+import os
import wheel.install
+import wheel.archive
import hashlib
try:
from StringIO import StringIO
except ImportError:
from io import BytesIO as StringIO
+import codecs
import zipfile
import pytest
+import shutil
+import tempfile
+from contextlib import contextmanager
+
+@contextmanager
+def environ(key, value):
+ old_value = os.environ.get(key)
+ try:
+ os.environ[key] = value
+ yield
+ finally:
+ if old_value is None:
+ del os.environ[key]
+ else:
+ os.environ[key] = old_value
+
+@contextmanager
+def temporary_directory():
+ # tempfile.TemporaryDirectory doesn't exist in Python 2.
+ tempdir = tempfile.mkdtemp()
+ try:
+ yield tempdir
+ finally:
+ shutil.rmtree(tempdir)
+
+@contextmanager
+def readable_zipfile(path):
+ # zipfile.ZipFile() isn't a context manager under Python 2.
+ zf = zipfile.ZipFile(path, 'r')
+ try:
+ yield zf
+ finally:
+ zf.close()
+
def test_verifying_zipfile():
if not hasattr(zipfile.ZipExtFile, '_update_crc'):
@@ -66,4 +103,21 @@ def test_pop_zipfile():
zf = wheel.install.VerifyingZipFile(sio, 'r')
assert len(zf.infolist()) == 1
- \ No newline at end of file
+
+def test_zipfile_timestamp():
+ # An environment variable can be used to influence the timestamp on
+ # TarInfo objects inside the zip. See issue #143. TemporaryDirectory is
+ # not a context manager under Python 3.
+ with temporary_directory() as tempdir:
+ for filename in ('one', 'two', 'three'):
+ path = os.path.join(tempdir, filename)
+ with codecs.open(path, 'w', encoding='utf-8') as fp:
+ fp.write(filename + '\n')
+ zip_base_name = os.path.join(tempdir, 'dummy')
+ # The earliest date representable in TarInfos, 1980-01-01
+ with environ('WHEEL_FORCE_TIMESTAMP', '315576060'):
+ zip_filename = wheel.archive.make_wheelfile_inner(
+ zip_base_name, tempdir)
+ with readable_zipfile(zip_filename) as zf:
+ for info in zf.infolist():
+ assert info.date_time[:3] == (1980, 1, 1)