-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: use the plugin as backend hooksSigned-off-by: Frost Ming <me@fr…
…ostming.com> * feat: use the plugin as backend hooks Signed-off-by: Frost Ming <me@frostming.com>
- Loading branch information
Showing
27 changed files
with
1,687 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +0,0 @@ | ||
""" | ||
pdm-build-locked | ||
A PDM plugin that adds locked dependencies to optional-dependencies on build | ||
""" | ||
from pdm.core import Core | ||
from pdm.project.config import ConfigItem | ||
|
||
from .command import BuildCommand | ||
|
||
|
||
def plugin(core: Core) -> None: | ||
"""register pdm plugin | ||
Args: | ||
core: pdm core | ||
""" | ||
core.register_command(BuildCommand) | ||
core.add_config( | ||
"build-locked.lock", | ||
ConfigItem("Build this project with locked dependencies", False, env_var="PDM_BUILD_LOCKED"), | ||
) | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
import sys | ||
import warnings | ||
from pathlib import Path | ||
from typing import Any | ||
|
||
if sys.version_info >= (3, 11): | ||
import tomllib | ||
else: | ||
import tomli as tomllib # pragma: no cover | ||
|
||
|
||
class UnsupportedRequirement(ValueError): | ||
"""Requirement not complying with PEP 508""" | ||
|
||
|
||
def requirement_dict_to_string(req_dict: dict[str, Any]) -> str: | ||
"""Build a requirement string from a package item from pdm.lock | ||
Args: | ||
req_dict: The package item from pdm.lock | ||
Returns: | ||
A PEP 582 requirement string | ||
""" | ||
extra_string = f"[{','.join(extras)}]" if (extras := req_dict.get("extras", [])) else "" | ||
version_string = f"=={version}" if (version := req_dict.get("version")) else "" | ||
if "name" not in req_dict: | ||
raise UnsupportedRequirement(f"Missing name in requirement: {req_dict}") | ||
if "editable" in req_dict: | ||
raise UnsupportedRequirement(f"Editable requirement is not allowed: {req_dict}") | ||
if "path" in req_dict: | ||
raise UnsupportedRequirement(f"Local path requirement is not allowed: {req_dict}") | ||
|
||
url_string = "" | ||
if "url" in req_dict: | ||
url_string = f" @ {req_dict['url']}" | ||
elif "ref" in req_dict: # VCS requirement | ||
vcs, repo = next((k, v) for k, v in req_dict.items() if k in ("git", "svn", "bzr", "hg")) # pragma: no cover | ||
url_string = f" @ {vcs}+{repo}@{req_dict.get('revision', req_dict['ref'])}" | ||
if "subdirectory" in req_dict: | ||
url_string = f"{url_string}#subdirectory={req_dict['subdirectory']}" | ||
|
||
marker_string = f" ; {marker}" if (marker := req_dict.get("marker")) else "" | ||
return f"{req_dict['name']}{extra_string}{version_string}{url_string}{marker_string}" | ||
|
||
|
||
def get_locked_group_name(group: str) -> str: | ||
""" | ||
Get the name of the locked group corresponding to the original group | ||
default dependencies: locked | ||
optional dependency groups: {group}-locked | ||
Args: | ||
group: original group name | ||
Returns: | ||
locked group name | ||
""" | ||
group_name = "locked" | ||
if group != "default": | ||
group_name = f"{group}-{group_name}" | ||
|
||
return group_name | ||
|
||
|
||
def update_metadata_with_locked(metadata: dict[str, Any], root: Path) -> None: # pragma: no cover | ||
"""Inplace update the metadata(pyproject.toml) with the locked dependencies. | ||
Args: | ||
metadata (dict[str, Any]): The metadata dictionary | ||
root (Path): The path to the project root | ||
Raises: | ||
UnsupportedRequirement | ||
""" | ||
lockfile = root / "pdm.lock" | ||
if "PDM_LOCKFILE" in os.environ: | ||
lockfile = Path(os.environ["PDM_LOCKFILE"]) | ||
if not lockfile.exists(): | ||
warnings.warn("The lockfile doesn't exist, skip locking dependencies", UserWarning, stacklevel=1) | ||
return | ||
with lockfile.open("rb") as f: | ||
lockfile_content = tomllib.load(f) | ||
|
||
if "inherit_metadata" not in lockfile_content.get("metadata", {}).get("strategy", []): | ||
warnings.warn( | ||
"The lockfile doesn't support 'inherit_metadata' strategy, skip locking dependencies", | ||
UserWarning, | ||
stacklevel=1, | ||
) | ||
return | ||
|
||
groups = ["default"] | ||
optional_groups = list(metadata.get("optional-dependencies", {})) | ||
locked_groups = lockfile_content.get("metadata", {}).get("groups", []) | ||
groups.extend(optional_groups) | ||
for group in groups: | ||
locked_group = get_locked_group_name(group) | ||
if locked_group in optional_groups: | ||
# already exists, don't override | ||
continue | ||
if group not in locked_groups: | ||
print(f"Group {group} is not stored in the lockfile, skip locking dependencies for it.") | ||
continue | ||
requirements: list[str] = [] | ||
for package in lockfile_content.get("package", []): | ||
if group in package.get("groups", []): | ||
try: | ||
requirements.append(requirement_dict_to_string(package)) | ||
except UnsupportedRequirement as e: | ||
print(f"Skipping unsupported requirement: {e}") | ||
if not requirements: | ||
raise UnsupportedRequirement(f"No valid PEP 508 requirements are found for group {group}") | ||
metadata.setdefault("optional-dependencies", {})[locked_group] = requirements |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
from typing import TYPE_CHECKING | ||
|
||
from ._utils import update_metadata_with_locked | ||
|
||
if TYPE_CHECKING: | ||
from pdm.backend.hooks import BuildHookInterface | ||
from pdm.backend.hooks.base import Context | ||
else: | ||
BuildHookInterface = object | ||
|
||
|
||
class BuildLockedHook(BuildHookInterface): | ||
def pdm_build_hook_enabled(self, context: Context) -> bool: | ||
if os.getenv("PDM_BUILD_LOCKED", "false") != "false": | ||
return True | ||
return context.config.build_config.get("locked", False) | ||
|
||
def pdm_build_initialize(self, context: Context) -> None: | ||
static_fields = list(context.config.metadata) | ||
update_metadata_with_locked(context.config.metadata, context.root) | ||
new_fields = set(context.config.metadata) - set(static_fields) | ||
for field in new_fields: | ||
if field in context.config.metadata.get("dynamic", []): | ||
context.config.metadata["dynamic"].remove(field) |
Oops, something went wrong.