Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pdm-backend for 
openSUSE:Factory checked in at 2024-01-07 21:40:59
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pdm-backend (Old)
 and      /work/SRC/openSUSE:Factory/.python-pdm-backend.new.28375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pdm-backend"

Sun Jan  7 21:40:59 2024 rev:3 rq:1137425 version:2.1.8

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pdm-backend/python-pdm-backend.changes    
2023-06-26 18:17:16.550785429 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pdm-backend.new.28375/python-pdm-backend.changes
 2024-01-07 21:41:14.894083624 +0100
@@ -1,0 +2,24 @@
+Sun Jan  7 19:20:21 UTC 2024 - Dirk Müller <[email protected]>
+
+- update to 2.1.8:
+  * Can't include nested data files in built wheel
+  * Update domain name
+- update to 2.1.7:
+  * Rename tag-pattern to tag_regex in the docs
+  * Copytree handle existing dirs
+  * Normalize the dist info name in the wheel
+- update to 2.1.6:
+  * Path to uri expansion in dependency string
+- update to 2.1.5:
+  * Don't quote project root in file:// urls Fix #190
+  * Write version to file doesn't work for editable installs
+- update to 2.1.4:
+  * Write version file for sdist build as well
+- update to 2.1.3:
+  * Remove duplicate files when building wheel
+- update to 2.1.2:
+  * Fix file permissions to 644 for wheel
+- update to 2.1.1:
+  * Pdm-backend should not normalize names in project metadata
+
+-------------------------------------------------------------------

Old:
----
  pdm_backend-2.1.0.tar.gz

New:
----
  pdm_backend-2.1.8.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pdm-backend.spec ++++++
--- /var/tmp/diff_new_pack.hVVLIK/_old  2024-01-07 21:41:15.338099775 +0100
+++ /var/tmp/diff_new_pack.hVVLIK/_new  2024-01-07 21:41:15.338099775 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pdm-backend
 #
-# Copyright (c) 2023 SUSE LLC
+# Copyright (c) 2024 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,12 +18,12 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-pdm-backend
-Version:        2.1.0
+Version:        2.1.8
 Release:        0
 Summary:        Backend used by PDM
 License:        MIT
 URL:            https://github.com/pdm-project/pdm-backend
-Source:         
https://files.pythonhosted.org/packages/8f/aa/df3ad85bf4eeb7a7a3364610ca399f56812e4827cff6495c2a20e4bf1bb4/pdm_backend-2.1.0.tar.gz
+Source:         
https://files.pythonhosted.org/packages/source/p/pdm-backend/pdm_backend-%{version}.tar.gz
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module editables}
 BuildRequires:  %{python_module importlib-metadata >= 3.6.0 if %python-base < 
3.10}

++++++ pdm_backend-2.1.0.tar.gz -> pdm_backend-2.1.8.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/PKG-INFO 
new/pdm_backend-2.1.8/PKG-INFO
--- old/pdm_backend-2.1.0/PKG-INFO      1970-01-01 01:00:00.000000000 +0100
+++ new/pdm_backend-2.1.8/PKG-INFO      1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pdm-backend
-Version: 2.1.0
+Version: 2.1.8
 Summary: The build backend used by PDM that supports latest packaging standards
 Keywords: packaging PEP 517 build
 Author-Email: Frost Ming <[email protected]>
@@ -15,7 +15,7 @@
 Classifier: Programming Language :: Python :: 3.11
 Project-URL: Homepage, https://github.com/pdm-project/pdm-backend
 Project-URL: Repository, https://github.com/pdm-project/pdm-backend
-Project-URL: Documentation, https://pdm-backend.fming.dev
+Project-URL: Documentation, https://backend.pdm-project.org
 Requires-Python: >=3.7
 Requires-Dist: importlib-metadata>=3.6; python_version < "3.10"
 Description-Content-Type: text/markdown
@@ -27,7 +27,7 @@
 
[![PyPI](https://img.shields.io/pypi/v/pdm-backend?label=PyPI)](https://pypi.org/project/pdm-backend)
 
[![Tests](https://github.com/pdm-project/pdm-backend/actions/workflows/ci.yml/badge.svg)](https://github.com/pdm-project/pdm-backend/actions/workflows/ci.yml)
 [![pre-commit.ci 
status](https://results.pre-commit.ci/badge/github/pdm-project/pdm-backend/main.svg)](https://results.pre-commit.ci/latest/github/pdm-project/pdm-backend/main)
-[![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
+[![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)][PDM]
 
 This is the backend for [PDM] projects that is fully-compatible with [PEP 517] 
spec, but you can also use it alone.
 It reads the metadata of [PEP 621] format and coverts it to [Core metadata].
@@ -35,11 +35,11 @@
 [pep 517]: https://www.python.org/dev/peps/pep-0517/
 [pep 621]: https://www.python.org/dev/peps/pep-0621/
 [Core metadata]: https://packaging.python.org/specifications/core-metadata/
-[PDM]: https://pdm.fming.dev
+[PDM]: https://pdm-project.org
 
 ## Links
 
-- [Documentation](https://pdm-backend.fming.dev)
+- [Documentation](https://backend.pdm-project.org)
 - [Changelog](https://github.com/pdm-project/pdm-backend/releases)
 - [PDM Documentation][PDM]
 - [PyPI](https://pypi.org/project/pdm-backend)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/README.md 
new/pdm_backend-2.1.8/README.md
--- old/pdm_backend-2.1.0/README.md     2023-06-08 10:15:04.705250500 +0200
+++ new/pdm_backend-2.1.8/README.md     2023-12-27 08:00:03.135141100 +0100
@@ -5,7 +5,7 @@
 
[![PyPI](https://img.shields.io/pypi/v/pdm-backend?label=PyPI)](https://pypi.org/project/pdm-backend)
 
[![Tests](https://github.com/pdm-project/pdm-backend/actions/workflows/ci.yml/badge.svg)](https://github.com/pdm-project/pdm-backend/actions/workflows/ci.yml)
 [![pre-commit.ci 
status](https://results.pre-commit.ci/badge/github/pdm-project/pdm-backend/main.svg)](https://results.pre-commit.ci/latest/github/pdm-project/pdm-backend/main)
-[![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
+[![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)][PDM]
 
 This is the backend for [PDM] projects that is fully-compatible with [PEP 517] 
spec, but you can also use it alone.
 It reads the metadata of [PEP 621] format and coverts it to [Core metadata].
@@ -13,11 +13,11 @@
 [pep 517]: https://www.python.org/dev/peps/pep-0517/
 [pep 621]: https://www.python.org/dev/peps/pep-0621/
 [Core metadata]: https://packaging.python.org/specifications/core-metadata/
-[PDM]: https://pdm.fming.dev
+[PDM]: https://pdm-project.org
 
 ## Links
 
-- [Documentation](https://pdm-backend.fming.dev)
+- [Documentation](https://backend.pdm-project.org)
 - [Changelog](https://github.com/pdm-project/pdm-backend/releases)
 - [PDM Documentation][PDM]
 - [PyPI](https://pypi.org/project/pdm-backend)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/pyproject.toml 
new/pdm_backend-2.1.8/pyproject.toml
--- old/pdm_backend-2.1.0/pyproject.toml        2023-06-08 10:15:25.005818400 
+0200
+++ new/pdm_backend-2.1.8/pyproject.toml        2023-12-27 08:00:14.563159000 
+0100
@@ -25,7 +25,7 @@
 dependencies = [
     "importlib-metadata>=3.6; python_version < \"3.10\"",
 ]
-version = "2.1.0"
+version = "2.1.8"
 
 [project.license]
 text = "MIT"
@@ -33,7 +33,7 @@
 [project.urls]
 Homepage = "https://github.com/pdm-project/pdm-backend";
 Repository = "https://github.com/pdm-project/pdm-backend";
-Documentation = "https://pdm-backend.fming.dev";
+Documentation = "https://backend.pdm-project.org";
 
 [tool.pdm.version]
 source = "scm"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/src/pdm/backend/base.py 
new/pdm_backend-2.1.8/src/pdm/backend/base.py
--- old/pdm_backend-2.1.0/src/pdm/backend/base.py       2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/base.py       2023-12-27 
08:00:03.139141000 +0100
@@ -14,7 +14,7 @@
 from pdm.backend.hooks import BuildHookInterface, Context
 from pdm.backend.hooks.version import DynamicVersionBuildHook
 from pdm.backend.structures import FileMap
-from pdm.backend.utils import import_module_at_path, is_python_package
+from pdm.backend.utils import expand_vars, import_module_at_path, 
is_python_package
 
 if TYPE_CHECKING:
     from typing import SupportsIndex
@@ -160,8 +160,23 @@
         if context.build_dir.exists():
             shutil.rmtree(context.build_dir)
 
+    def _fix_dependencies(self) -> None:
+        """Fix the dependencies and remove dynamic variables from the 
metadata"""
+        metadata = self.config.metadata
+        root = self.location.as_posix()
+        if metadata.get("dependencies"):
+            metadata["dependencies"] = [
+                expand_vars(dep, root) for dep in metadata["dependencies"]
+            ]
+        if metadata.get("optional-dependencies"):
+            for name, deps in metadata["optional-dependencies"].items():
+                metadata["optional-dependencies"][name] = [
+                    expand_vars(dep, root) for dep in deps
+                ]
+
     def initialize(self, context: Context) -> None:
         """Initialize the build context."""
+        self._fix_dependencies()
         self.call_hook("pdm_build_initialize", context)
 
     def get_files(self, context: Context) -> Iterable[tuple[str, Path]]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/src/pdm/backend/config.py 
new/pdm_backend-2.1.8/src/pdm/backend/config.py
--- old/pdm_backend-2.1.0/src/pdm/backend/config.py     2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/config.py     2023-12-27 
08:00:03.139141000 +0100
@@ -52,6 +52,8 @@
     def to_coremetadata(self) -> str:
         """Return the metadata as a Core Metadata string."""
         metadata = StandardMetadata.from_pyproject(self.data, 
project_dir=self.root)
+        # Fix the name field to unnormalized form.
+        metadata.name = self.metadata["name"]
         return str(metadata.as_rfc822())
 
     @classmethod
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/src/pdm/backend/editable.py 
new/pdm_backend-2.1.8/src/pdm/backend/editable.py
--- old/pdm_backend-2.1.0/src/pdm/backend/editable.py   2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/editable.py   2023-12-27 
08:00:03.139141000 +0100
@@ -35,8 +35,7 @@
 
     def pdm_build_update_files(self, context: Context, files: dict[str, Path]) 
-> None:
         packages: list[str] = 
context.config.convert_package_paths()["packages"]
-        package_dir = context.config.build_config.package_dir
-        proxied = {os.path.join(package_dir, p.replace(".", "/")) for p in 
packages}
+        proxied = {p.replace(".", "/") for p in packages}
         for relpath in list(files):
             if os.path.splitext(relpath)[1] in (".py", ".pyc", ".pyo"):
                 # All .py[cod] files are proxied
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/src/pdm/backend/hooks/base.py 
new/pdm_backend-2.1.8/src/pdm/backend/hooks/base.py
--- old/pdm_backend-2.1.0/src/pdm/backend/hooks/base.py 2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/hooks/base.py 2023-12-27 
08:00:03.139141000 +0100
@@ -61,11 +61,16 @@
         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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pdm_backend-2.1.0/src/pdm/backend/hooks/setuptools.py 
new/pdm_backend-2.1.8/src/pdm/backend/hooks/setuptools.py
--- old/pdm_backend-2.1.0/src/pdm/backend/hooks/setuptools.py   2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/hooks/setuptools.py   2023-12-27 
08:00:03.139141000 +0100
@@ -59,6 +59,15 @@
     return "\n".join(result)
 
 
+def _recursive_copy_files(src: Path, dest: Path) -> None:
+    if src.is_file():
+        shutil.copy2(src, dest)
+    else:
+        dest.mkdir(exist_ok=True)
+        for child in src.iterdir():
+            _recursive_copy_files(child, dest / child.name)
+
+
 class SetuptoolsBuildHook:
     """A build hook to run setuptools build command."""
 
@@ -84,11 +93,7 @@
             if not lib_dir:
                 return
             # copy the files under temp_dir/lib.* to context.build_dir
-            for file in lib_dir.iterdir():
-                if file.is_dir():
-                    shutil.copytree(file, build_dir / file.name)
-                else:
-                    shutil.copy2(file, build_dir)
+            _recursive_copy_files(lib_dir, build_dir)
 
     def _build_inplace(self, context: Context) -> None:
         setup_py = self.ensure_setup_py(context)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pdm_backend-2.1.0/src/pdm/backend/hooks/version/__init__.py 
new/pdm_backend-2.1.8/src/pdm/backend/hooks/version/__init__.py
--- old/pdm_backend-2.1.0/src/pdm/backend/hooks/version/__init__.py     
2023-06-08 10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/hooks/version/__init__.py     
2023-12-27 08:00:03.139141000 +0100
@@ -108,8 +108,15 @@
         write_template: str = "{}\n",
     ) -> None:
         """Write the resolved version to the file."""
-        if write_to is not None and context.target != "sdist":
-            target = context.build_dir / write_to
+        if write_to is not None:
+            if context.target == "sdist" and 
context.config.build_config.package_dir:
+                write_to = os.path.join(
+                    context.config.build_config.package_dir, write_to
+                )
+            if context.target == "editable":
+                target = Path(context.config.build_config.package_dir or ".") 
/ write_to
+            else:
+                target = context.build_dir / write_to
             if not target.parent.exists():
                 target.parent.mkdir(0o700, parents=True)
             with open(target, "w", encoding="utf-8", newline="") as fp:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/src/pdm/backend/sdist.py 
new/pdm_backend-2.1.8/src/pdm/backend/sdist.py
--- old/pdm_backend-2.1.0/src/pdm/backend/sdist.py      2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/sdist.py      2023-12-27 
08:00:03.139141000 +0100
@@ -11,23 +11,7 @@
 from pdm.backend._vendor.packaging.utils import canonicalize_name
 from pdm.backend.base import Builder
 from pdm.backend.hooks import Context
-from pdm.backend.utils import safe_version, to_filename
-
-
-def normalize_file_permissions(st_mode: int) -> int:
-    """
-    Normalizes the permission bits in the st_mode field from stat to 644/755
-
-    Popular VCSs only track whether a file is executable or not. The exact
-    permissions can vary on systems with different umasks. Normalising
-    to 644 (non executable) or 755 (executable) makes builds more reproducible.
-    """
-    # Set 644 permissions, leaving higher bits of st_mode unchanged
-    new_mode = (st_mode | 0o644) & ~0o133
-    if st_mode & 0o100:
-        new_mode |= 0o111  # Executable: 644 -> 755
-
-    return new_mode
+from pdm.backend.utils import normalize_file_permissions, safe_version, 
to_filename
 
 
 def clean_tarinfo(tar_info: tarfile.TarInfo) -> tarfile.TarInfo:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/src/pdm/backend/utils.py 
new/pdm_backend-2.1.8/src/pdm/backend/utils.py
--- old/pdm_backend-2.1.0/src/pdm/backend/utils.py      2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/utils.py      2023-12-27 
08:00:03.139141000 +0100
@@ -198,7 +198,7 @@
         quote: Callable[[str], str] = urllib.parse.quote
     else:
         quote = str
-    line = line.replace("${PROJECT_ROOT}", quote(root).lstrip("/"))
+    line = line.replace("file:///${PROJECT_ROOT}", Path(root).as_uri())
 
     def replace_func(match: Match[str]) -> str:
         rv = os.getenv(match.group(1))
@@ -219,3 +219,18 @@
     module = importlib.util.module_from_spec(spec)
     spec.loader.exec_module(module)  # type: ignore
     return module
+
+
+def normalize_file_permissions(st_mode: int) -> int:
+    """
+    Normalizes the permission bits in the st_mode field from stat to 644/755
+    Popular VCSs only track whether a file is executable or not. The exact
+    permissions can vary on systems with different umasks. Normalising
+    to 644 (non executable) or 755 (executable) makes builds more reproducible.
+    """
+    # Set 644 permissions, leaving higher bits of st_mode unchanged
+    new_mode = (st_mode | 0o644) & ~0o133
+    if st_mode & 0o100:
+        new_mode |= 0o111  # Executable: 644 -> 755
+
+    return new_mode
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/src/pdm/backend/wheel.py 
new/pdm_backend-2.1.8/src/pdm/backend/wheel.py
--- old/pdm_backend-2.1.0/src/pdm/backend/wheel.py      2023-06-08 
10:15:04.709250500 +0200
+++ new/pdm_backend-2.1.8/src/pdm/backend/wheel.py      2023-12-27 
08:00:03.139141000 +0100
@@ -4,6 +4,7 @@
 import hashlib
 import io
 import os
+import posixpath
 import shutil
 import stat
 import sys
@@ -20,10 +21,11 @@
 from pdm.backend.base import Builder
 from pdm.backend.hooks import Context
 from pdm.backend.hooks.setuptools import SetuptoolsBuildHook
+from pdm.backend.structures import FileMap
 from pdm.backend.utils import (
-    expand_vars,
     get_abi_tag,
     get_platform,
+    normalize_file_permissions,
     safe_version,
     to_filename,
 )
@@ -97,46 +99,39 @@
             plat_name = self.config_settings["--plat-name"]
         return python_tag, py_limited_api, plat_name
 
-    def _fix_dependencies(self) -> None:
-        """Fix the dependencies and remove dynamic variables from the 
metadata"""
-        metadata = self.config.metadata
-        root = self.location.as_posix()
-        if metadata.get("dependencies"):
-            metadata["dependencies"] = [
-                expand_vars(dep, root) for dep in metadata["dependencies"]
-            ]
-        if metadata.get("optional-dependencies"):
-            for name, deps in metadata["optional-dependencies"].items():
-                metadata["optional-dependencies"][name] = [
-                    expand_vars(dep, root) for dep in deps
-                ]
-
-    def initialize(self, context: Context) -> None:
-        self._fix_dependencies()
-        return super().initialize(context)
-
     def prepare_metadata(self, metadata_directory: str) -> Path:
         """Write the dist-info files under the given directory"""
         context = self.build_context(Path(metadata_directory))
         self.initialize(context)
         return self._write_dist_info(Path(metadata_directory))
 
-    def get_files(self, context: Context) -> Iterable[tuple[str, Path]]:
+    def _collect_files(self, context: Context) -> FileMap:
         package_dir = self.config.build_config.package_dir
-        for relpath, path in super().get_files(context):
+        result = FileMap()
+
+        def clean_prefix(relpath: str) -> str:
             # remove the package-dir part from the relative paths
             if package_dir and relpath.startswith(package_dir + "/"):
                 relpath = relpath[len(package_dir) + 1 :]
-            yield relpath, path
+            return relpath
+
+        result.update(
+            (clean_prefix(k), v) for k, v in 
super()._collect_files(context).items()
+        )
+        return result
+
+    def get_files(self, context: Context) -> Iterable[tuple[str, Path]]:
+        yield from super().get_files(context)
         yield from self._get_metadata_files(context)
         yield from self._get_wheel_data(context)
 
     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 = (
@@ -150,7 +145,12 @@
         self, context: Context, files: Iterable[tuple[str, Path]]
     ) -> Path:
         records: list[RecordEntry] = []
-        with tempfile.NamedTemporaryFile(suffix=".whl", delete=False) as fp:
+        fd, temp_name = tempfile.mkstemp(suffix=".whl")
+        st_mode = os.stat(temp_name).st_mode
+        new_mode = normalize_file_permissions(st_mode)
+        os.chmod(temp_name, new_mode)
+
+        with os.fdopen(fd, "w+b") as fp:
             with zipfile.ZipFile(fp, "w", compression=zipfile.ZIP_DEFLATED) as 
zf:
                 for rel_path, full_path in files:
                     records.append(self._add_file_to_zip(zf, rel_path, 
full_path))
@@ -159,7 +159,7 @@
         target = context.dist_dir / f"{self.name_version}-{self.tag}.whl"
         if target.exists():
             target.unlink()
-        shutil.move(fp.name, target)
+        shutil.move(temp_name, target)
         return target
 
     @property
@@ -235,8 +235,9 @@
     ) -> RecordEntry:
         self._show_add_file(rel_path, full_path)
         zi = zipfile.ZipInfo(rel_path, ZIPINFO_DATE_TIME)
-        st_mode = os.stat(full_path).st_mode
-        zi.external_attr = (st_mode & 0xFFFF) << 16  # Unix attributes
+        st_mode = full_path.stat().st_mode
+        new_mode = normalize_file_permissions(st_mode)
+        zi.external_attr = (new_mode & 0xFFFF) << 16  # Unix attributes
 
         if stat.S_ISDIR(st_mode):
             zi.external_attr |= 0x10  # MS-DOS directory flag
@@ -251,11 +252,9 @@
         zf: zipfile.ZipFile, zi: zipfile.ZipInfo, src: IO[bytes]
     ) -> str:
         hashsum = hashlib.sha256()
-        for buf in iter(lambda: src.read(2**16), b""):
-            hashsum.update(buf)
-
-        src.seek(0)
-        zf.writestr(zi, src.read(), compress_type=zipfile.ZIP_DEFLATED)
+        data = src.read()
+        hashsum.update(data)
+        zf.writestr(zi, data, compress_type=zipfile.ZIP_DEFLATED)
         return urlsafe_b64encode(hashsum.digest()).decode("ascii").rstrip("=")
 
     def _write_record(self, zf: zipfile.ZipFile, records: list[RecordEntry]) 
-> None:
@@ -296,13 +295,19 @@
     def _get_metadata_files(self, context: Context) -> Iterable[tuple[str, 
Path]]:
         """Generate the metadata files for the wheel."""
         if context.kwargs.get("metadata_directory"):
-            return 
self._iter_files_in_directory(context.kwargs["metadata_directory"])
+            return 
self._iter_metadata_files(context.kwargs["metadata_directory"])
         else:
             dist_info = self._write_dist_info(context.ensure_build_dir())
-            return self._iter_files_in_directory(str(dist_info))
+            return self._iter_metadata_files(str(dist_info))
 
-    def _iter_files_in_directory(self, path: str) -> Iterable[tuple[str, 
Path]]:
+    def _iter_metadata_files(self, path: str) -> Iterable[tuple[str, Path]]:
+        dist_info_name = self.dist_info_name
         for root, _, files in os.walk(path):
-            relroot = os.path.relpath(root, os.path.dirname(path))
             for file in files:
-                yield (Path(relroot, file).as_posix(), Path(root) / file)
+                # the relative path is concated with the dist-info name
+                # so that the dist info name is always consistent with the 
current build
+                # e.g. <path>/METADATA -> <name>-<version>.dist-info/METADATA
+                relpath = posixpath.join(
+                    dist_info_name, Path(root, 
file).relative_to(path).as_posix()
+                )
+                yield relpath, Path(root, file)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/tests/conftest.py 
new/pdm_backend-2.1.8/tests/conftest.py
--- old/pdm_backend-2.1.0/tests/conftest.py     2023-06-08 10:15:04.709250500 
+0200
+++ new/pdm_backend-2.1.8/tests/conftest.py     2023-12-27 08:00:03.139141000 
+0100
@@ -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
 
 
[email protected]()
-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", "[email protected]"])
-        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
[email protected]
+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
+
+
[email protected]
+def dist(tmp_path: Path) -> Path:
+    return tmp_path / "dist"
+
+
[email protected]
+def scm(fixture_project: Path) -> None:
+    subprocess.check_call(["git", "init"])
+    subprocess.check_call(["git", "config", "user.email", "[email protected]"])
+    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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pdm_backend-2.1.0/tests/fixtures/projects/demo-package-include/pyproject.toml
 
new/pdm_backend-2.1.8/tests/fixtures/projects/demo-package-include/pyproject.toml
--- 
old/pdm_backend-2.1.0/tests/fixtures/projects/demo-package-include/pyproject.toml
   2023-06-08 10:15:04.709250500 +0200
+++ 
new/pdm_backend-2.1.8/tests/fixtures/projects/demo-package-include/pyproject.toml
   2023-12-27 08:00:03.139141000 +0100
@@ -31,4 +31,4 @@
 source-includes = ["scripts/"]
 
 [tool.pdm.build.wheel-data]
-scripts = ["scripts/*"]
+scripts = ["scripts/**/*"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pdm_backend-2.1.0/tests/fixtures/projects/demo-package-include/scripts/data/my_script.sh
 
new/pdm_backend-2.1.8/tests/fixtures/projects/demo-package-include/scripts/data/my_script.sh
--- 
old/pdm_backend-2.1.0/tests/fixtures/projects/demo-package-include/scripts/data/my_script.sh
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/pdm_backend-2.1.8/tests/fixtures/projects/demo-package-include/scripts/data/my_script.sh
        2023-12-27 08:00:03.143141300 +0100
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Hello world"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pdm_backend-2.1.0/tests/fixtures/projects/demo-package-include/scripts/my_script.sh
 
new/pdm_backend-2.1.8/tests/fixtures/projects/demo-package-include/scripts/my_script.sh
--- 
old/pdm_backend-2.1.0/tests/fixtures/projects/demo-package-include/scripts/my_script.sh
     2023-06-08 10:15:04.709250500 +0200
+++ 
new/pdm_backend-2.1.8/tests/fixtures/projects/demo-package-include/scripts/my_script.sh
     1970-01-01 01:00:00.000000000 +0100
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-echo "Hello world"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/tests/test_api.py 
new/pdm_backend-2.1.8/tests/test_api.py
--- old/pdm_backend-2.1.0/tests/test_api.py     2023-06-08 10:15:04.713250400 
+0200
+++ new/pdm_backend-2.1.8/tests/test_api.py     2023-12-27 08:00:03.143141300 
+0100
@@ -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
+from tests.testutils import get_tarball_names, get_wheel_names
 
+pytestmark = pytest.mark.usefixtures("fixture_project")
 
-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)
-        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
-
-
-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"
-
-        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
[email protected]("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
+
+
[email protected]("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
+
+
[email protected]("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
+
+
[email protected]("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 "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_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
[email protected]("name", ["demo-package-include"])
+def test_build_package_data_relative(dist: Path, fixture_project: Path) -> 
None:
+    from pdm.backend.config import tomli_w, tomllib
+
+    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
+        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
 
 
-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()
[email protected]("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"
+
+    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
+
+
[email protected]("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"
+
+    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
+
+
[email protected]("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()
+
+
[email protected]("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")
 
+    wheel_name = api.build_wheel(
+        dist.as_posix(), metadata_directory=str(dist / dist_info)
+    )
 
-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")
+    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"
+
+
[email protected]("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
+
+
[email protected]("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"'
+
+
[email protected]("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"'
+
+
[email protected]("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
 
-        wheel_name = api.build_wheel(
-            tmp_path.as_posix(), metadata_directory=str(tmp_path / dist_info)
+        metadata = email.message_from_bytes(
+            zf.read("demo_package-0.1.0+editable.dist-info/METADATA")
         )
+        assert "editables" in metadata.get_all("Requires-Dist", [])
 
-        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"
-
-
-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
-
-        zip_names = get_wheel_names(tmp_path / wheel_name)
-        assert "foo_module.py" in zip_names
-
-
-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"]
-
-        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)
-
-        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"'
-
-
-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)
-
-        tar_names = get_tarball_names(tmp_path / 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"'
-
-
-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
+        pth_content = zf.read("demo_package.pth").decode("utf-8").strip()
+        assert pth_content == "import _editable_impl_demo_package"
 
-            metadata = email.message_from_bytes(
-                zf.read("demo_package-0.1.0+editable.dist-info/METADATA")
+        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())
             )
-            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())
-                )
[email protected]("name", ["demo-src-package-include"])
+def test_build_editable_src(dist: Path, fixture_project: Path) -> None:
+    wheel_name = api.build_editable(dist.as_posix())
+
+    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 (
+            "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())
-                )
-            )
-
[email protected]("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")
+
+    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
 
-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())
-
-        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(project.resolve())
+        pth_content = zf.read("demo_package.pth").decode("utf-8").strip()
+        assert pth_content == str(fixture_project.resolve())
 
 
-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
[email protected]("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_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"
[email protected]("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"
 
-        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
[email protected]("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
 
 
-def test_build_wheel_write_version_to_file(project_with_scm: Path) -> None:
-    builder = WheelBuilder(project_with_scm)
[email protected]("scm")
[email protected]("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)
[email protected]("scm")
[email protected]("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:
[email protected]("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"
 
 
[email protected]("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)
[email protected]("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"
 
 
[email protected]("scm")
 @pytest.mark.parametrize(
     "settings, cleanup", [("true", False), ("false", True), ("0", True), ("1", 
False)]
 )
[email protected]("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)
 
 
[email protected]("scm")
 @pytest.mark.parametrize(
     "settings, cleanup", [("", False), (True, False), (None, False)]
 )
[email protected]("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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/tests/test_utils.py 
new/pdm_backend-2.1.8/tests/test_utils.py
--- old/pdm_backend-2.1.0/tests/test_utils.py   2023-06-08 10:15:04.713250400 
+0200
+++ new/pdm_backend-2.1.8/tests/test_utils.py   2023-12-27 08:00:03.143141300 
+0100
@@ -1,10 +1,27 @@
+import os
+
+import pytest
+
 from pdm.backend.utils import expand_vars
 
+is_nt = os.name == "nt"
+
 
-def test_expand_vars(monkeypatch):
[email protected](is_nt, reason="Posix path")
+def test_expand_vars_posix(monkeypatch):
     monkeypatch.setenv("FOO", "foo=a")
     monkeypatch.setenv("BAR", "bar")
     root = "/abc/def"
 
     line = "file:///${PROJECT_ROOT}/${FOO}:${BAR}:${BAZ}"
     assert expand_vars(line, root) == "file:///abc/def/foo%3Da:bar:${BAZ}"
+
+
[email protected](not is_nt, reason="Windows path")
+def test_expand_vars_win(monkeypatch):
+    monkeypatch.setenv("FOO", "foo=a")
+    monkeypatch.setenv("BAR", "bar")
+    root = "C:/abc/def"
+
+    line = "file:///${PROJECT_ROOT}/${FOO}:${BAR}:${BAZ}"
+    assert expand_vars(line, root) == "file:///C:/abc/def/foo%3Da:bar:${BAZ}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pdm_backend-2.1.0/tests/testutils.py 
new/pdm_backend-2.1.8/tests/testutils.py
--- old/pdm_backend-2.1.0/tests/testutils.py    2023-06-08 10:15:04.713250400 
+0200
+++ new/pdm_backend-2.1.8/tests/testutils.py    2023-12-27 08:00:03.143141300 
+0100
@@ -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_wheel_names(path: Path) -> List[str]:
     with zipfile.ZipFile(path) as zf:
         return zf.namelist()
-
-
[email protected]
-def build_fixture_project(project_name: str) -> Iterator[Path]:
-    project = FIXTURES / "projects" / project_name
-    with utils.cd(project):
-        yield project

Reply via email to