Skip to content

Commit ef27b23

Browse files
committed
ENH: make sdist archives reproducible
1 parent 3334d6c commit ef27b23

File tree

3 files changed

+39
-3
lines changed

3 files changed

+39
-3
lines changed

mesonpy/__init__.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import tarfile
3232
import tempfile
3333
import textwrap
34-
import time
3534
import typing
3635
import warnings
3736

@@ -867,6 +866,7 @@ def sdist(self, directory: Path) -> pathlib.Path:
867866
meson_dist_name = f'{self._meson_name}-{self._meson_version}'
868867
meson_dist_path = pathlib.Path(self._build_dir, 'meson-dist', f'{meson_dist_name}.tar.gz')
869868
sdist_path = pathlib.Path(directory, f'{dist_name}.tar.gz')
869+
pyproject_toml_mtime = 0
870870

871871
with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist_path) as sdist:
872872
for member in meson_dist.getmembers():
@@ -877,6 +877,9 @@ def sdist(self, directory: Path) -> pathlib.Path:
877877
stem = member.name.split('/', 1)[1]
878878
member.name = '/'.join((dist_name, stem))
879879

880+
if stem == 'pyproject.toml':
881+
pyproject_toml_mtime = member.mtime
882+
880883
# Reset owner and group to root:root. This mimics what
881884
# 'git archive' does and makes the sdist reproducible upon
882885
# being built by different users.
@@ -889,7 +892,23 @@ def sdist(self, directory: Path) -> pathlib.Path:
889892
member = tarfile.TarInfo(f'{dist_name}/PKG-INFO')
890893
member.uid = member.gid = 0
891894
member.uname = member.gname = 'root'
892-
member.mtime = time.time()
895+
896+
# Set the 'PKG-INFO' modification time to the modification time of
897+
# 'pyproject.toml' in the archive generated by 'meson dist'. In
898+
# turn this is the last commit time, unless touched by a dist
899+
# script. This makes the sdist reproducible upon being built at
900+
# different times, when dist scripts are not used, which should be
901+
# the majority of cases.
902+
#
903+
# Note that support for dynamic version in project metadata allows
904+
# the version to depend on the build time. Therefore, setting the
905+
# 'PKG-INFO' modification time to the 'pyproject.toml'
906+
# modification time can be seen as not strictly correct. However,
907+
# the sdist standard does not dictate which modification time to
908+
# use for 'PKG-INFO'. This choice allows to make the sdist
909+
# byte-for-byte reproducible in the most common case.
910+
member.mtime = pyproject_toml_mtime
911+
893912
metadata = bytes(self._metadata.as_rfc822())
894913
member.size = len(metadata)
895914
sdist.addfile(member, io.BytesIO(metadata))

mesonpy/_util.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ def create_targz(path: Path) -> Iterator[tarfile.TarFile]:
3737
os.makedirs(os.path.dirname(path), exist_ok=True)
3838
file = typing.cast(IO[bytes], gzip.GzipFile(
3939
path,
40-
mode='wb',
40+
mode='w',
41+
# Set the stream last modification time to 0. This mimics
42+
# what 'git archive' does and makes the archives byte-for-byte
43+
# reproducible.
44+
mtime=0,
4145
))
4246
tar = tarfile.TarFile(
4347
mode='w',

tests/test_sdist.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import sys
1010
import tarfile
1111
import textwrap
12+
import time
1213

1314
import pytest
1415

@@ -191,3 +192,15 @@ def test_generated_files(sdist_generated_files):
191192

192193
# All the archive members have a valid mtime.
193194
assert 0 not in mtimes
195+
196+
197+
def test_reproducible(package_pure, tmp_path):
198+
t1 = time.time()
199+
sdist_path_a = mesonpy.build_sdist(tmp_path / 'a')
200+
t2 = time.time()
201+
# Ensure that the two sdists are build at least one second apart.
202+
time.sleep(max(t1 + 1.0 - t2, 0.0))
203+
sdist_path_b = mesonpy.build_sdist(tmp_path / 'b')
204+
205+
assert sdist_path_a == sdist_path_b
206+
assert tmp_path.joinpath('a', sdist_path_a).read_bytes() == tmp_path.joinpath('b', sdist_path_b).read_bytes()

0 commit comments

Comments
 (0)