Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push wheel timestamps to 1980 if SOURCE_DATE_EPOCH before that #448

Merged
merged 4 commits into from Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions flit_core/flit_core/tests/test_wheel.py
@@ -1,4 +1,5 @@
from pathlib import Path
from zipfile import ZipFile

from testpath import assert_isfile

Expand All @@ -10,3 +11,21 @@ def test_licenses_dir(tmp_path):
# Smoketest for https://github.com/takluyver/flit/issues/399
info = make_wheel_in(samples_dir / 'inclusion' / 'pyproject.toml', tmp_path)
assert_isfile(info.file)


def test_source_date_epoch(tmp_path, monkeypatch):
monkeypatch.setenv('SOURCE_DATE_EPOCH', '1633007882')
info = make_wheel_in(samples_dir / 'pep621' / 'pyproject.toml', tmp_path)
assert_isfile(info.file)
# Minimum value for zip timestamps is 1980-1-1
with ZipFile(info.file, 'r') as zf:
assert zf.getinfo('module1a.py').date_time[:3] == (2021, 9, 30)


def test_zero_timestamp(tmp_path, monkeypatch):
monkeypatch.setenv('SOURCE_DATE_EPOCH', '0')
info = make_wheel_in(samples_dir / 'pep621' / 'pyproject.toml', tmp_path)
assert_isfile(info.file)
# Minimum value for zip timestamps is 1980-1-1
with ZipFile(info.file, 'r') as zf:
assert zf.getinfo('module1a.py').date_time == (1980, 1, 1, 0, 0, 0)
36 changes: 23 additions & 13 deletions flit_core/flit_core/wheel.py
Expand Up @@ -2,15 +2,14 @@
import contextlib
from datetime import datetime
import hashlib
from glob import glob
import io
import logging
import os
import os.path as osp
import stat
import sys
import tempfile
from types import SimpleNamespace
from typing import Optional
import zipfile

from flit_core import __version__
Expand All @@ -36,6 +35,27 @@ def _set_zinfo_mode(zinfo, mode):
zinfo.external_attr = mode << 16


def zip_timestamp_from_env() -> Optional[tuple]:
"""Prepare a timestamp from $SOURCE_DATE_EPOCH, if set"""
try:
# If SOURCE_DATE_EPOCH is set (e.g. by Debian), it's used for
# timestamps inside the zip file.
d = datetime.utcfromtimestamp(int(os.environ['SOURCE_DATE_EPOCH']))
except (KeyError, ValueError):
# Otherwise, we'll use the mtime of files, and generated files will
# default to 2016-1-1 00:00:00
return None

if d.year >= 1980:
log.info("Zip timestamps will be from SOURCE_DATE_EPOCH: %s", d)
# zipfile expects a 6-tuple, not a datetime object
return d.year, d.month, d.day, d.hour, d.minute, d.second
else:
log.info("SOURCE_DATE_EPOCH is below the minimum for zip file timestamps")
log.info("Zip timestamps will be 1980-01-01 00:00:00")
return 1980, 1, 1, 0, 0, 0


class WheelBuilder:
def __init__(self, directory, module, metadata, entrypoints, target_fp):
"""Build a wheel from a module/package
Expand All @@ -46,17 +66,7 @@ def __init__(self, directory, module, metadata, entrypoints, target_fp):
self.entrypoints = entrypoints

self.records = []
try:
# If SOURCE_DATE_EPOCH is set (e.g. by Debian), it's used for
# timestamps inside the zip file.
d = datetime.utcfromtimestamp(int(os.environ['SOURCE_DATE_EPOCH']))
log.info("Zip timestamps will be from SOURCE_DATE_EPOCH: %s", d)
# zipfile expects a 6-tuple, not a datetime object
self.source_time_stamp = (d.year, d.month, d.day, d.hour, d.minute, d.second)
except (KeyError, ValueError):
# Otherwise, we'll use the mtime of files, and generated files will
# default to 2016-1-1 00:00:00
self.source_time_stamp = None
self.source_time_stamp = zip_timestamp_from_env()

# Open the zip file ready to write
self.wheel_zip = zipfile.ZipFile(target_fp, 'w',
Expand Down