Skip to content

Commit 92518cd

Browse files
committed
mesonpy: include uncommited changes in sdist
Fixes #53 Signed-off-by: Filipe Laíns <[email protected]>
1 parent 931a06f commit 92518cd

File tree

10 files changed

+123
-44
lines changed

10 files changed

+123
-44
lines changed

mesonpy/__init__.py

+35-12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import collections
1414
import contextlib
1515
import functools
16+
import io
1617
import itertools
1718
import json
1819
import os
@@ -23,6 +24,7 @@
2324
import subprocess
2425
import sys
2526
import sysconfig
27+
import tarfile
2628
import tempfile
2729
import textwrap
2830
import typing
@@ -538,7 +540,8 @@ def version(self) -> str:
538540
assert isinstance(version, str)
539541
return version
540542

541-
@property
543+
@property # type: ignore[misc]
544+
@functools.lru_cache(maxsize=1)
542545
def metadata(self) -> bytes: # noqa: C901
543546
"""Project metadata."""
544547
# the rest of the keys are only available when using PEP 621 metadata
@@ -718,26 +721,46 @@ def _select_abi_tag(self) -> Optional[mesonpy._tags.Tag]: # noqa: C901
718721
def sdist(self, directory: Path) -> pathlib.Path:
719722
"""Generates a sdist (source distribution) in the specified directory."""
720723
# generate meson dist file
721-
self._meson('dist', '--no-tests', '--formats', 'gztar')
724+
self._meson('dist', '--allow-dirty', '--no-tests', '--formats', 'gztar')
722725

723726
# move meson dist file to output path
724727
dist_name = f'{self.name}-{self.version}'
725728
meson_dist_name = f'{self._meson_name}-{self._meson_version}'
726-
meson_dist = pathlib.Path(self._build_dir, 'meson-dist', f'{meson_dist_name}.tar.gz')
729+
meson_dist_path = pathlib.Path(self._build_dir, 'meson-dist', f'{meson_dist_name}.tar.gz')
727730
sdist = pathlib.Path(directory, f'{dist_name}.tar.gz')
728731

729-
with mesonpy._util.edit_targz(meson_dist, sdist) as content:
730-
# rename from meson name to sdist name if necessary
731-
if dist_name != meson_dist_name:
732-
shutil.move(str(content / meson_dist_name), str(content / dist_name))
732+
with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist) as (tar, mtime):
733+
for member in meson_dist.getmembers():
734+
# skip the generated meson native file
735+
if member.name == f'{meson_dist_name}/.mesonpy-native-file.ini':
736+
continue
737+
738+
# calculate the file path in the source directory
739+
assert member.name, member.name
740+
member_parts = member.name.split('/')
741+
if len(member_parts) <= 1:
742+
continue
743+
path = self._source_dir.joinpath(*member_parts[1:])
744+
745+
if not path.is_file():
746+
continue
747+
748+
# rewrite the path if necessary, to match the sdist distribution name
749+
if dist_name != meson_dist_name:
750+
member.name = path.relative_to(self._source_dir).as_posix()
751+
752+
# rewrite the size
753+
member.size = os.path.getsize(path)
733754

734-
# remove .mesonpy-native-file.ini if it exists
735-
native_file = content / meson_dist_name / '.mesonpy-native-file.ini'
736-
if native_file.exists():
737-
native_file.unlink()
755+
with path.open('rb') as f:
756+
tar.addfile(member, fileobj=f)
738757

739758
# add PKG-INFO to dist file to make it a sdist
740-
content.joinpath(dist_name, 'PKG-INFO').write_bytes(self.metadata)
759+
pkginfo_info = tarfile.TarInfo(f'{dist_name}/PKG-INFO')
760+
if mtime:
761+
pkginfo_info.mtime = mtime
762+
pkginfo_info.size = len(self.metadata) # type: ignore[arg-type]
763+
tar.addfile(pkginfo_info, fileobj=io.BytesIO(self.metadata)) # type: ignore[arg-type]
741764

742765
return sdist
743766

mesonpy/_util.py

+20-31
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
import contextlib
66
import gzip
77
import os
8-
import pathlib
98
import sys
109
import tarfile
11-
import tempfile
1210
import typing
1311

14-
from typing import IO
12+
from typing import IO, Optional, Tuple
1513

1614
from mesonpy._compat import Iterable, Iterator, Path
1715

@@ -41,35 +39,26 @@ def add_ld_path(paths: Iterable[str]) -> Iterator[None]:
4139

4240

4341
@contextlib.contextmanager
44-
def edit_targz(path: Path, new_path: Path) -> Iterator[pathlib.Path]:
42+
def create_targz(path: Path) -> Iterator[Tuple[tarfile.TarFile, Optional[int]]]:
4543
"""Opens a .tar.gz file in the file system for edition.."""
46-
with tempfile.TemporaryDirectory(prefix='mesonpy-') as tmpdir:
47-
workdir = pathlib.Path(tmpdir)
48-
with tarfile.open(path, 'r:gz') as tar:
49-
tar.extractall(tmpdir)
50-
51-
yield workdir
52-
53-
# reproducibility
54-
source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH')
55-
mtime = int(source_date_epoch) if source_date_epoch else None
56-
57-
file = typing.cast(IO[bytes], gzip.GzipFile(
58-
os.path.join(path, new_path),
59-
mode='wb',
60-
mtime=mtime,
61-
))
62-
with contextlib.closing(file), tarfile.TarFile(
63-
mode='w',
64-
fileobj=file,
65-
format=tarfile.PAX_FORMAT, # changed in 3.8 to GNU
66-
) as tar:
67-
for path in workdir.rglob('*'):
68-
if path.is_file():
69-
tar.add(
70-
name=path,
71-
arcname=path.relative_to(workdir).as_posix(),
72-
)
44+
45+
# reproducibility
46+
source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH')
47+
mtime = int(source_date_epoch) if source_date_epoch else None
48+
49+
file = typing.cast(IO[bytes], gzip.GzipFile(
50+
os.path.join(path, path),
51+
mode='wb',
52+
mtime=mtime,
53+
))
54+
tar = tarfile.TarFile(
55+
mode='w',
56+
fileobj=file,
57+
format=tarfile.PAX_FORMAT, # changed in 3.8 to GNU
58+
)
59+
60+
with contextlib.closing(file), tar:
61+
yield tar, mtime
7362

7463

7564
class CLICounter:

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
build-backend = 'mesonpy'
33
backend-path = ['.']
44
requires = [
5-
'meson>=0.60.0',
5+
'meson>=0.62.0',
66
'ninja',
77
'pep621>=0.3.0',
88
'tomli>=1.0.0',

tests/packages/subdirs/meson.build

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
project(
2+
'subdirs',
3+
version: '1.0.0',
4+
)
5+
6+
py_mod = import('python')
7+
py = py_mod.find_installation()
8+
9+
py.install_sources([
10+
'subdirs' / '__init__.py',
11+
'subdirs' / 'a' / '__init__.py',
12+
'subdirs' / 'a' / 'b' / 'c.py',
13+
'subdirs' / 'b' / 'c.py',
14+
])

tests/packages/subdirs/pyproject.toml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
build-backend = 'mesonpy'
3+
requires = ['meson-python']

tests/packages/subdirs/subdirs/__init__.py

Whitespace-only changes.

tests/packages/subdirs/subdirs/a/__init__.py

Whitespace-only changes.

tests/packages/subdirs/subdirs/a/b/c.py

Whitespace-only changes.

tests/packages/subdirs/subdirs/b/c.py

Whitespace-only changes.

tests/test_sdist.py

+50
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# SPDX-License-Identifier: EUPL-1.2
22

3+
import os
34
import tarfile
5+
import textwrap
6+
7+
import mesonpy
8+
9+
from .conftest import in_git_repo_context
410

511

612
def test_contents(sdist_library):
@@ -14,3 +20,47 @@ def test_contents(sdist_library):
1420
'library-1.0.0/pyproject.toml',
1521
'library-1.0.0/PKG-INFO',
1622
}
23+
24+
25+
def test_contents_subdirs(sdist_subdirs):
26+
sdist = tarfile.open(sdist_subdirs, 'r:gz')
27+
28+
assert set(sdist.getnames()) == {
29+
'subdirs-1.0.0/PKG-INFO',
30+
'subdirs-1.0.0/meson.build',
31+
'subdirs-1.0.0/pyproject.toml',
32+
'subdirs-1.0.0/subdirs/__init__.py',
33+
'subdirs-1.0.0/subdirs/a/__init__.py',
34+
'subdirs-1.0.0/subdirs/a/b/c.py',
35+
'subdirs-1.0.0/subdirs/b/c.py',
36+
}
37+
38+
39+
def test_contents_unstaged(package_pure, tmpdir):
40+
new_data = textwrap.dedent('''
41+
def bar():
42+
return 'foo'
43+
''').strip()
44+
45+
with open('pure.py', 'r') as f:
46+
old_data = f.read()
47+
48+
try:
49+
with in_git_repo_context(), open('pure.py', 'w') as f, open('crap', 'x'):
50+
f.write(new_data)
51+
52+
sdist_path = mesonpy.build_sdist(os.fspath(tmpdir))
53+
finally:
54+
with open('pure.py', 'w') as f:
55+
f.write(old_data)
56+
os.unlink('crap')
57+
58+
sdist = tarfile.open(tmpdir / sdist_path, 'r:gz')
59+
60+
assert set(sdist.getnames()) == {
61+
'pure-1.0.0/PKG-INFO',
62+
'pure-1.0.0/meson.build',
63+
'pure-1.0.0/pure.py',
64+
'pure-1.0.0/pyproject.toml',
65+
}
66+
assert sdist.extractfile('pure-1.0.0/pure.py').read() == new_data.encode()

0 commit comments

Comments
 (0)