summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnderson Bravalheri <andersonbravalheri@gmail.com>2023-04-21 16:42:49 +0100
committerAnderson Bravalheri <andersonbravalheri@gmail.com>2023-05-05 18:26:25 +0100
commit80fca53293e3400c22f0bb8fde17002b36a96ee8 (patch)
treecefdba94285299697641d3a6a4f73a198177d2ac
parent3deabb76aede789c3404d041dc9bda7c761e1af9 (diff)
downloadpython-setuptools-git-80fca53293e3400c22f0bb8fde17002b36a96ee8.tar.gz
Avoid using bdist_wheel in editable_wheel
-rw-r--r--setuptools/command/editable_wheel.py134
1 files changed, 74 insertions, 60 deletions
diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py
index ffcc2cc0..371fd5a6 100644
--- a/setuptools/command/editable_wheel.py
+++ b/setuptools/command/editable_wheel.py
@@ -15,8 +15,9 @@ import os
import shutil
import sys
import traceback
-from contextlib import suppress
+from contextlib import ExitStack, suppress
from enum import Enum
+from functools import lru_cache
from inspect import cleandoc
from itertools import chain
from pathlib import Path
@@ -32,6 +33,7 @@ from typing import (
Tuple,
TypeVar,
Union,
+ cast,
)
from .. import (
@@ -41,6 +43,8 @@ from .. import (
errors,
namespaces,
)
+from .._wheelbuilder import WheelBuilder
+from ..extern.packaging.tags import sys_tags
from ..discovery import find_package_path
from ..dist import Distribution
from ..warnings import (
@@ -50,9 +54,6 @@ from ..warnings import (
)
from .build_py import build_py as build_py_cls
-if TYPE_CHECKING:
- from wheel.wheelfile import WheelFile # noqa
-
if sys.version_info >= (3, 8):
from typing import Protocol
elif TYPE_CHECKING:
@@ -62,6 +63,7 @@ else:
_Path = Union[str, Path]
_P = TypeVar("_P", bound=_Path)
+_Tag = Tuple[str, str, str]
_logger = logging.getLogger(__name__)
@@ -116,6 +118,20 @@ Options like `package-data`, `include/exclude-package-data` or
"""
+@lru_cache(maxsize=0)
+def _any_compat_tag() -> _Tag:
+ """
+ PEP 660 does not require the tag to be identical to the tag that will be used
+ in production, it only requires the tag to be compatible with the current system.
+ Moreover, PEP 660 also guarantees that the generated wheel file should be used in
+ the same system where it was produced.
+ Therefore we can just be pragmatic and pick one of the compatible tags.
+ """
+ tag = next(sys_tags())
+ components = (tag.interpreter, tag.abi, tag.platform)
+ return cast(_Tag, tuple(map(_normalization.filename_component, components)))
+
+
class editable_wheel(Command):
"""Build 'editable' wheel for development.
This command is private and reserved for internal use of setuptools,
@@ -141,34 +157,34 @@ class editable_wheel(Command):
self.project_dir = dist.src_root or os.curdir
self.package_dir = dist.package_dir or {}
self.dist_dir = Path(self.dist_dir or os.path.join(self.project_dir, "dist"))
+ if self.dist_info_dir:
+ self.dist_info_dir = Path(self.dist_info_dir)
def run(self):
try:
self.dist_dir.mkdir(exist_ok=True)
- self._ensure_dist_info()
-
- # Add missing dist_info files
- self.reinitialize_command("bdist_wheel")
- bdist_wheel = self.get_finalized_command("bdist_wheel")
- bdist_wheel.write_wheelfile(self.dist_info_dir)
-
- self._create_wheel_file(bdist_wheel)
+ self._create_wheel_file()
except Exception:
traceback.print_exc()
project = self.distribution.name or self.distribution.get_name()
_DebuggingTips.emit(project=project)
raise
- def _ensure_dist_info(self):
+ def _get_dist_info_name(self, tmp_dir):
if self.dist_info_dir is None:
dist_info = self.reinitialize_command("dist_info")
- dist_info.output_dir = self.dist_dir
+ dist_info.output_dir = tmp_dir
dist_info.ensure_finalized()
- dist_info.run()
self.dist_info_dir = dist_info.dist_info_dir
- else:
- assert str(self.dist_info_dir).endswith(".dist-info")
- assert Path(self.dist_info_dir, "METADATA").exists()
+ return dist_info.name
+
+ assert str(self.dist_info_dir).endswith(".dist-info")
+ assert (self.dist_info_dir / "METADATA").exists()
+ return self.dist_info_dir.name[: -len(".dist-info")]
+
+ def _ensure_dist_info(self):
+ if not Path(self.dist_info_dir, "METADATA").exists():
+ self.distribution.run_command("dist_info")
def _install_namespaces(self, installation_dir, pth_prefix):
# XXX: Only required to support the deprecated namespace practice
@@ -208,8 +224,7 @@ class editable_wheel(Command):
scripts = str(Path(unpacked_wheel, f"{name}.data", "scripts"))
# egg-info may be generated again to create a manifest (used for package data)
- egg_info = dist.reinitialize_command("egg_info", reinit_subcommands=True)
- egg_info.egg_base = str(tmp_dir)
+ egg_info = dist.get_command_obj("egg_info")
egg_info.ignore_egg_info_in_manifest = True
build = dist.reinitialize_command("build", reinit_subcommands=True)
@@ -321,31 +336,29 @@ class editable_wheel(Command):
# needs work.
)
- def _create_wheel_file(self, bdist_wheel):
- from wheel.wheelfile import WheelFile
-
- dist_info = self.get_finalized_command("dist_info")
- dist_name = dist_info.name
- tag = "-".join(bdist_wheel.get_tag())
- build_tag = "0.editable" # According to PEP 427 needs to start with digit
- archive_name = f"{dist_name}-{build_tag}-{tag}.whl"
- wheel_path = Path(self.dist_dir, archive_name)
- if wheel_path.exists():
- wheel_path.unlink()
-
- unpacked_wheel = TemporaryDirectory(suffix=archive_name)
- build_lib = TemporaryDirectory(suffix=".build-lib")
- build_tmp = TemporaryDirectory(suffix=".build-temp")
-
- with unpacked_wheel as unpacked, build_lib as lib, build_tmp as tmp:
- unpacked_dist_info = Path(unpacked, Path(self.dist_info_dir).name)
- shutil.copytree(self.dist_info_dir, unpacked_dist_info)
- self._install_namespaces(unpacked, dist_info.name)
+ def _create_wheel_file(self):
+ with ExitStack() as stack:
+ lib = stack.enter_context(TemporaryDirectory(suffix=".build-lib"))
+ tmp = stack.enter_context(TemporaryDirectory(suffix=".build-temp"))
+ dist_name = self._get_dist_info_name(tmp)
+
+ tag = "-".join(_any_compat_tag()) # Loose tag for the sake of simplicity...
+ build_tag = "0.editable" # According to PEP 427 needs to start with digit.
+ archive_name = f"{dist_name}-{build_tag}-{tag}.whl"
+ wheel_path = Path(self.dist_dir, archive_name)
+ if wheel_path.exists():
+ wheel_path.unlink()
+
+ unpacked = stack.enter_context(TemporaryDirectory(suffix=archive_name))
+ self._install_namespaces(unpacked, dist_name)
files, mapping = self._run_build_commands(dist_name, unpacked, lib, tmp)
- strategy = self._select_strategy(dist_name, tag, lib)
- with strategy, WheelFile(wheel_path, "w") as wheel_obj:
- strategy(wheel_obj, files, mapping)
- wheel_obj.write_files(unpacked)
+
+ strategy = stack.enter_context(self._select_strategy(dist_name, tag, lib))
+ builder = stack.enter_context(WheelBuilder(wheel_path))
+ strategy(builder, files, mapping)
+ builder.add_tree(unpacked, exclude=["*.dist-info/*", "*.egg-info/*"])
+ self._ensure_dist_info()
+ builder.add_tree(self.dist_info_dir, prefix=self.dist_info_dir.name)
return wheel_path
@@ -383,7 +396,7 @@ class editable_wheel(Command):
class EditableStrategy(Protocol):
- def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
+ def __call__(self, wheel: WheelBuilder, files: List[str], mapping: Dict[str, str]):
...
def __enter__(self):
@@ -399,10 +412,9 @@ class _StaticPth:
self.name = name
self.path_entries = path_entries
- def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
+ def __call__(self, wheel: WheelBuilder, files: List[str], mapping: Dict[str, str]):
entries = "\n".join((str(p.resolve()) for p in self.path_entries))
- contents = bytes(f"{entries}\n", "utf-8")
- wheel.writestr(f"__editable__.{self.name}.pth", contents)
+ wheel.new_file(f"__editable__.{self.name}.pth", f"{entries}\n")
def __enter__(self):
msg = f"""
@@ -426,8 +438,10 @@ class _LinkTree(_StaticPth):
By collocating ``auxiliary_dir`` and the original source code, limitations
with hardlinks should be avoided.
"""
+
def __init__(
- self, dist: Distribution,
+ self,
+ dist: Distribution,
name: str,
auxiliary_dir: _Path,
build_lib: _Path,
@@ -437,7 +451,7 @@ class _LinkTree(_StaticPth):
self._file = dist.get_command_obj("build_py").copy_file
super().__init__(dist, name, [self.auxiliary_dir])
- def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
+ def __call__(self, wheel: WheelBuilder, files: List[str], mapping: Dict[str, str]):
self._create_links(files, mapping)
super().__call__(wheel, files, mapping)
@@ -492,24 +506,24 @@ class _TopLevelFinder:
self.dist = dist
self.name = name
- def __call__(self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]):
+ def __call__(self, wheel: WheelBuilder, files: List[str], mapping: Dict[str, str]):
src_root = self.dist.src_root or os.curdir
top_level = chain(_find_packages(self.dist), _find_top_level_modules(self.dist))
package_dir = self.dist.package_dir or {}
roots = _find_package_roots(top_level, package_dir, src_root)
- namespaces_: Dict[str, List[str]] = dict(chain(
- _find_namespaces(self.dist.packages or [], roots),
- ((ns, []) for ns in _find_virtual_namespaces(roots)),
- ))
+ namespaces_: Dict[str, List[str]] = dict(
+ chain(
+ _find_namespaces(self.dist.packages or [], roots),
+ ((ns, []) for ns in _find_virtual_namespaces(roots)),
+ )
+ )
name = f"__editable__.{self.name}.finder"
finder = _normalization.safe_identifier(name)
- content = bytes(_finder_template(name, roots, namespaces_), "utf-8")
- wheel.writestr(f"{finder}.py", content)
-
- content = bytes(f"import {finder}; {finder}.install()", "utf-8")
- wheel.writestr(f"__editable__.{self.name}.pth", content)
+ wheel.new_file(f"{finder}.py", _finder_template(name, roots, namespaces_))
+ pth = f"__editable__.{self.name}.pth"
+ wheel.new_file(pth, f"import {finder}; {finder}.install()")
def __enter__(self):
msg = "Editable install will be performed using a meta path finder.\n"