summaryrefslogtreecommitdiff
path: root/setuptools/command
diff options
context:
space:
mode:
authorAnderson Bravalheri <andersonbravalheri@gmail.com>2022-04-04 01:02:41 +0100
committerAnderson Bravalheri <andersonbravalheri@gmail.com>2022-06-15 16:43:45 +0100
commit5866b8563cc35d01d08053d8142f4c09255a07f3 (patch)
treecb14e55d74f22ef5c52fc1a01a8930e75613b751 /setuptools/command
parent3e9f4418d528228af7aba1a3bddbbf20b2327422 (diff)
downloadpython-setuptools-git-5866b8563cc35d01d08053d8142f4c09255a07f3.tar.gz
Rely on wheel and bdist_wheel for editable_wheel
Diffstat (limited to 'setuptools/command')
-rw-r--r--setuptools/command/editable_wheel.py151
1 files changed, 61 insertions, 90 deletions
diff --git a/setuptools/command/editable_wheel.py b/setuptools/command/editable_wheel.py
index f862e6a0..199dbd7c 100644
--- a/setuptools/command/editable_wheel.py
+++ b/setuptools/command/editable_wheel.py
@@ -4,28 +4,12 @@ Create a wheel that, when installed, will make the source package 'editable'
'setup.py develop'. Based on the setuptools develop command.
"""
-# TODO doesn't behave when called outside the hook
-
-import base64
import os
-import time
-from pathlib import Path
-
+import shutil
+import sys
from distutils.core import Command
-from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
-
-import pkg_resources
-from setuptools import __version__
-
-SOURCE_EPOCH_ZIP = 499162860
-
-WHEEL_FILE = f"""\
-Wheel-Version: 1.0
-Generator: setuptools ({__version__})
-Root-Is-Purelib: false
-Tag: py3-none-any
-Tag: ed-none-any
-"""
+from pathlib import Path
+from tempfile import TemporaryDirectory
class editable_wheel(Command):
@@ -35,91 +19,78 @@ class editable_wheel(Command):
user_options = [
("dist-dir=", "d", "directory to put final built distributions in"),
+ ("dist-info-dir=", "I", "path to a pre-build .dist-info directory"),
]
boolean_options = []
- def run(self):
- self.build_editable_wheel()
-
def initialize_options(self):
self.dist_dir = None
+ self.dist_info_dir = None
+ self.project_dir = None
def finalize_options(self):
- self.dist_info = self.get_finalized_command("dist_info")
- self.egg_base = self.dist_info.egg_base
- self.dist_info_dir = Path(self.dist_info.dist_info_dir)
- self.target = pkg_resources.normalize_path(self.egg_base)
+ dist = self.distribution
+ self.project_dir = dist.src_root or os.curdir
+ self.dist_dir = Path(self.dist_dir or os.path.join(self.project_dir, "dist"))
+ self.dist_dir.mkdir(exist_ok=True)
+
+ @property
+ def target(self):
+ package_dir = self.distribution.package_dir or {}
+ return package_dir.get("") or self.project_dir
- def build_editable_wheel(self):
- if getattr(self.distribution, "use_2to3", False):
- raise NotImplementedError("2to3 not supported")
+ def run(self):
+ self._ensure_dist_info()
- di = self.get_finalized_command("dist_info")
- di.egg_base = self.dist_dir
- di.finalize_options()
- self.run_command("dist_info")
+ # Add missing dist_info files
+ bdist_wheel = self.reinitialize_command("bdist_wheel")
+ bdist_wheel.write_wheelfile(self.dist_info_dir)
# Build extensions in-place
self.reinitialize_command("build_ext", inplace=1)
self.run_command("build_ext")
- mtime = time.gmtime(SOURCE_EPOCH_ZIP)[:6]
-
- dist_dir = Path(self.dist_dir)
- dist_info_dir = self.dist_info_dir
- fullname = self.distribution.metadata.get_fullname()
- # superfluous 'ed' tag is only a hint to the user,
- # and guarantees we can't overwrite the normal wheel
- wheel_name = f"{fullname}-ed.py3-none-any.whl"
- wheel_path = dist_dir / wheel_name
-
+ self._create_wheel_file(bdist_wheel)
+
+ def _ensure_dist_info(self):
+ if self.dist_info_dir is None:
+ dist_info = self.reinitialize_command("dist_info")
+ dist_info.output_dir = self.dist_dir
+ dist_info.finalize_options()
+ dist_info.run()
+ self.dist_info_dir = dist_info.dist_info_dir
+ else:
+ assert str(self.dist_info_dir).endswith(".dist-info")
+ assert list(Path(self.dist_info_dir).glob("*.dist-info/METADATA"))
+
+ def _create_wheel_file(self, bdist_wheel):
+ from wheel.wheelfile import WheelFile
+ dist_info = self.get_finalized_command("dist_info")
+ tag = "-".join(bdist_wheel.get_tag())
+ editable_name = dist_info.name
+ build_tag = "0.editable" # According to PEP 427 needs to start with digit
+ archive_name = f"{editable_name}-{build_tag}-{tag}.whl"
+ wheel_path = Path(self.dist_dir, archive_name)
if wheel_path.exists():
wheel_path.unlink()
- with ZipFile(wheel_path, "a", compression=ZIP_DEFLATED) as archive:
- # copy .pth file
- pth = ZipInfo(f"{fullname}_ed.pth", mtime)
- archive.writestr(pth, f"{self.target}\n")
-
- # copy .dist-info directory
- for f in sorted(os.listdir(dist_dir / dist_info_dir)):
- with (dist_dir / dist_info_dir / f).open() as metadata:
- info = ZipInfo(str(dist_info_dir / f), mtime)
- archive.writestr(info, metadata.read())
-
- # Add WHEEL file
- info = ZipInfo(str(dist_info_dir / "WHEEL"), mtime)
- archive.writestr(info, WHEEL_FILE)
-
- add_manifest(archive, dist_info_dir)
-
-
-def urlsafe_b64encode(data):
- """urlsafe_b64encode without padding"""
- return base64.urlsafe_b64encode(data).rstrip(b"=")
-
-
-# standalone wheel helpers based on enscons
-def add_manifest(archive, dist_info_dir):
- """
- Add the wheel manifest.
- """
- import hashlib
- import zipfile
-
- lines = []
- for f in archive.namelist():
- data = archive.read(f)
- size = len(data)
- digest = hashlib.sha256(data).digest()
- digest = "sha256=" + (urlsafe_b64encode(digest).decode("ascii"))
- lines.append("%s,%s,%s" % (f.replace(",", ",,"), digest, size))
-
- record_path = dist_info_dir / "RECORD"
- lines.append(str(record_path) + ",,")
- RECORD = "\n".join(lines)
- archive.writestr(
- zipfile.ZipInfo(str(record_path), time.gmtime(SOURCE_EPOCH_ZIP)[:6]), RECORD
- )
- archive.close()
+ # Currently the wheel API receives a directory and dump all its contents
+ # inside of a wheel. So let's use a temporary directory.
+ with TemporaryDirectory(suffix=archive_name) as tmp:
+ tmp_dist_info = Path(tmp, Path(self.dist_info_dir).name)
+ shutil.copytree(self.dist_info_dir, tmp_dist_info)
+ pth = Path(tmp, f"_editable.{editable_name}.pth")
+ pth.write_text(f"{_normalize_path(self.target)}\n", encoding="utf-8")
+
+ with WheelFile(wheel_path, "w") as wf:
+ wf.write_files(tmp)
+
+ return wheel_path
+
+
+def _normalize_path(filename):
+ """Normalize a file/dir name for comparison purposes"""
+ # See pkg_resources.normalize_path
+ file = os.path.abspath(filename) if sys.platform == 'cygwin' else filename
+ return os.path.normcase(os.path.realpath(os.path.normpath(file)))