Skip to content

Commit 35d3a09

Browse files
committed
Preserve symlink contents in sdists
Wheels do not support symlinks (as they are ZIP files), but tarballs do. For consistent results however, expand those symlinks to the files they reference. This is consistent with 0.16.0, and fixes this regression in 0.17.0.
1 parent 9faca83 commit 35d3a09

File tree

8 files changed

+82
-0
lines changed

8 files changed

+82
-0
lines changed

mesonpy/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import argparse
1515
import collections
1616
import contextlib
17+
import copy
1718
import difflib
1819
import functools
1920
import importlib.machinery
@@ -854,6 +855,17 @@ def sdist(self, directory: Path) -> pathlib.Path:
854855

855856
with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist_path) as sdist:
856857
for member in meson_dist.getmembers():
858+
# Wheels do not support links, though sdist tarballs could. For portability, reduce these to regular files.
859+
if member.islnk() or member.issym():
860+
# Symlinks are relative to member directory, but hard links are relative to tarball root.
861+
path = member.name.rsplit('/', 1)[0] + '/' if member.issym() else ''
862+
orig = meson_dist.getmember(path + member.linkname)
863+
member = copy.copy(member)
864+
member.mode = orig.mode
865+
member.mtime = orig.mtime
866+
member.size = orig.size
867+
member.type = tarfile.REGTYPE
868+
857869
if member.isfile():
858870
file = meson_dist.extractfile(member.name)
859871

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# SPDX-FileCopyrightText: 2024 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
project('symlinks', version: '1.0.0')
6+
7+
py = import('python').find_installation()
8+
9+
install_subdir(
10+
'subdir',
11+
install_dir: py.get_install_dir(pure: false),
12+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-FileCopyrightText: 2024 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
[build-system]
6+
build-backend = 'mesonpy'
7+
requires = ['meson-python']
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SPDX-FileCopyrightText: 2024 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test.py
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SPDX-FileCopyrightText: 2024 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT

tests/test_sdist.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,33 @@ def test_contents_subdirs(sdist_subdirs):
126126
assert 0 not in mtimes
127127

128128

129+
def test_contents_symlinks(sdist_symlinks):
130+
with tarfile.open(sdist_symlinks, 'r:gz') as sdist:
131+
names = {member.name for member in sdist.getmembers()}
132+
mtimes = {member.mtime for member in sdist.getmembers()}
133+
134+
orig_info = sdist.getmember('symlinks-1.0.0/subdir/test.py')
135+
symlink_info = sdist.getmember('symlinks-1.0.0/subdir/symlink.py')
136+
assert orig_info.mode == symlink_info.mode
137+
assert orig_info.mtime == symlink_info.mtime
138+
assert orig_info.size == symlink_info.size
139+
orig = sdist.extractfile('symlinks-1.0.0/subdir/test.py')
140+
symlink = sdist.extractfile('symlinks-1.0.0/subdir/symlink.py')
141+
assert orig.read() == symlink.read()
142+
143+
assert names == {
144+
'symlinks-1.0.0/PKG-INFO',
145+
'symlinks-1.0.0/meson.build',
146+
'symlinks-1.0.0/pyproject.toml',
147+
'symlinks-1.0.0/subdir/__init__.py',
148+
'symlinks-1.0.0/subdir/test.py',
149+
'symlinks-1.0.0/subdir/symlink.py',
150+
}
151+
152+
# All the archive members have a valid mtime.
153+
assert 0 not in mtimes
154+
155+
129156
def test_contents_unstaged(package_pure, tmp_path):
130157
new = textwrap.dedent('''
131158
def bar():

tests/test_wheel.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,23 @@ def test_install_subdir(wheel_install_subdir):
339339
}
340340

341341

342+
def test_install_symlink(wheel_symlinks):
343+
artifact = wheel.wheelfile.WheelFile(wheel_symlinks)
344+
# Handling of the exclude_files and exclude_directories requires
345+
# Meson 1.1.0, see https://github.com/mesonbuild/meson/pull/11432.
346+
# Run the test anyway to ensure that meson-python can produce a
347+
# wheel also for older versions of Meson.
348+
if MESON_VERSION >= (1, 1, 99):
349+
assert wheel_contents(artifact) == {
350+
'symlinks-1.0.0.dist-info/METADATA',
351+
'symlinks-1.0.0.dist-info/RECORD',
352+
'symlinks-1.0.0.dist-info/WHEEL',
353+
'subdir/__init__.py',
354+
'subdir/test.py',
355+
'subdir/symlink.py',
356+
}
357+
358+
342359
def test_vendored_meson(wheel_vendored_meson):
343360
# This test will error if the vendored meson.py wrapper script in
344361
# the test package isn't used.

0 commit comments

Comments
 (0)