Skip to content

Commit 9ac7f0a

Browse files
committed
ENH: add support for symbolic links in package repository
Symbolic links are included as regular files in the sdist archive. Only symbolic links pointing to files withing the archive are supported.
1 parent 1c8092d commit 9ac7f0a

File tree

10 files changed

+80
-0
lines changed

10 files changed

+80
-0
lines changed

mesonpy/__init__.py

Lines changed: 25 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
@@ -23,6 +24,7 @@
2324
import os
2425
import pathlib
2526
import platform
27+
import posixpath
2628
import re
2729
import shutil
2830
import subprocess
@@ -937,6 +939,29 @@ def sdist(self, directory: Path) -> pathlib.Path:
937939

938940
with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist_path) as sdist:
939941
for member in meson_dist.getmembers():
942+
# Recursively resolve symbolic links. The source distribution
943+
# archive format specification allows for symbolic links as
944+
# long as the target path does not include a '..' component.
945+
# This makes symbolic links support unusable in most cases,
946+
# therefore include the symbolic link targets as regular files
947+
# in all cases.
948+
while member.issym():
949+
name = member.name
950+
target = posixpath.normpath(posixpath.join(posixpath.dirname(member.name), member.linkname))
951+
try:
952+
# This can be implemented using the .replace() method
953+
# in Python 3.12 and later. The .replace() method was
954+
# added as part of PEP 706 and back-ported to Python
955+
# 3.9 and later in patch releases, thus it cannot be
956+
# relied upon until the minimum supported Python
957+
# version is 3.12.
958+
member = copy.copy(meson_dist.getmember(target))
959+
member.name = name
960+
except KeyError:
961+
# Symbolic link with absolute path target, pointing
962+
# outside the archive, or dangling: ignore.
963+
break
964+
940965
if member.isfile():
941966
file = meson_dist.extractfile(member.name)
942967

tests/packages/symlinks/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# SPDX-FileCopyrightText: 2025 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT

tests/packages/symlinks/baz.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/__init__.py

tests/packages/symlinks/meson.build

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-FileCopyrightText: 2025 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+
py.install_sources(
10+
'__init__.py',
11+
'submodule/__init__.py',
12+
'submodule/aaa.py',
13+
'submodule/bbb.py',
14+
subdir: 'symlinks',
15+
preserve_path: true,
16+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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']

tests/packages/symlinks/qux.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../__init__.py
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../__init__.py
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# SPDX-FileCopyrightText: 2025 The meson-python developers
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
def foo():
6+
return 42
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
aaa.py

tests/test_sdist.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,22 @@ def test_reproducible(package_pure, tmp_path):
216216

217217
assert sdist_path_a == sdist_path_b
218218
assert tmp_path.joinpath('a', sdist_path_a).read_bytes() == tmp_path.joinpath('b', sdist_path_b).read_bytes()
219+
220+
221+
def test_symlinks(sdist_symlinks):
222+
with tarfile.open(sdist_symlinks, 'r:gz') as sdist:
223+
names = {member.name for member in sdist.getmembers()}
224+
mtimes = {member.mtime for member in sdist.getmembers()}
225+
226+
assert names == {
227+
'symlinks-1.0.0/PKG-INFO',
228+
'symlinks-1.0.0/meson.build',
229+
'symlinks-1.0.0/pyproject.toml',
230+
'symlinks-1.0.0/__init__.py',
231+
'symlinks-1.0.0/submodule/__init__.py',
232+
'symlinks-1.0.0/submodule/aaa.py',
233+
'symlinks-1.0.0/submodule/bbb.py',
234+
}
235+
236+
# All the archive members have a valid mtime.
237+
assert 0 not in mtimes

0 commit comments

Comments
 (0)