diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e5c11d..da95e64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: hooks: - id: black - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 additional_dependencies: @@ -21,13 +21,13 @@ repos: exclude: ^.*/?setup\.py$ - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.982 + rev: v0.991 hooks: - id: mypy exclude: ^(src/pdm/backend/_vendor|tests|scripts) - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.3 hooks: - id: pyupgrade args: diff --git a/docs/build_config.md b/docs/build_config.md index b633c5d..a49be08 100644 --- a/docs/build_config.md +++ b/docs/build_config.md @@ -136,7 +136,7 @@ Some build frontends such as [build] and [pdm] supports passing options from com - `--python-tag=` Override the python implementation compatibility tag(e.g. `cp37`, `py3`, `pp3`) - `--py-limited-api=` Python tag (`cp32`|`cp33`|`cpNN`) for abi3 wheel tag - `--plat-name=` Override the platform name(e.g. `win_amd64`, `manylinux2010_x86_64`) -- `no-clean` Don't clean the build directory before the build starts +- `no-clean-build` Don't clean the build directory before the build starts, this can also work by setting env var `PDM_BUILD_NO_CLEAN` to `1`. For example, you can supply these options with [build]: diff --git a/docs/hooks.md b/docs/hooks.md index e02c6a4..68488ae 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -70,18 +70,33 @@ Or, you can generate it anywhere and include the path explicitly. ## Enable the hook for a specific build target -Sometimes you only want to activate the hook for a specific hook, you can define the `is_enabled()` hook: +Sometimes you only want to activate the hook for a specific hook, you can define the `pdm_build_hook_enabled()` hook: === "pdm_build.py" ```python - def is_enabled(context): + def pdm_build_hook_enabled(context): # Only enable for sdist builds return context.target == "sdist" ``` You can also look at the `context` object inside a specific hook to determine it should be called. + +## Build hooks flow + +The hooks are called in the following order: + +```mermaid +flowchart TD + A{{pdm_build_hook_enabled?}}-->pdm_build_initialize + pdm_build_initialize-.run-setuptools.-> pdm_build_update_setup_kwargs + pdm_build_update_setup_kwargs-.->pdm_build_update_files + pdm_build_update_files --> pdm_build_finalize +``` + +Read the [API references](./api.md) for more details. + ## Distribute the hook as a plugin If you want to share your hook with others, you can change the hook script file into a Python package and upload it to PyPI. @@ -119,12 +134,12 @@ pdm-build-mypyc ```python class MypycBuildHook: + def pdm_build_hook_enabled(self, context): + return context.target == "wheel" + def pdm_build_initialize(self, context): context.ensure_build_dir() mypyc_build(context.build_dir) - - def is_enabled(self, context): - return context.target == "wheel" ``` diff --git a/src/pdm/backend/__init__.py b/src/pdm/backend/__init__.py index b20f313..b56e688 100644 --- a/src/pdm/backend/__init__.py +++ b/src/pdm/backend/__init__.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Any, Mapping -__version__ = "2.0.0a2" +__version__ = "2.0.0a3" def get_requires_for_build_wheel( diff --git a/src/pdm/backend/base.py b/src/pdm/backend/base.py index d451421..eb01933 100644 --- a/src/pdm/backend/base.py +++ b/src/pdm/backend/base.py @@ -126,8 +126,8 @@ def call_hook( ) -> None: """Call the hook on all registered hooks and skip if not implemented.""" for hook in self._hooks: - if hasattr(hook, "is_enabled"): - if not hook.is_enabled(context): + if hasattr(hook, "pdm_build_hook_enabled"): + if not hook.pdm_build_hook_enabled(context): continue if hasattr(hook, hook_name): getattr(hook, hook_name)(context, *args, **kwargs) @@ -182,7 +182,10 @@ def finalize(self, context: Context, artifact: Path) -> None: def build(self, build_dir: str, **kwargs: Any) -> Path: """Build the package and return the path to the artifact.""" context = self.build_context(Path(build_dir), **kwargs) - if not self.config_settings.get("no-clean"): + if ( + not self.config_settings.get("no-clean-build") + or os.getenv("PDM_BUILD_NO_CLEAN", "false").lower() != "false" + ): self.clean(context) self.initialize(context) files = self.get_files(context) diff --git a/src/pdm/backend/hooks/base.py b/src/pdm/backend/hooks/base.py index b96d832..c4d7897 100644 --- a/src/pdm/backend/hooks/base.py +++ b/src/pdm/backend/hooks/base.py @@ -49,7 +49,7 @@ class BuildHookInterface(Protocol): Custom hooks can implement part of the methods to provide corresponding abilities. """ - def is_enabled(self, context: Context) -> bool: + def pdm_build_hook_enabled(self, context: Context) -> bool: """Return True if the hook is enabled for the current build and context Parameters: @@ -107,6 +107,7 @@ def pdm_build_update_setup_kwargs( kwargs: The arguments to be passed to the setup() function Note: - This hook will be called in the subprocess of running setup.py + This hook will be called in the subprocess of running setup.py. + Any changes made to the context won't be written back. """ ... diff --git a/src/pdm/backend/hooks/setuptools.py b/src/pdm/backend/hooks/setuptools.py index d9e9908..977e6d9 100644 --- a/src/pdm/backend/hooks/setuptools.py +++ b/src/pdm/backend/hooks/setuptools.py @@ -64,7 +64,7 @@ def _format_dict_list(data: dict[str, list[str]], indent: int = 4) -> str: class SetuptoolsBuildHook: """A build hook to run setuptools build command.""" - def is_enabled(self, context: Context) -> bool: + def pdm_build_hook_enabled(self, context: Context) -> bool: return context.target != "sdist" and context.config.build_config.run_setuptools def pdm_build_update_files(self, context: Context, files: dict[str, Path]) -> None: diff --git a/src/pdm/backend/wheel.py b/src/pdm/backend/wheel.py index 10adb9c..6b94527 100644 --- a/src/pdm/backend/wheel.py +++ b/src/pdm/backend/wheel.py @@ -11,7 +11,7 @@ import zipfile from base64 import urlsafe_b64encode from pathlib import Path -from typing import IO, Any, Iterable, Mapping, NamedTuple +from typing import IO, Any, Iterable, Mapping, NamedTuple, cast from pdm.backend import __version__ from pdm.backend._vendor.packaging import tags @@ -39,7 +39,7 @@ % __version__ ) -PY_LIMITED_API_PATTERN = r"cp3\d" +PY_LIMITED_API_PATTERN = r"cp3\d{1,2}" # Fix the date time for reproducible builds ZIPINFO_DEFAULT_DATE_TIME = (2016, 1, 1, 0, 0, 0) @@ -60,29 +60,28 @@ class WheelBuilder(Builder): hooks = Builder.hooks + [SetuptoolsBuildHook()] def __init__( - self, - location: str | Path, - config_settings: Mapping[str, Any] | None = None, + self, location: str | Path, config_settings: Mapping[str, Any] | None = None ) -> None: super().__init__(location, config_settings) - self._parse_config_settings() + self.__tag: str | None = None - def _parse_config_settings(self) -> None: - self.python_tag = None - self.py_limited_api = None - self.plat_name = None + def _get_platform_tags(self) -> tuple[str | None, str | None, str | None]: + python_tag: str | None = None + py_limited_api: str | None = None + plat_name: str | None = None if not self.config_settings: - return + return python_tag, py_limited_api, plat_name if "--python-tag" in self.config_settings: - self.python_tag = self.config_settings["--python-tag"] + python_tag = self.config_settings["--python-tag"] if "--py-limited-api" in self.config_settings: - self.py_limited_api = self.config_settings["--py-limited-api"] - if not re.match(PY_LIMITED_API_PATTERN, self.py_limited_api): + py_limited_api = cast(str, self.config_settings["--py-limited-api"]) + if not re.match(PY_LIMITED_API_PATTERN, py_limited_api): raise ValueError( "py-limited-api must match '%s'" % PY_LIMITED_API_PATTERN ) if "--plat-name" in self.config_settings: - self.plat_name = self.config_settings["--plat-name"] + plat_name = self.config_settings["--plat-name"] + return python_tag, py_limited_api, plat_name def prepare_metadata(self, metadata_directory: str) -> Path: """Write the dist-info files under the given directory""" @@ -154,16 +153,20 @@ def dist_info_name(self) -> str: @property def tag(self) -> str: - platform = self.plat_name - impl = self.python_tag + if self.__tag is None: + self.__tag = self._get_tag() + return self.__tag + + def _get_tag(self) -> str: + impl, abi, platform = self._get_platform_tags() is_purelib = self.config.build_config.is_purelib if not is_purelib: if not platform: platform = get_platform(self.location / "build") if not impl: impl = tags.interpreter_name() + tags.interpreter_version() - if self.py_limited_api and impl.startswith("cp3"): - impl = self.py_limited_api + if abi and impl.startswith("cp3"): # type: ignore[union-attr] + impl = abi abi_tag = "abi3" else: abi_tag = str(get_abi_tag()).lower() @@ -178,14 +181,14 @@ def tag(self) -> str: else: impl = "py3" - platform = platform.lower().replace("-", "_").replace(".", "_") + platform = platform.lower().replace("-", "_").replace(".", "_") # type: ignore tag = (impl, abi_tag, platform) if not is_purelib: supported_tags = [(t.interpreter, t.abi, platform) for t in tags.sys_tags()] assert ( tag in supported_tags ), f"would build wheel with unsupported tag {tag}" - return "-".join(tag) + return "-".join(tag) # type: ignore[arg-type] def _write_dist_info(self, parent: Path) -> Path: """write the dist-info directory and return the path to it"""