Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: install headers into package namespace #2574

Merged
merged 3 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/pdm/cli/commands/fix/fixers.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def get_message(self) -> str:
dist = str(package_type == "library").lower()
return (
rf'[success]package-type = "{package_type}"[/] has been renamed to '
rf"[info]distribution = {dist}[/] under \[tool.pdm\] table"
rf"[info]distribution = {dist}[/] under \[tool.pdm] table"
)

def check(self) -> bool:
Expand Down
34 changes: 29 additions & 5 deletions src/pdm/environments/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import abc
import functools
import os
import re
import shutil
Expand All @@ -10,13 +11,13 @@
from contextlib import contextmanager
from functools import cached_property, partial
from pathlib import Path
from typing import TYPE_CHECKING, Generator
from typing import TYPE_CHECKING, Generator, no_type_check

from pdm.exceptions import BuildError, PdmUsageError
from pdm.models.in_process import get_pep508_environment, get_python_abis, get_uname, sysconfig_get_platform
from pdm.models.python import PythonInfo
from pdm.models.working_set import WorkingSet
from pdm.utils import get_trusted_hosts, is_pip_compatible_with_python
from pdm.utils import deprecation_warning, get_trusted_hosts, is_pip_compatible_with_python

if TYPE_CHECKING:
import unearth
Expand All @@ -26,11 +27,31 @@
from pdm.project import Project


@no_type_check
def get_paths_wrapper(get_paths): # pragma: no cover
@functools.wraps(get_paths)
def wrapped(self: BaseEnvironment, dist_name: str | None = None) -> dict[str, str]:
result = get_paths(self)
if dist_name and "headers" in result:
result["headers"] = os.path.join(result["headers"], dist_name)
return result

return wrapped


class BaseEnvironment(abc.ABC):
"""Environment dependent stuff related to the selected Python interpreter."""

is_local = False

def __init_subclass__(cls) -> None:
import inspect

get_paths_params = inspect.signature(cls.get_paths).parameters
if "dist_name" not in get_paths_params: # pragma: no cover
deprecation_warning("get_paths() should accept a `dist_name` argument", stacklevel=3)
cls.get_paths = get_paths_wrapper(cls.get_paths) # type: ignore[method-assign]

def __init__(self, project: Project, *, python: str | None = None) -> None:
"""
:param project: the project instance
Expand All @@ -55,8 +76,11 @@ def interpreter(self) -> PythonInfo:
return self._interpreter

@abc.abstractmethod
def get_paths(self) -> dict[str, str]:
"""Get paths like ``sysconfig.get_paths()`` for installation."""
def get_paths(self, dist_name: str | None = None) -> dict[str, str]:
"""Get paths like ``sysconfig.get_paths()`` for installation.
:param dist_name: The package name to be installed, if any.
"""
...

@property
Expand Down Expand Up @@ -252,7 +276,7 @@ class BareEnvironment(BaseEnvironment):
def __init__(self, project: Project) -> None:
super().__init__(project, python=sys.executable)

def get_paths(self) -> dict[str, str]:
def get_paths(self, dist_name: str | None = None) -> dict[str, str]:
return {}

def get_working_set(self) -> WorkingSet:
Expand Down
6 changes: 4 additions & 2 deletions src/pdm/environments/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,10 @@ def packages_path(self) -> Path:
pypackages.joinpath(subdir).mkdir(exist_ok=True, parents=True)
return pypackages

def get_paths(self) -> dict[str, str]:
return pdm_scheme(self.packages_path.as_posix())
def get_paths(self, dist_name: str | None = None) -> dict[str, str]:
scheme = pdm_scheme(self.packages_path.as_posix())
scheme["headers"] = os.path.join(scheme["headers"], dist_name or "UNKNOWN")
return scheme

def update_shebangs(self, new_path: str) -> None:
"""Update the shebang lines"""
Expand Down
6 changes: 5 additions & 1 deletion src/pdm/environments/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, project: Project, *, python: str | None = None, prefix: str |
super().__init__(project, python=python)
self.prefix = prefix

def get_paths(self) -> dict[str, str]:
def get_paths(self, dist_name: str | None = None) -> dict[str, str]:
is_venv = self.interpreter.get_venv() is not None
if self.prefix is not None:
replace_vars = {"base": self.prefix, "platbase": self.prefix}
Expand All @@ -27,9 +27,13 @@ def get_paths(self) -> dict[str, str]:
replace_vars = None
kind = "user" if not is_venv and self.project.global_config["global_project.user_site"] else "default"
paths = get_sys_config_paths(str(self.interpreter.executable), replace_vars, kind=kind)
if not dist_name and not is_venv:
dist_name = "UNKNOWN"
if is_venv and self.prefix is None:
python_xy = f"python{self.interpreter.identifier}"
paths["include"] = os.path.join(paths["data"], "include", "site", python_xy)
if dist_name:
paths["include"] = os.path.join(paths["include"], dist_name)
paths["prefix"] = paths["data"]
paths["headers"] = paths["include"]
return paths
Expand Down
10 changes: 8 additions & 2 deletions src/pdm/installers/installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ def _is_python_package(root: str | Path) -> bool:
return False


def _get_dist_name(wheel_path: str) -> str:
from packaging.utils import parse_wheel_filename

return parse_wheel_filename(os.path.basename(wheel_path))[0]


_namespace_package_lines = frozenset(
[
# pkg_resources style
Expand Down Expand Up @@ -192,7 +198,7 @@ def install_wheel(wheel: str, environment: BaseEnvironment, direct_url: dict[str
if direct_url is not None:
additional_metadata = {"direct_url.json": json.dumps(direct_url, indent=2).encode()}
destination = InstallDestination(
scheme_dict=environment.get_paths(),
scheme_dict=environment.get_paths(_get_dist_name(wheel)),
interpreter=str(environment.interpreter.executable),
script_kind=_get_kind(environment),
)
Expand Down Expand Up @@ -263,7 +269,7 @@ def skip_files(source: WheelFile, element: WheelContentElement) -> bool:
additional_contents.append(((filename, "", str(len(stream.getvalue()))), stream, False))

destination = InstallDestination(
scheme_dict=environment.get_paths(),
scheme_dict=environment.get_paths(_get_dist_name(wheel)),
interpreter=interpreter,
script_kind=script_kind,
link_to=lib_path,
Expand Down