Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,24 +1063,31 @@ def sdist(self, directory: Path) -> pathlib.Path:

with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist_path) as sdist:
for member in meson_dist.getmembers():
# Record the original member name. The symbolic link
# resolution loop will make ``member`` point to the link
# target, but it needs to be archived under the original name.
# Symbolic link target resolution must be relative to
# ``member.name``, which therefore cannot be updated in the
# symbolic link resolution loop.
name = member.name

# Recursively resolve symbolic links. The source distribution
# archive format specification allows for symbolic links as
# long as the target path does not include a '..' component.
# This makes symbolic links support unusable in most cases,
# therefore include the symbolic link targets as regular files
# in all cases.
visited = set()
while member.issym():
name = member.name
# Detect symbolic link chains resulting in a cycle.
if member.name in visited:
warnings.warn(
f'symbolic link resulting in a cycle ignored: {name}', stacklevel=1)
break
visited.add(member.name)
target = posixpath.normpath(posixpath.join(posixpath.dirname(member.name), member.linkname))
try:
# This can be implemented using the .replace() method
# in Python 3.12 and later. The .replace() method was
# added as part of PEP 706 and back-ported to Python
# 3.9 and later in patch releases, thus it cannot be
# relied upon until the minimum supported Python
# version is 3.12.
member = copy.copy(meson_dist.getmember(target))
member.name = name
member = meson_dist.getmember(target)
except KeyError:
warnings.warn(
'symbolic link with absolute path target, pointing outside the '
Expand All @@ -1089,13 +1096,14 @@ def sdist(self, directory: Path) -> pathlib.Path:
if member.isdir():
warnings.warn(
f'symbolic link pointing to a directory ignored: {name}', stacklevel=1)

# Copy `member` before starting to modify it
member = copy.copy(member)
break

if member.isfile():
file = meson_dist.extractfile(member.name)

# Copy ``member`` before starting to modify it
member = copy.copy(member)

# Reset pax extended header. The tar archive member may be
# using pax headers to store some file metadata. The pax
# headers are not reset when the metadata is modified and
Expand All @@ -1113,7 +1121,7 @@ def sdist(self, directory: Path) -> pathlib.Path:
member.pax_headers = {}

# Rewrite the path to match the sdist distribution name.
stem = member.name.split('/', 1)[1]
stem = name.split('/', 1)[1]
member.name = '/'.join((dist_name, stem))

if stem == 'pyproject.toml':
Expand Down
1 change: 1 addition & 0 deletions tests/packages/symlinks/ccc.py
1 change: 1 addition & 0 deletions tests/packages/symlinks/cycle
1 change: 1 addition & 0 deletions tests/packages/symlinks/foo
1 change: 1 addition & 0 deletions tests/test_sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ def test_symlinks(tmp_path, sdist_symlinks):
'symlinks-1.0.0/meson.build',
'symlinks-1.0.0/pyproject.toml',
'symlinks-1.0.0/__init__.py',
'symlinks-1.0.0/ccc.py',
'symlinks-1.0.0/submodule/__init__.py',
'symlinks-1.0.0/submodule/aaa.py',
'symlinks-1.0.0/submodule/bbb.py',
Expand Down
Loading