diff options
| author | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-04-04 01:02:41 +0100 |
|---|---|---|
| committer | Anderson Bravalheri <andersonbravalheri@gmail.com> | 2022-06-15 16:43:45 +0100 |
| commit | 5866b8563cc35d01d08053d8142f4c09255a07f3 (patch) | |
| tree | cb14e55d74f22ef5c52fc1a01a8930e75613b751 /setuptools/command | |
| parent | 3e9f4418d528228af7aba1a3bddbbf20b2327422 (diff) | |
| download | python-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.py | 151 |
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))) |
