Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve symlink contents in sdists #713

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import argparse
import collections
import contextlib
import copy
import difflib
import functools
import importlib.machinery
Expand Down Expand Up @@ -854,6 +855,17 @@ 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():
# Wheels do not support links, though sdist tarballs could. For portability, reduce these to regular files.
if member.islnk() or member.issym():
# Symlinks are relative to member directory, but hard links are relative to tarball root.
path = member.name.rsplit('/', 1)[0] + '/' if member.issym() else ''
orig = meson_dist.getmember(path + member.linkname)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are meson or git enforcing that symlinks do not point to location outside the tarball or outside the repository?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git does not appear to enforce any rules about committed symlinks.

member = copy.copy(member)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the copy here necessary?

member.mode = orig.mode
member.mtime = orig.mtime
member.size = orig.size
member.type = tarfile.REGTYPE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't the link point to a directory, or to a special file?


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

Expand Down
12 changes: 12 additions & 0 deletions tests/packages/symlinks/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024 The meson-python developers
#
# SPDX-License-Identifier: MIT

project('symlinks', version: '1.0.0')

py = import('python').find_installation()

install_subdir(
'subdir',
install_dir: py.get_install_dir(pure: false),
)
7 changes: 7 additions & 0 deletions tests/packages/symlinks/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2024 The meson-python developers
#
# SPDX-License-Identifier: MIT

[build-system]
build-backend = 'mesonpy'
requires = ['meson-python']
3 changes: 3 additions & 0 deletions tests/packages/symlinks/subdir/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2024 The meson-python developers
#
# SPDX-License-Identifier: MIT
1 change: 1 addition & 0 deletions tests/packages/symlinks/subdir/symlink.py
3 changes: 3 additions & 0 deletions tests/packages/symlinks/subdir/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2024 The meson-python developers
#
# SPDX-License-Identifier: MIT
27 changes: 27 additions & 0 deletions tests/test_sdist.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,33 @@ def test_contents_subdirs(sdist_subdirs):
assert 0 not in mtimes


def test_contents_symlinks(sdist_symlinks):
with tarfile.open(sdist_symlinks, 'r:gz') as sdist:
names = {member.name for member in sdist.getmembers()}
mtimes = {member.mtime for member in sdist.getmembers()}

orig_info = sdist.getmember('symlinks-1.0.0/subdir/test.py')
symlink_info = sdist.getmember('symlinks-1.0.0/subdir/symlink.py')
assert orig_info.mode == symlink_info.mode
assert orig_info.mtime == symlink_info.mtime
assert orig_info.size == symlink_info.size
orig = sdist.extractfile('symlinks-1.0.0/subdir/test.py')
symlink = sdist.extractfile('symlinks-1.0.0/subdir/symlink.py')
assert orig.read() == symlink.read()

assert names == {
'symlinks-1.0.0/PKG-INFO',
'symlinks-1.0.0/meson.build',
'symlinks-1.0.0/pyproject.toml',
'symlinks-1.0.0/subdir/__init__.py',
'symlinks-1.0.0/subdir/test.py',
'symlinks-1.0.0/subdir/symlink.py',
}

# All the archive members have a valid mtime.
assert 0 not in mtimes


def test_contents_unstaged(package_pure, tmp_path):
new = textwrap.dedent('''
def bar():
Expand Down
17 changes: 17 additions & 0 deletions tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,23 @@ def test_install_subdir(wheel_install_subdir):
}


def test_install_symlink(wheel_symlinks):
artifact = wheel.wheelfile.WheelFile(wheel_symlinks)
# Handling of the exclude_files and exclude_directories requires
# Meson 1.1.0, see https://github.com/mesonbuild/meson/pull/11432.
# Run the test anyway to ensure that meson-python can produce a
# wheel also for older versions of Meson.
if MESON_VERSION >= (1, 1, 99):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't use exlude_files or exclude_directories in your test, why this version check?

assert wheel_contents(artifact) == {
'symlinks-1.0.0.dist-info/METADATA',
'symlinks-1.0.0.dist-info/RECORD',
'symlinks-1.0.0.dist-info/WHEEL',
'subdir/__init__.py',
'subdir/test.py',
'subdir/symlink.py',
}


def test_vendored_meson(wheel_vendored_meson):
# This test will error if the vendored meson.py wrapper script in
# the test package isn't used.
Expand Down
Loading