From ad7de0985c209136d70d19d76e1325dbcc502eb8 Mon Sep 17 00:00:00 2001 From: Frost Ming Date: Wed, 27 Dec 2023 14:57:01 +0800 Subject: [PATCH] fix: Can't include nested data files in built wheel * fix: Can't include nested data files in built wheel Fixes #205 Signed-off-by: Frost Ming * add python 3.12 Signed-off-by: Frost Ming --- .github/workflows/ci.yml | 2 +- src/pdm/backend/hooks/base.py | 9 +- src/pdm/backend/wheel.py | 5 +- tests/conftest.py | 35 +- .../demo-package-include/pyproject.toml | 2 +- .../scripts/{ => data}/my_script.sh | 0 tests/test_api.py | 651 +++++++++--------- tests/testutils.py | 13 +- 8 files changed, 379 insertions(+), 338 deletions(-) rename tests/fixtures/projects/demo-package-include/scripts/{ => data}/my_script.sh (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7719785..b6d5029 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "pypy-3.7", "pypy-3.8"] + python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12", "pypy-3.7", "pypy-3.8"] os: [ubuntu-latest, macOS-latest, windows-latest] steps: diff --git a/src/pdm/backend/hooks/base.py b/src/pdm/backend/hooks/base.py index 33f6a27..1eaff20 100644 --- a/src/pdm/backend/hooks/base.py +++ b/src/pdm/backend/hooks/base.py @@ -61,11 +61,16 @@ def ensure_build_dir(self) -> Path: return self.build_dir def expand_paths(self, path: str) -> Iterable[Path]: + def path_filter(p: Path) -> bool: + return p.is_file() or p.is_symlink() + plib_path = Path(path) if plib_path.parts and plib_path.parts[0] == "${BUILD_DIR}": - return self.build_dir.glob(Path(*plib_path.parts[1:]).as_posix()) + return filter( + path_filter, self.build_dir.glob(Path(*plib_path.parts[1:]).as_posix()) + ) - return self.root.glob(path) + return filter(path_filter, self.root.glob(path)) class BuildHookInterface(Protocol): diff --git a/src/pdm/backend/wheel.py b/src/pdm/backend/wheel.py index 952e0d2..d5f1adb 100644 --- a/src/pdm/backend/wheel.py +++ b/src/pdm/backend/wheel.py @@ -128,9 +128,10 @@ def get_files(self, context: Context) -> Iterable[tuple[str, Path]]: def _get_wheel_data(self, context: Context) -> Iterable[tuple[str, Path]]: for name, paths in context.config.build_config.wheel_data.items(): for path in paths: - relative_to: str | None = None + relative_to: Path | None = None if not isinstance(path, str): - relative_to = path.get("relative-to") + if path.get("relative-to"): + relative_to = context.root / path["relative-to"] path = path["path"] for child in context.expand_paths(path): relpath = ( diff --git a/tests/conftest.py b/tests/conftest.py index beb1107..09621a4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,7 @@ import shutil import subprocess +from pathlib import Path +from typing import Generator import pytest @@ -7,15 +9,24 @@ from tests import FIXTURES -@pytest.fixture() -def project_with_scm(tmp_path): - project = FIXTURES / "projects/demo-using-scm" - shutil.copytree(project, tmp_path / project.name) - with utils.cd(tmp_path / project.name): - subprocess.check_call(["git", "init"]) - subprocess.check_call(["git", "config", "user.email", "you@any.com"]) - subprocess.check_call(["git", "config", "user.name", "Name"]) - subprocess.check_call(["git", "add", "."]) - subprocess.check_call(["git", "commit", "-m", "initial commit"]) - subprocess.check_call(["git", "tag", "-a", "0.1.0", "-m", "version 0.1.0"]) - yield tmp_path / project.name +@pytest.fixture +def fixture_project(tmp_path: Path, name: str) -> Generator[Path, None, None]: + project = FIXTURES / "projects" / name + shutil.copytree(project, tmp_path / name) + with utils.cd(tmp_path / name): + yield tmp_path / name + + +@pytest.fixture +def dist(tmp_path: Path) -> Path: + return tmp_path / "dist" + + +@pytest.fixture +def scm(fixture_project: Path) -> None: + subprocess.check_call(["git", "init"]) + subprocess.check_call(["git", "config", "user.email", "you@any.com"]) + subprocess.check_call(["git", "config", "user.name", "Name"]) + subprocess.check_call(["git", "add", "."]) + subprocess.check_call(["git", "commit", "-m", "initial commit"]) + subprocess.check_call(["git", "tag", "-a", "0.1.0", "-m", "version 0.1.0"]) diff --git a/tests/fixtures/projects/demo-package-include/pyproject.toml b/tests/fixtures/projects/demo-package-include/pyproject.toml index f548cdc..2a78d38 100644 --- a/tests/fixtures/projects/demo-package-include/pyproject.toml +++ b/tests/fixtures/projects/demo-package-include/pyproject.toml @@ -31,4 +31,4 @@ excludes = [ source-includes = ["scripts/"] [tool.pdm.build.wheel-data] -scripts = ["scripts/*"] +scripts = ["scripts/**/*"] diff --git a/tests/fixtures/projects/demo-package-include/scripts/my_script.sh b/tests/fixtures/projects/demo-package-include/scripts/data/my_script.sh similarity index 100% rename from tests/fixtures/projects/demo-package-include/scripts/my_script.sh rename to tests/fixtures/projects/demo-package-include/scripts/data/my_script.sh diff --git a/tests/test_api.py b/tests/test_api.py index 3b68f69..145a418 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -8,435 +8,470 @@ import pdm.backend as api from pdm.backend.wheel import WheelBuilder -from tests.testutils import build_fixture_project, get_tarball_names, get_wheel_names - - -def test_build_single_module(tmp_path: Path) -> None: - with build_fixture_project("demo-module"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - assert api.get_requires_for_build_sdist() == [] - assert api.get_requires_for_build_wheel() == [] - assert sdist_name == "demo_module-0.1.0.tar.gz" - assert wheel_name == "demo_module-0.1.0-py3-none-any.whl" - tar_names = get_tarball_names(tmp_path / sdist_name) - for name in [ - "foo_module.py", - "bar_module.py", - "LICENSE", - "pyproject.toml", - "PKG-INFO", - "README.md", - ]: - assert f"demo_module-0.1.0/{name}" in tar_names - - zip_names = get_wheel_names(tmp_path / wheel_name) - for name in ["foo_module.py", "bar_module.py"]: - assert name in zip_names - - for name in ("pyproject.toml", "LICENSE"): - assert name not in zip_names - - assert "demo_module-0.1.0.dist-info/licenses/LICENSE" in zip_names - - -def test_build_package(tmp_path: Path) -> None: - with build_fixture_project("demo-package"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - assert sdist_name == "demo_package-0.1.0.tar.gz" - assert wheel_name == "demo_package-0.1.0-py2.py3-none-any.whl" - - tar_names = get_tarball_names(tmp_path / sdist_name) - assert "demo_package-0.1.0/my_package/__init__.py" in tar_names - assert "demo_package-0.1.0/my_package/data.json" in tar_names - assert "demo_package-0.1.0/single_module.py" not in tar_names - assert "demo_package-0.1.0/data_out.json" in tar_names - - zip_names = get_wheel_names(tmp_path / wheel_name) +from tests.testutils import get_tarball_names, get_wheel_names + +pytestmark = pytest.mark.usefixtures("fixture_project") + + +@pytest.mark.parametrize("name", ["demo-module"]) +def test_build_single_module(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + assert api.get_requires_for_build_sdist() == [] + assert api.get_requires_for_build_wheel() == [] + assert sdist_name == "demo_module-0.1.0.tar.gz" + assert wheel_name == "demo_module-0.1.0-py3-none-any.whl" + tar_names = get_tarball_names(dist / sdist_name) + for name in [ + "foo_module.py", + "bar_module.py", + "LICENSE", + "pyproject.toml", + "PKG-INFO", + "README.md", + ]: + assert f"demo_module-0.1.0/{name}" in tar_names + + zip_names = get_wheel_names(dist / wheel_name) + for name in ["foo_module.py", "bar_module.py"]: + assert name in zip_names + + for name in ("pyproject.toml", "LICENSE"): + assert name not in zip_names + + assert "demo_module-0.1.0.dist-info/licenses/LICENSE" in zip_names + + +@pytest.mark.parametrize("name", ["demo-package"]) +def test_build_package(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + assert sdist_name == "demo_package-0.1.0.tar.gz" + assert wheel_name == "demo_package-0.1.0-py2.py3-none-any.whl" + + tar_names = get_tarball_names(dist / sdist_name) + assert "demo_package-0.1.0/my_package/__init__.py" in tar_names + assert "demo_package-0.1.0/my_package/data.json" in tar_names + assert "demo_package-0.1.0/single_module.py" not in tar_names + assert "demo_package-0.1.0/data_out.json" in tar_names + + zip_names = get_wheel_names(dist / wheel_name) + assert "my_package/__init__.py" in zip_names + assert "my_package/data.json" in zip_names + assert "single_module.py" not in zip_names + assert "data_out.json" not in zip_names + + +@pytest.mark.parametrize("name", ["demo-src-package"]) +def test_build_src_package(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + assert sdist_name == "demo_package-0.1.0.tar.gz" + assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" + + tar_names = get_tarball_names(dist / sdist_name) + zip_names = get_wheel_names(dist / wheel_name) + assert "demo_package-0.1.0/src/my_package/__init__.py" in tar_names + assert "demo_package-0.1.0/src/my_package/data.json" in tar_names + + assert "my_package/__init__.py" in zip_names + assert "my_package/data.json" in zip_names + + +@pytest.mark.parametrize("name", ["demo-package-include"]) +def test_build_package_include(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + assert sdist_name == "demo_package-0.1.0.tar.gz" + assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" + + tar_names = get_tarball_names(dist / sdist_name) + + assert "demo_package-0.1.0/my_package/__init__.py" in tar_names + assert "demo_package-0.1.0/my_package/data.json" not in tar_names + assert "demo_package-0.1.0/requirements.txt" in tar_names + assert "demo_package-0.1.0/data_out.json" in tar_names + + with zipfile.ZipFile(dist / wheel_name) as zf: + zip_names = zf.namelist() assert "my_package/__init__.py" in zip_names - assert "my_package/data.json" in zip_names - assert "single_module.py" not in zip_names - assert "data_out.json" not in zip_names + assert "my_package/data.json" not in zip_names + assert "requirements.txt" in zip_names + assert "data_out.json" in zip_names + assert "demo_package-0.1.0.data/scripts/my_script.sh" in zip_names + if os.name != "nt": + info = zf.getinfo("demo_package-0.1.0.data/scripts/my_script.sh") + filemode = info.external_attr >> 16 + assert filemode & 0o111 -def test_build_src_package(tmp_path: Path) -> None: - with build_fixture_project("demo-src-package"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - assert sdist_name == "demo_package-0.1.0.tar.gz" - assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" +@pytest.mark.parametrize("name", ["demo-package-include"]) +def test_build_package_data_relative(dist: Path, fixture_project: Path) -> None: + from pdm.backend.config import tomli_w, tomllib - tar_names = get_tarball_names(tmp_path / sdist_name) - zip_names = get_wheel_names(tmp_path / wheel_name) - assert "demo_package-0.1.0/src/my_package/__init__.py" in tar_names - assert "demo_package-0.1.0/src/my_package/data.json" in tar_names + with open(fixture_project / "pyproject.toml", "rb") as fp: + pyproject = tomllib.load(fp) + pyproject["tool"]["pdm"]["build"]["wheel-data"]["scripts"] = [ + {"path": "scripts/**/*", "relative-to": "scripts/"} + ] + with open(fixture_project / "pyproject.toml", "wb") as fp: + tomli_w.dump(pyproject, fp) + wheel_name = api.build_wheel(dist.as_posix()) + with zipfile.ZipFile(dist / wheel_name) as zf: + zip_names = zf.namelist() assert "my_package/__init__.py" in zip_names - assert "my_package/data.json" in zip_names - - -def test_build_package_include(tmp_path: Path) -> None: - with build_fixture_project("demo-package-include"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - assert sdist_name == "demo_package-0.1.0.tar.gz" - assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" - - tar_names = get_tarball_names(tmp_path / sdist_name) - - assert "demo_package-0.1.0/my_package/__init__.py" in tar_names - assert "demo_package-0.1.0/my_package/data.json" not in tar_names - assert "demo_package-0.1.0/requirements.txt" in tar_names - assert "demo_package-0.1.0/data_out.json" in tar_names - - with zipfile.ZipFile(tmp_path / wheel_name) as zf: - zip_names = zf.namelist() - assert "my_package/__init__.py" in zip_names - assert "my_package/data.json" not in zip_names - assert "requirements.txt" in zip_names - assert "data_out.json" in zip_names - assert "demo_package-0.1.0.data/scripts/my_script.sh" in zip_names - if os.name != "nt": - info = zf.getinfo("demo_package-0.1.0.data/scripts/my_script.sh") - filemode = info.external_attr >> 16 - assert filemode & 0o111 - - -def test_namespace_package_by_include(tmp_path: Path) -> None: - with build_fixture_project("demo-pep420-package"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - assert sdist_name == "demo_package-0.1.0.tar.gz" - assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" - - tar_names = get_tarball_names(tmp_path / sdist_name) - zip_names = get_wheel_names(tmp_path / wheel_name) - assert "demo_package-0.1.0/foo/my_package/__init__.py" in tar_names - assert "demo_package-0.1.0/foo/my_package/data.json" in tar_names - - assert "foo/my_package/__init__.py" in zip_names - assert "foo/my_package/data.json" in zip_names - - -def test_build_explicit_package_dir(tmp_path: Path) -> None: - with build_fixture_project("demo-explicit-package-dir"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - assert sdist_name == "demo_package-0.1.0.tar.gz" - assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" - - tar_names = get_tarball_names(tmp_path / sdist_name) - zip_names = get_wheel_names(tmp_path / wheel_name) - assert "demo_package-0.1.0/foo/my_package/__init__.py" in tar_names - assert "demo_package-0.1.0/foo/my_package/data.json" in tar_names + assert "my_package/data.json" not in zip_names + assert "requirements.txt" in zip_names + assert "data_out.json" in zip_names + assert "demo_package-0.1.0.data/scripts/data/my_script.sh" in zip_names + if os.name != "nt": + info = zf.getinfo("demo_package-0.1.0.data/scripts/data/my_script.sh") + filemode = info.external_attr >> 16 + assert filemode & 0o111 - assert "my_package/__init__.py" in zip_names - assert "my_package/data.json" in zip_names +@pytest.mark.parametrize("name", ["demo-pep420-package"]) +def test_namespace_package_by_include(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + assert sdist_name == "demo_package-0.1.0.tar.gz" + assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" -def test_prepare_metadata(tmp_path: Path) -> None: - with build_fixture_project("demo-package"): - dist_info = api.prepare_metadata_for_build_wheel(tmp_path.as_posix()) - assert dist_info == "demo_package-0.1.0.dist-info" - for filename in ("WHEEL", "METADATA"): - assert (tmp_path / dist_info / filename).is_file() + tar_names = get_tarball_names(dist / sdist_name) + zip_names = get_wheel_names(dist / wheel_name) + assert "demo_package-0.1.0/foo/my_package/__init__.py" in tar_names + assert "demo_package-0.1.0/foo/my_package/data.json" in tar_names + assert "foo/my_package/__init__.py" in zip_names + assert "foo/my_package/data.json" in zip_names -def test_build_wheel_metadata_identical(tmp_path: Path) -> None: - with build_fixture_project("demo-package"): - dist_info = api.prepare_metadata_for_build_wheel(tmp_path.as_posix()) - (tmp_path / dist_info / "other.txt").write_text("foo") - wheel_name = api.build_wheel( - tmp_path.as_posix(), metadata_directory=str(tmp_path / dist_info) - ) +@pytest.mark.parametrize("name", ["demo-explicit-package-dir"]) +def test_build_explicit_package_dir(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + assert sdist_name == "demo_package-0.1.0.tar.gz" + assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" - with zipfile.ZipFile(tmp_path / wheel_name) as wheel: - assert f"{dist_info}/other.txt" in wheel.namelist() - assert wheel.read(f"{dist_info}/other.txt") == b"foo" + tar_names = get_tarball_names(dist / sdist_name) + zip_names = get_wheel_names(dist / wheel_name) + assert "demo_package-0.1.0/foo/my_package/__init__.py" in tar_names + assert "demo_package-0.1.0/foo/my_package/data.json" in tar_names + assert "my_package/__init__.py" in zip_names + assert "my_package/data.json" in zip_names -def test_build_package_with_modules_in_src(tmp_path: Path) -> None: - with build_fixture_project("demo-src-pymodule"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - tar_names = get_tarball_names(tmp_path / sdist_name) - assert "demo_module-0.1.0/src/foo_module.py" in tar_names +@pytest.mark.parametrize("name", ["demo-package"]) +def test_prepare_metadata(dist: Path) -> None: + dist_info = api.prepare_metadata_for_build_wheel(dist.as_posix()) + assert dist_info == "demo_package-0.1.0.dist-info" + for filename in ("WHEEL", "METADATA"): + assert (dist / dist_info / filename).is_file() - zip_names = get_wheel_names(tmp_path / wheel_name) - assert "foo_module.py" in zip_names +@pytest.mark.parametrize("name", ["demo-package"]) +def test_build_wheel_metadata_identical(dist: Path) -> None: + dist_info = api.prepare_metadata_for_build_wheel(dist.as_posix()) + (dist / dist_info / "other.txt").write_text("foo") -def test_build_with_cextension(tmp_path: Path) -> None: - with build_fixture_project("demo-cextension"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - assert api.get_requires_for_build_sdist() == [] - assert api.get_requires_for_build_wheel() == ["setuptools>=40.8.0"] + wheel_name = api.build_wheel( + dist.as_posix(), metadata_directory=str(dist / dist_info) + ) - zip_names = get_wheel_names(tmp_path / wheel_name) - assert "my_package/__init__.py" in zip_names - assert ( - "my_package/hellomodule.c" not in zip_names - ), "Not collect c files while building wheel" - extension_suffix = ".pyd" if sys.platform == "win32" else ".so" - assert any(name.endswith(extension_suffix) for name in zip_names) + with zipfile.ZipFile(dist / wheel_name) as wheel: + assert f"{dist_info}/other.txt" in wheel.namelist() + assert wheel.read(f"{dist_info}/other.txt") == b"foo" + + +@pytest.mark.parametrize("name", ["demo-src-pymodule"]) +def test_build_package_with_modules_in_src(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + + tar_names = get_tarball_names(dist / sdist_name) + assert "demo_module-0.1.0/src/foo_module.py" in tar_names + + zip_names = get_wheel_names(dist / wheel_name) + assert "foo_module.py" in zip_names + + +@pytest.mark.parametrize("name", ["demo-cextension"]) +def test_build_with_cextension(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + assert api.get_requires_for_build_sdist() == [] + assert api.get_requires_for_build_wheel() == ["setuptools>=40.8.0"] + + zip_names = get_wheel_names(dist / wheel_name) + assert "my_package/__init__.py" in zip_names + assert ( + "my_package/hellomodule.c" not in zip_names + ), "Not collect c files while building wheel" + extension_suffix = ".pyd" if sys.platform == "win32" else ".so" + assert any(name.endswith(extension_suffix) for name in zip_names) + + tar_names = get_tarball_names(dist / sdist_name) + assert "demo_package-0.1.0/my_package/__init__.py" in tar_names + assert ( + "demo_package-0.1.0/my_package/hellomodule.c" in tar_names + ), "Collect c files while building sdist" + assert not any( + path.startswith("build") for path in tar_names + ), 'Not collect c files in temporary directory "./build"' + + +@pytest.mark.parametrize("name", ["demo-cextension-in-src"]) +def test_build_with_cextension_in_src(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + sdist_name = api.build_sdist(dist.as_posix()) + + zip_names = get_wheel_names(dist / wheel_name) + assert "my_package/__init__.py" in zip_names + assert ( + "my_package/hellomodule.c" not in zip_names + ), "Not collect c files while building wheel" + extension_suffix = ".pyd" if sys.platform == "win32" else ".so" + assert any(name.endswith(extension_suffix) for name in zip_names) + + tar_names = get_tarball_names(dist / sdist_name) + assert "demo_package-0.1.0/src/my_package/__init__.py" in tar_names + assert ( + "demo_package-0.1.0/src/my_package/hellomodule.c" in tar_names + ), "Collect c files while building sdist" + assert not any( + path.startswith("build") for path in tar_names + ), 'Not collect c files in temporary directory "./build"' + + +@pytest.mark.parametrize("name", ["demo-package"]) +def test_build_editable(dist: Path, fixture_project: Path) -> None: + wheel_name = api.build_editable(dist.as_posix()) + assert api.get_requires_for_build_editable() == ["editables"] + with zipfile.ZipFile(dist / wheel_name) as zf: + namelist = zf.namelist() + assert "demo_package.pth" in namelist + assert "_editable_impl_demo_package.py" in namelist + assert "demo_package-0.1.0+editable.dist-info/licenses/LICENSE" in namelist + + metadata = email.message_from_bytes( + zf.read("demo_package-0.1.0+editable.dist-info/METADATA") + ) + assert "editables" in metadata.get_all("Requires-Dist", []) - tar_names = get_tarball_names(tmp_path / sdist_name) - assert "demo_package-0.1.0/my_package/__init__.py" in tar_names - assert ( - "demo_package-0.1.0/my_package/hellomodule.c" in tar_names - ), "Collect c files while building sdist" - assert not any( - path.startswith("build") for path in tar_names - ), 'Not collect c files in temporary directory "./build"' + pth_content = zf.read("demo_package.pth").decode("utf-8").strip() + assert pth_content == "import _editable_impl_demo_package" + proxy_module = zf.read("_editable_impl_demo_package.py").decode("utf-8").strip() + assert proxy_module == ( + "from editables.redirector import RedirectingFinder as F\n" + "F.install()\n" + "F.map_module('my_package', {!r})".format( + str((fixture_project / "my_package" / "__init__.py").resolve()) + ) + ) -def test_build_with_cextension_in_src(tmp_path: Path) -> None: - with build_fixture_project("demo-cextension-in-src"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - sdist_name = api.build_sdist(tmp_path.as_posix()) - zip_names = get_wheel_names(tmp_path / wheel_name) - assert "my_package/__init__.py" in zip_names - assert ( - "my_package/hellomodule.c" not in zip_names - ), "Not collect c files while building wheel" - extension_suffix = ".pyd" if sys.platform == "win32" else ".so" - assert any(name.endswith(extension_suffix) for name in zip_names) +@pytest.mark.parametrize("name", ["demo-src-package-include"]) +def test_build_editable_src(dist: Path, fixture_project: Path) -> None: + wheel_name = api.build_editable(dist.as_posix()) - tar_names = get_tarball_names(tmp_path / sdist_name) - assert "demo_package-0.1.0/src/my_package/__init__.py" in tar_names + with zipfile.ZipFile(dist / wheel_name) as zf: + namelist = zf.namelist() + assert "demo_package.pth" in namelist + assert "_editable_impl_demo_package.py" in namelist assert ( - "demo_package-0.1.0/src/my_package/hellomodule.c" in tar_names - ), "Collect c files while building sdist" - assert not any( - path.startswith("build") for path in tar_names - ), 'Not collect c files in temporary directory "./build"' - - -def test_build_editable(tmp_path: Path) -> None: - with build_fixture_project("demo-package") as project: - wheel_name = api.build_editable(tmp_path.as_posix()) - assert api.get_requires_for_build_editable() == ["editables"] - with zipfile.ZipFile(tmp_path / wheel_name) as zf: - namelist = zf.namelist() - assert "demo_package.pth" in namelist - assert "_editable_impl_demo_package.py" in namelist - assert "demo_package-0.1.0+editable.dist-info/licenses/LICENSE" in namelist - - metadata = email.message_from_bytes( - zf.read("demo_package-0.1.0+editable.dist-info/METADATA") - ) - assert "editables" in metadata.get_all("Requires-Dist", []) - - pth_content = zf.read("demo_package.pth").decode("utf-8").strip() - assert pth_content == "import _editable_impl_demo_package" - - proxy_module = ( - zf.read("_editable_impl_demo_package.py").decode("utf-8").strip() - ) - assert proxy_module == ( - "from editables.redirector import RedirectingFinder as F\n" - "F.install()\n" - "F.map_module('my_package', {!r})".format( - str((project / "my_package" / "__init__.py").resolve()) - ) + "my_package/data.json" not in namelist + ), "data files in proxy modules are excluded" + assert "data_out.json" in namelist + + pth_content = zf.read("demo_package.pth").decode("utf-8").strip() + assert pth_content == "import _editable_impl_demo_package" + + proxy_module = zf.read("_editable_impl_demo_package.py").decode("utf-8").strip() + assert proxy_module == ( + "from editables.redirector import RedirectingFinder as F\n" + "F.install()\n" + "F.map_module('my_package', {!r})".format( + str((fixture_project / "sub" / "my_package" / "__init__.py").resolve()) ) + ) -def test_build_editable_src(tmp_path: Path) -> None: - with build_fixture_project("demo-src-package-include") as project: - wheel_name = api.build_editable(tmp_path.as_posix()) - - with zipfile.ZipFile(tmp_path / wheel_name) as zf: - namelist = zf.namelist() - assert "demo_package.pth" in namelist - assert "_editable_impl_demo_package.py" in namelist - assert ( - "my_package/data.json" not in namelist - ), "data files in proxy modules are excluded" - assert "data_out.json" in namelist - - pth_content = zf.read("demo_package.pth").decode("utf-8").strip() - assert pth_content == "import _editable_impl_demo_package" - - proxy_module = ( - zf.read("_editable_impl_demo_package.py").decode("utf-8").strip() - ) - assert proxy_module == ( - "from editables.redirector import RedirectingFinder as F\n" - "F.install()\n" - "F.map_module('my_package', {!r})".format( - str((project / "sub" / "my_package" / "__init__.py").resolve()) - ) - ) +@pytest.mark.parametrize("name", ["demo-pep420-package"]) +def test_build_editable_pep420(dist: Path, fixture_project: Path) -> None: + with pytest.warns(UserWarning) as recorded: + wheel_name = api.build_editable(dist.as_posix()) + assert len(recorded) == 1 + assert str(recorded.pop().message).startswith("editables backend is not available") -def test_build_editable_pep420(tmp_path: Path) -> None: - with build_fixture_project("demo-pep420-package") as project: - with pytest.warns(UserWarning) as recorded: - wheel_name = api.build_editable(tmp_path.as_posix()) + with zipfile.ZipFile(dist / wheel_name) as zf: + namelist = zf.namelist() + assert "demo_package.pth" in namelist + assert "__editables_demo_package.py" not in namelist - assert len(recorded) == 1 - assert str(recorded.pop().message).startswith( - "editables backend is not available" + metadata = email.message_from_bytes( + zf.read("demo_package-0.1.0+editable.dist-info/METADATA") ) + assert "editables" not in metadata.get_all("Requires-Dist", []) - with zipfile.ZipFile(tmp_path / wheel_name) as zf: - namelist = zf.namelist() - assert "demo_package.pth" in namelist - assert "__editables_demo_package.py" not in namelist - - metadata = email.message_from_bytes( - zf.read("demo_package-0.1.0+editable.dist-info/METADATA") - ) - assert "editables" not in metadata.get_all("Requires-Dist", []) + pth_content = zf.read("demo_package.pth").decode("utf-8").strip() + assert pth_content == str(fixture_project.resolve()) - pth_content = zf.read("demo_package.pth").decode("utf-8").strip() - assert pth_content == str(project.resolve()) +@pytest.mark.parametrize("name", ["demo-package"]) +def test_prepare_metadata_for_editable(dist: Path) -> None: + dist_info = api.prepare_metadata_for_build_editable(dist.as_posix()) + assert dist_info == "demo_package-0.1.0+editable.dist-info" + with (dist / dist_info / "METADATA").open("rb") as metadata: + deps = email.message_from_binary_file(metadata).get_all("Requires-Dist") + assert "editables" in deps -def test_prepare_metadata_for_editable(tmp_path: Path) -> None: - with build_fixture_project("demo-package"): - dist_info = api.prepare_metadata_for_build_editable(tmp_path.as_posix()) - assert dist_info == "demo_package-0.1.0+editable.dist-info" - with (tmp_path / dist_info / "METADATA").open("rb") as metadata: - deps = email.message_from_binary_file(metadata).get_all("Requires-Dist") - assert "editables" in deps +@pytest.mark.parametrize("name", ["demo-purelib-with-build"]) +def test_build_purelib_project_with_build(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" -def test_build_purelib_project_with_build(tmp_path: Path) -> None: - with build_fixture_project("demo-purelib-with-build"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - assert wheel_name == "demo_package-0.1.0-py3-none-any.whl" - - with zipfile.ZipFile(tmp_path / wheel_name) as zf: - wheel_metadata = email.message_from_bytes( - zf.read("demo_package-0.1.0.dist-info/WHEEL") - ) - version = zf.read("my_package/version.txt").decode("utf-8").strip() - assert version == "0.1.0" - assert wheel_metadata["Root-Is-Purelib"] == "true" + with zipfile.ZipFile(dist / wheel_name) as zf: + wheel_metadata = email.message_from_bytes( + zf.read("demo_package-0.1.0.dist-info/WHEEL") + ) + version = zf.read("my_package/version.txt").decode("utf-8").strip() + assert version == "0.1.0" + assert wheel_metadata["Root-Is-Purelib"] == "true" @pytest.mark.skipif( sys.platform.startswith("win"), reason="Check file mode on Unix only" ) -def test_build_wheel_preserve_permission(tmp_path: Path) -> None: - with build_fixture_project("demo-package"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - with zipfile.ZipFile(tmp_path / wheel_name) as zf: - info = zf.getinfo("my_package/executable") - filemode = info.external_attr >> 16 - assert filemode & 0o111 - - -def test_build_wheel_write_version_to_file(project_with_scm: Path) -> None: - builder = WheelBuilder(project_with_scm) +@pytest.mark.parametrize("name", ["demo-package"]) +def test_build_wheel_preserve_permission(dist: Path) -> None: + wheel_name = api.build_wheel(dist.as_posix()) + with zipfile.ZipFile(dist / wheel_name) as zf: + info = zf.getinfo("my_package/executable") + filemode = info.external_attr >> 16 + assert filemode & 0o111 + + +@pytest.mark.usefixtures("scm") +@pytest.mark.parametrize("name", ["demo-using-scm"]) +def test_build_wheel_write_version_to_file(fixture_project: Path, dist) -> None: + builder = WheelBuilder(fixture_project) builder.config.data.setdefault("tool", {}).setdefault("pdm", {})["version"] = { "source": "scm", "write_to": "foo/__version__.py", } with builder: - wheel_name = builder.build(project_with_scm / "dist") + wheel_name = builder.build(dist) with zipfile.ZipFile(wheel_name) as zf: version = zf.read("foo/__version__.py").decode("utf-8").strip() assert version == "0.1.0" -def test_build_wheel_write_version_to_file_template(project_with_scm: Path) -> None: - builder = WheelBuilder(project_with_scm) +@pytest.mark.usefixtures("scm") +@pytest.mark.parametrize("name", ["demo-using-scm"]) +def test_build_wheel_write_version_to_file_template( + fixture_project: Path, dist: Path +) -> None: + builder = WheelBuilder(fixture_project) builder.config.data.setdefault("tool", {}).setdefault("pdm", {})["version"] = { "source": "scm", "write_to": "foo/__version__.py", "write_template": '__version__ = "{}"\n', } with builder: - wheel_name = builder.build(project_with_scm / "dist") + wheel_name = builder.build(dist) with zipfile.ZipFile(wheel_name) as zf: version = zf.read("foo/__version__.py").decode("utf-8").strip() assert version == '__version__ = "0.1.0"' -def test_override_scm_version_via_env_var(tmp_path: Path, monkeypatch) -> None: +@pytest.mark.parametrize("name", ["demo-using-scm"]) +def test_override_scm_version_via_env_var( + dist: Path, monkeypatch: pytest.MonkeyPatch +) -> None: monkeypatch.setenv("PDM_BUILD_SCM_VERSION", "1.0.0") - with build_fixture_project("demo-using-scm"): - wheel_name = api.build_wheel(tmp_path.as_posix()) - assert wheel_name == "foo-1.0.0-py3-none-any.whl" + wheel_name = api.build_wheel(dist.as_posix()) + assert wheel_name == "foo-1.0.0-py3-none-any.whl" +@pytest.mark.usefixtures("scm") @pytest.mark.parametrize("getter", ["get_version:run", "get_version:run()"]) -def test_get_version_from_call(project_with_scm: Path, getter: str) -> None: - builder = WheelBuilder(project_with_scm) +@pytest.mark.parametrize("name", ["demo-using-scm"]) +def test_get_version_from_call(fixture_project: Path, getter: str, dist: Path) -> None: + builder = WheelBuilder(fixture_project) builder.config.data.setdefault("tool", {}).setdefault("pdm", {})["version"] = { "source": "call", "write_to": "foo/__version__.py", "getter": getter, } - project_with_scm.joinpath("get_version.py").write_text( - "def run(): return '1.1.1'\n" - ) + fixture_project.joinpath("get_version.py").write_text("def run(): return '1.1.1'\n") with builder: - wheel_name = builder.build(project_with_scm / "dist") + wheel_name = builder.build(dist) assert wheel_name.name == "foo-1.1.1-py3-none-any.whl" with zipfile.ZipFile(wheel_name) as zf: version = zf.read("foo/__version__.py").decode("utf-8").strip() assert version == "1.1.1" +@pytest.mark.usefixtures("scm") @pytest.mark.parametrize( "settings, cleanup", [("true", False), ("false", True), ("0", True), ("1", False)] ) +@pytest.mark.parametrize("name", ["demo-using-scm"]) def test_clean_not_called_if_envset( - project_with_scm: Path, + fixture_project: Path, monkeypatch: pytest.MonkeyPatch, settings: str, cleanup: bool, + dist: Path, ) -> None: monkeypatch.setenv("PDM_BUILD_NO_CLEAN", settings) - builder = WheelBuilder(project_with_scm) + builder = WheelBuilder(fixture_project) builder.config.data.setdefault("tool", {}).setdefault("pdm", {})["version"] = { "source": "scm", "write_to": "foo/__version__.py", } - test_file = project_with_scm / ".pdm-build" / "testfile" - os.makedirs(project_with_scm / ".pdm-build", exist_ok=True) + test_file = fixture_project / ".pdm-build" / "testfile" + os.makedirs(fixture_project / ".pdm-build", exist_ok=True) test_file.touch() assert os.path.exists(test_file) with builder: - builder.build(project_with_scm / "dist") + builder.build(dist) if cleanup: assert not os.path.exists(test_file) else: assert os.path.exists(test_file) +@pytest.mark.usefixtures("scm") @pytest.mark.parametrize( "settings, cleanup", [("", False), (True, False), (None, False)] ) +@pytest.mark.parametrize("name", ["demo-using-scm"]) def test_clean_not_called_if_config_settings_exist( - project_with_scm: Path, settings: bool, cleanup: bool + fixture_project: Path, settings: bool, cleanup: bool, dist: Path ) -> None: builder = WheelBuilder( - project_with_scm, config_settings={"no-clean-build": settings} + fixture_project, config_settings={"no-clean-build": settings} ) builder.config.data.setdefault("tool", {}).setdefault("pdm", {})["version"] = { "source": "scm", "write_to": "foo/__version__.py", } - test_file = project_with_scm / ".pdm-build" / "testfile" - os.makedirs(project_with_scm / ".pdm-build", exist_ok=True) + test_file = fixture_project / ".pdm-build" / "testfile" + os.makedirs(fixture_project / ".pdm-build", exist_ok=True) test_file.touch() assert os.path.exists(test_file) with builder: - builder.build(project_with_scm / "dist") + builder.build(dist) if cleanup: assert not os.path.exists(test_file) else: diff --git a/tests/testutils.py b/tests/testutils.py index d75ee4f..89cfcf0 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -1,11 +1,7 @@ -import contextlib import tarfile import zipfile from pathlib import Path -from typing import Iterator, List - -from pdm.backend import utils -from tests import FIXTURES +from typing import List def get_tarball_names(path: Path) -> List[str]: @@ -16,10 +12,3 @@ def get_tarball_names(path: Path) -> List[str]: def get_wheel_names(path: Path) -> List[str]: with zipfile.ZipFile(path) as zf: return zf.namelist() - - -@contextlib.contextmanager -def build_fixture_project(project_name: str) -> Iterator[Path]: - project = FIXTURES / "projects" / project_name - with utils.cd(project): - yield project