From c5640592df2c06d76718d4821239a65eec6895de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Fri, 20 May 2022 19:23:34 +0100 Subject: [PATCH] mesonpy: include uncommited changes in sdist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #53 Signed-off-by: Filipe LaĆ­ns --- mesonpy/__init__.py | 47 +++++++++++++----- mesonpy/_util.py | 51 ++++++++------------ pyproject.toml | 2 +- tests/packages/subdirs/meson.build | 14 ++++++ tests/packages/subdirs/pyproject.toml | 3 ++ tests/packages/subdirs/subdirs/__init__.py | 0 tests/packages/subdirs/subdirs/a/__init__.py | 0 tests/packages/subdirs/subdirs/a/b/c.py | 0 tests/packages/subdirs/subdirs/b/c.py | 0 tests/test_sdist.py | 51 ++++++++++++++++++++ 10 files changed, 124 insertions(+), 44 deletions(-) create mode 100644 tests/packages/subdirs/meson.build create mode 100644 tests/packages/subdirs/pyproject.toml create mode 100644 tests/packages/subdirs/subdirs/__init__.py create mode 100644 tests/packages/subdirs/subdirs/a/__init__.py create mode 100644 tests/packages/subdirs/subdirs/a/b/c.py create mode 100644 tests/packages/subdirs/subdirs/b/c.py diff --git a/mesonpy/__init__.py b/mesonpy/__init__.py index 5f46b11aa..ffd69c1bb 100644 --- a/mesonpy/__init__.py +++ b/mesonpy/__init__.py @@ -13,6 +13,7 @@ import collections import contextlib import functools +import io import itertools import json import os @@ -23,6 +24,7 @@ import subprocess import sys import sysconfig +import tarfile import tempfile import textwrap import typing @@ -538,7 +540,8 @@ def version(self) -> str: assert isinstance(version, str) return version - @property + @property # type: ignore[misc] + @functools.lru_cache(maxsize=1) def metadata(self) -> bytes: # noqa: C901 """Project metadata.""" # 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 def sdist(self, directory: Path) -> pathlib.Path: """Generates a sdist (source distribution) in the specified directory.""" # generate meson dist file - self._meson('dist', '--no-tests', '--formats', 'gztar') + self._meson('dist', '--allow-dirty', '--no-tests', '--formats', 'gztar') # move meson dist file to output path dist_name = f'{self.name}-{self.version}' meson_dist_name = f'{self._meson_name}-{self._meson_version}' - meson_dist = pathlib.Path(self._build_dir, 'meson-dist', f'{meson_dist_name}.tar.gz') + meson_dist_path = pathlib.Path(self._build_dir, 'meson-dist', f'{meson_dist_name}.tar.gz') sdist = pathlib.Path(directory, f'{dist_name}.tar.gz') - with mesonpy._util.edit_targz(meson_dist, sdist) as content: - # rename from meson name to sdist name if necessary - if dist_name != meson_dist_name: - shutil.move(str(content / meson_dist_name), str(content / dist_name)) + with tarfile.open(meson_dist_path, 'r:gz') as meson_dist, mesonpy._util.create_targz(sdist) as (tar, mtime): + for member in meson_dist.getmembers(): + # skip the generated meson native file + if member.name == f'{meson_dist_name}/.mesonpy-native-file.ini': + continue + + # calculate the file path in the source directory + assert member.name, member.name + member_parts = member.name.split('/') + if len(member_parts) <= 1: + continue + path = self._source_dir.joinpath(*member_parts[1:]) + + if not path.is_file(): + continue + + # rewrite the path if necessary, to match the sdist distribution name + if dist_name != meson_dist_name: + member.name = path.relative_to(self._source_dir).as_posix() + + # rewrite the size + member.size = os.path.getsize(path) - # remove .mesonpy-native-file.ini if it exists - native_file = content / meson_dist_name / '.mesonpy-native-file.ini' - if native_file.exists(): - native_file.unlink() + with path.open('rb') as f: + tar.addfile(member, fileobj=f) # add PKG-INFO to dist file to make it a sdist - content.joinpath(dist_name, 'PKG-INFO').write_bytes(self.metadata) + pkginfo_info = tarfile.TarInfo(f'{dist_name}/PKG-INFO') + if mtime: + pkginfo_info.mtime = mtime + pkginfo_info.size = len(self.metadata) # type: ignore[arg-type] + tar.addfile(pkginfo_info, fileobj=io.BytesIO(self.metadata)) # type: ignore[arg-type] return sdist diff --git a/mesonpy/_util.py b/mesonpy/_util.py index d5ecbe7f9..e986627a1 100644 --- a/mesonpy/_util.py +++ b/mesonpy/_util.py @@ -5,13 +5,11 @@ import contextlib import gzip import os -import pathlib import sys import tarfile -import tempfile import typing -from typing import IO +from typing import IO, Optional, Tuple from mesonpy._compat import Iterable, Iterator, Path @@ -41,35 +39,26 @@ def add_ld_path(paths: Iterable[str]) -> Iterator[None]: @contextlib.contextmanager -def edit_targz(path: Path, new_path: Path) -> Iterator[pathlib.Path]: +def create_targz(path: Path) -> Iterator[Tuple[tarfile.TarFile, Optional[int]]]: """Opens a .tar.gz file in the file system for edition..""" - with tempfile.TemporaryDirectory(prefix='mesonpy-') as tmpdir: - workdir = pathlib.Path(tmpdir) - with tarfile.open(path, 'r:gz') as tar: - tar.extractall(tmpdir) - - yield workdir - - # reproducibility - source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') - mtime = int(source_date_epoch) if source_date_epoch else None - - file = typing.cast(IO[bytes], gzip.GzipFile( - os.path.join(path, new_path), - mode='wb', - mtime=mtime, - )) - with contextlib.closing(file), tarfile.TarFile( - mode='w', - fileobj=file, - format=tarfile.PAX_FORMAT, # changed in 3.8 to GNU - ) as tar: - for path in workdir.rglob('*'): - if path.is_file(): - tar.add( - name=path, - arcname=path.relative_to(workdir).as_posix(), - ) + + # reproducibility + source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') + mtime = int(source_date_epoch) if source_date_epoch else None + + file = typing.cast(IO[bytes], gzip.GzipFile( + os.path.join(path, path), + mode='wb', + mtime=mtime, + )) + tar = tarfile.TarFile( + mode='w', + fileobj=file, + format=tarfile.PAX_FORMAT, # changed in 3.8 to GNU + ) + + with contextlib.closing(file), tar: + yield tar, mtime class CLICounter: diff --git a/pyproject.toml b/pyproject.toml index 068926c8e..968f02bf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ build-backend = 'mesonpy' backend-path = ['.'] requires = [ - 'meson>=0.60.0', + 'meson>=0.62.0', 'ninja', 'pep621>=0.3.0', 'tomli>=1.0.0', diff --git a/tests/packages/subdirs/meson.build b/tests/packages/subdirs/meson.build new file mode 100644 index 000000000..d2e7d4917 --- /dev/null +++ b/tests/packages/subdirs/meson.build @@ -0,0 +1,14 @@ +project( + 'subdirs', + version: '1.0.0', +) + +py_mod = import('python') +py = py_mod.find_installation() + +py.install_sources([ + 'subdirs' / '__init__.py', + 'subdirs' / 'a' / '__init__.py', + 'subdirs' / 'a' / 'b' / 'c.py', + 'subdirs' / 'b' / 'c.py', +]) diff --git a/tests/packages/subdirs/pyproject.toml b/tests/packages/subdirs/pyproject.toml new file mode 100644 index 000000000..d6f3b6861 --- /dev/null +++ b/tests/packages/subdirs/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +build-backend = 'mesonpy' +requires = ['meson-python'] diff --git a/tests/packages/subdirs/subdirs/__init__.py b/tests/packages/subdirs/subdirs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/packages/subdirs/subdirs/a/__init__.py b/tests/packages/subdirs/subdirs/a/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/packages/subdirs/subdirs/a/b/c.py b/tests/packages/subdirs/subdirs/a/b/c.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/packages/subdirs/subdirs/b/c.py b/tests/packages/subdirs/subdirs/b/c.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_sdist.py b/tests/test_sdist.py index fccf1d183..f0ce8f999 100644 --- a/tests/test_sdist.py +++ b/tests/test_sdist.py @@ -1,6 +1,12 @@ # SPDX-License-Identifier: EUPL-1.2 +import os import tarfile +import textwrap + +import mesonpy + +from .conftest import in_git_repo_context def test_contents(sdist_library): @@ -14,3 +20,48 @@ def test_contents(sdist_library): 'library-1.0.0/pyproject.toml', 'library-1.0.0/PKG-INFO', } + + +def test_contents_subdirs(sdist_subdirs): + sdist = tarfile.open(sdist_subdirs, 'r:gz') + + assert set(sdist.getnames()) == { + 'subdirs-1.0.0/PKG-INFO', + 'subdirs-1.0.0/meson.build', + 'subdirs-1.0.0/pyproject.toml', + 'subdirs-1.0.0/subdirs/__init__.py', + 'subdirs-1.0.0/subdirs/a/__init__.py', + 'subdirs-1.0.0/subdirs/a/b/c.py', + 'subdirs-1.0.0/subdirs/b/c.py', + } + + +def test_contents_unstaged(package_pure, tmpdir): + new_data = textwrap.dedent(''' + def bar(): + return 'foo' + ''').strip() + + with open('pure.py', 'r') as f: + old_data = f.read() + + try: + with in_git_repo_context(), open('pure.py', 'w') as f, open('crap', 'x'): + f.write(new_data) + + sdist_path = mesonpy.build_sdist(os.fspath(tmpdir)) + finally: + with open('pure.py', 'w') as f: + f.write(old_data) + os.unlink('crap') + + sdist = tarfile.open(tmpdir / sdist_path, 'r:gz') + + assert set(sdist.getnames()) == { + 'pure-1.0.0/PKG-INFO', + 'pure-1.0.0/meson.build', + 'pure-1.0.0/pure.py', + 'pure-1.0.0/pyproject.toml', + } + read_data = sdist.extractfile('pure-1.0.0/pure.py').read().replace(b'\r\n', b'\n') + assert read_data == new_data.encode()