Skip to content

Commit

Permalink
fix: install headers into package namespace
Browse files Browse the repository at this point in the history
* fix: install headers into package namespace

Signed-off-by: Frost Ming <[email protected]>

* add compatible handling

Signed-off-by: Frost Ming <[email protected]>

* fix wrapper

Signed-off-by: Frost Ming <[email protected]>
  • Loading branch information
frostming authored Jan 19, 2024
1 parent b95c31a commit abde992
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 11 deletions.
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

0 comments on commit abde992

Please sign in to comment.