Skip to content

Commit 17b9cac

Browse files
committed
ENH: do not include uncommitted changes in the sdist
Including uncommitted changes in the sdist was implemented in mesonbuild#58 after the discussion in mesonbuild#53. However, the current behavior for which uncommitted changes to files under version control are included but not other files, is a hard to justify surprising half measure. After careful analysis, it has been determined that none of the use cases for this feature is still valid. Removing it makes the implementation easier, and the behavior less surprising and easier to document and explain. While at it, modernize the associated test.
1 parent 36e6392 commit 17b9cac

File tree

4 files changed

+85
-63
lines changed

4 files changed

+85
-63
lines changed

mesonpy/__init__.py

+40-47
Original file line numberDiff line numberDiff line change
@@ -865,64 +865,57 @@ def _meson_version(self) -> str:
865865

866866
def sdist(self, directory: Path) -> pathlib.Path:
867867
"""Generates a sdist (source distribution) in the specified directory."""
868-
# generate meson dist file
868+
# Generate meson dist file.
869869
self._run(self._meson + ['dist', '--allow-dirty', '--no-tests', '--formats', 'gztar', *self._meson_args['dist']])
870870

871-
# move meson dist file to output path
872871
dist_name = f'{self._metadata.distribution_name}-{self._metadata.version}'
873872
meson_dist_name = f'{self._meson_name}-{self._meson_version}'
874873
meson_dist_path = pathlib.Path(self._build_dir, 'meson-dist', f'{meson_dist_name}.tar.gz')
875-
sdist = pathlib.Path(directory, f'{dist_name}.tar.gz')
874+
sdist_path = pathlib.Path(directory, f'{dist_name}.tar.gz')
876875

877-
with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist) as tar:
876+
with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist_path) as sdist:
878877
for member in meson_dist.getmembers():
879-
# calculate the file path in the source directory
880-
assert member.name, member.name
881-
member_parts = member.name.split('/')
882-
if len(member_parts) <= 1:
883-
continue
884-
path = self._source_dir.joinpath(*member_parts[1:])
885-
886-
if not path.exists() and member.isfile():
887-
# File doesn't exists on the source directory but exists on
888-
# the Meson dist, so it is generated file, which we need to
889-
# include.
890-
# See https://mesonbuild.com/Reference-manual_builtin_meson.html#mesonadd_dist_script
891-
892-
# MESON_DIST_ROOT could have a different base name
893-
# than the actual sdist basename, so we need to rename here
878+
if member.isfile():
894879
file = meson_dist.extractfile(member.name)
895-
member.name = str(pathlib.Path(dist_name, *member_parts[1:]).as_posix())
896-
tar.addfile(member, file)
897-
continue
898-
899-
if not path.is_file():
900-
continue
901880

902-
info = tarfile.TarInfo(member.name)
903-
file_stat = os.stat(path)
904-
info.mtime = member.mtime
905-
info.size = file_stat.st_size
906-
info.mode = int(oct(file_stat.st_mode)[-3:], 8)
907-
908-
# rewrite the path if necessary, to match the sdist distribution name
909-
if dist_name != meson_dist_name:
910-
info.name = pathlib.Path(
911-
dist_name,
912-
path.relative_to(self._source_dir)
913-
).as_posix()
914-
915-
with path.open('rb') as f:
916-
tar.addfile(info, fileobj=f)
917-
918-
# add PKG-INFO to dist file to make it a sdist
919-
pkginfo_info = tarfile.TarInfo(f'{dist_name}/PKG-INFO')
920-
pkginfo_info.mtime = time.time() # type: ignore[assignment]
881+
# Reset pax extended header. The tar archive member may be
882+
# using pax headers to store some file metadata. The pax
883+
# headers are not reset when the metadata is modified and
884+
# they take precedence when the member is deserialized.
885+
# This is relevant because when rewriting the member name,
886+
# the length of the path may shrink from being more than
887+
# 100 characters (requiring the path to be stored in the
888+
# pax headers) to being less than 100 characters. When this
889+
# happens, the tar archive member is serialized with the
890+
# shorter name in the regular header and the longer one in
891+
# the extended pax header. The archives handled here are
892+
# not expected to use extended pax headers other than for
893+
# the ones required to encode file metadata. The easiest
894+
# solution is to reset the pax extended headers.
895+
member.pax_headers = {}
896+
897+
# Rewrite the path to match the sdist distribution name.
898+
stem = member.name.split('/', 1)[1]
899+
member.name = '/'.join((dist_name, stem))
900+
901+
# Reset owner and group to root:root. This mimics what
902+
# 'git archive' does and makes the sdist reproducible upon
903+
# being built by different users.
904+
member.uname = member.gname = 'root'
905+
member.uid = member.gid = 0
906+
907+
sdist.addfile(member, file)
908+
909+
# Add 'PKG-INFO'.
910+
member = tarfile.TarInfo(f'{dist_name}/PKG-INFO')
911+
member.uid = member.gid = 0
912+
member.uname = member.gname = 'root'
913+
member.mtime = time.time()
921914
metadata = bytes(self._metadata.as_rfc822())
922-
pkginfo_info.size = len(metadata)
923-
tar.addfile(pkginfo_info, fileobj=io.BytesIO(metadata))
915+
member.size = len(metadata)
916+
sdist.addfile(member, io.BytesIO(metadata))
924917

925-
return sdist
918+
return sdist_path
926919

927920
def wheel(self, directory: Path) -> pathlib.Path:
928921
"""Generates a wheel in the specified directory."""

tests/packages/long-path/meson.build

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-FileCopyrightText: 2024 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
project('very-long-project-name-that-makes-the-paths-within-the-sdist-exceed-100-characters-xxxxxxxxxxxxxxxxx', version: '1.0.0')
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: 2021 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
[build-system]
6+
build-backend = 'mesonpy'
7+
requires = ['meson-python']
8+
9+
[project]
10+
name = 'long-path'
11+
dynamic = ['version']

tests/test_sdist.py

+29-16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# SPDX-License-Identifier: MIT
44

55
import os
6+
import pathlib
67
import re
78
import stat
89
import sys
@@ -122,37 +123,35 @@ def test_contents_subdirs(sdist_subdirs):
122123

123124

124125
def test_contents_unstaged(package_pure, tmp_path):
125-
new_data = textwrap.dedent('''
126-
def bar():
127-
return 'foo'
126+
new = textwrap.dedent('''
127+
def bar():
128+
return 'foo'
128129
''').strip()
129130

130-
with open('pure.py', 'r') as f:
131-
old_data = f.read()
132-
133-
try:
134-
with in_git_repo_context():
135-
with open('pure.py', 'w') as f, open('crap', 'x'):
136-
f.write(new_data)
131+
old = pathlib.Path('pure.py').read_text()
137132

133+
with in_git_repo_context():
134+
try:
135+
pathlib.Path('pure.py').write_text(new)
136+
pathlib.Path('other.py').touch()
138137
sdist_path = mesonpy.build_sdist(os.fspath(tmp_path))
139-
finally:
140-
with open('pure.py', 'w') as f:
141-
f.write(old_data)
142-
os.unlink('crap')
138+
finally:
139+
pathlib.Path('pure.py').write_text(old)
140+
pathlib.Path('other.py').unlink()
143141

144142
with tarfile.open(tmp_path / sdist_path, 'r:gz') as sdist:
145143
names = {member.name for member in sdist.getmembers()}
146144
mtimes = {member.mtime for member in sdist.getmembers()}
147-
read_data = sdist.extractfile('pure-1.0.0/pure.py').read().replace(b'\r\n', b'\n')
145+
data = sdist.extractfile('pure-1.0.0/pure.py').read().replace(b'\r\n', b'\n')
148146

147+
# Verify that uncommitted changes are not included in the sdist.
149148
assert names == {
150149
'pure-1.0.0/PKG-INFO',
151150
'pure-1.0.0/meson.build',
152151
'pure-1.0.0/pure.py',
153152
'pure-1.0.0/pyproject.toml',
154153
}
155-
assert read_data == new_data.encode()
154+
assert data == old.encode()
156155

157156
# All the archive members have a valid mtime.
158157
assert 0 not in mtimes
@@ -192,3 +191,17 @@ def test_generated_files(sdist_generated_files):
192191

193192
# All the archive members have a valid mtime.
194193
assert 0 not in mtimes
194+
195+
196+
def test_long_path(sdist_long_path):
197+
# See https://github.com/mesonbuild/meson-python/pull/587#pullrequestreview-2020891328
198+
# and https://github.com/mesonbuild/meson-python/pull/587#issuecomment-2075973593
199+
200+
with tarfile.open(sdist_long_path, 'r:gz') as sdist:
201+
names = {member.name for member in sdist.getmembers()}
202+
203+
assert names == {
204+
'long_path-1.0.0/PKG-INFO',
205+
'long_path-1.0.0/meson.build',
206+
'long_path-1.0.0/pyproject.toml'
207+
}

0 commit comments

Comments
 (0)