From 8901269d00620bac36aaa77570b9966a2f4c4117 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 9 Mar 2023 16:09:48 +0000 Subject: [PATCH 1/4] Avoid using pypa/wheel unstable API in dist_info --- setuptools/command/dist_info.py | 88 ++++++++++++++++----------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py index 9df625cee7..615bf26d37 100644 --- a/setuptools/command/dist_info.py +++ b/setuptools/command/dist_info.py @@ -5,8 +5,6 @@ import os import shutil -import sys -from contextlib import contextmanager from distutils import log from distutils.core import Command from pathlib import Path @@ -15,6 +13,16 @@ from ..warnings import SetuptoolsDeprecationWarning +_IGNORE = { + "PKG-INFO", + "requires.txt", + "SOURCES.txt", + "not-zip-safe", + "dependency_links.txt", +} +# Files to be ignored when copying the egg-info into dist-info + + class dist_info(Command): """ This command is private and reserved for internal use of setuptools, @@ -69,7 +77,16 @@ def finalize_options(self): egg_info = self.reinitialize_command("egg_info") egg_info.egg_base = str(self.output_dir) + self._sync_tag_details(egg_info) + egg_info.finalize_options() + self.egg_info = egg_info + name = _normalization.safer_name(dist.get_name()) + version = _normalization.safer_best_effort_version(dist.get_version()) + self.name = f"{name}-{version}" + self.dist_info_dir = Path(self.output_dir, f"{self.name}.dist-info") + + def _sync_tag_details(self, egg_info): if self.tag_date: egg_info.tag_date = self.tag_date else: @@ -80,48 +97,31 @@ def finalize_options(self): else: self.tag_build = egg_info.tag_build - egg_info.finalize_options() - self.egg_info = egg_info - - name = _normalization.safer_name(dist.get_name()) - version = _normalization.safer_best_effort_version(dist.get_version()) - self.name = f"{name}-{version}" - self.dist_info_dir = os.path.join(self.output_dir, f"{self.name}.dist-info") - - @contextmanager - def _maybe_bkp_dir(self, dir_path: str, requires_bkp: bool): - if requires_bkp: - bkp_name = f"{dir_path}.__bkp__" - _rm(bkp_name, ignore_errors=True) - _copy(dir_path, bkp_name, dirs_exist_ok=True, symlinks=True) - try: - yield - finally: - _rm(dir_path, ignore_errors=True) - shutil.move(bkp_name, dir_path) - else: - yield - def run(self): self.output_dir.mkdir(parents=True, exist_ok=True) self.egg_info.run() - egg_info_dir = self.egg_info.egg_info - assert os.path.isdir(egg_info_dir), ".egg-info dir should have been created" - - log.info("creating '{}'".format(os.path.abspath(self.dist_info_dir))) - bdist_wheel = self.get_finalized_command('bdist_wheel') - - # TODO: if bdist_wheel if merged into setuptools, just add "keep_egg_info" there - with self._maybe_bkp_dir(egg_info_dir, self.keep_egg_info): - bdist_wheel.egg2dist(egg_info_dir, self.dist_info_dir) - - -def _rm(dir_name, **opts): - if os.path.isdir(dir_name): - shutil.rmtree(dir_name, **opts) - - -def _copy(src, dst, **opts): - if sys.version_info < (3, 8): - opts.pop("dirs_exist_ok", None) - shutil.copytree(src, dst, **opts) + egg_info_dir = Path(self.egg_info.egg_info) + dist_info_dir = self.dist_info_dir + + assert egg_info_dir.is_dir(), ".egg-info dir should have been created" + log.info(f"creating {str(os.path.abspath(dist_info_dir))!r}") + + # The egg-info dir should now be basically equivalent to the dist-info dir + # If in the future we don't want to use egg_info, we have to create the files: + # METADATA, entry-points.txt + shutil.copytree(egg_info_dir, dist_info_dir, ignore=lambda _, __: _IGNORE) + shutil.copy2(egg_info_dir / "PKG-INFO", dist_info_dir / "METADATA") + if self.distribution.dependency_links: + shutil.copy2(egg_info_dir / "dependency_links.txt", dist_info_dir) + + for dest, orig in self._license_paths(): + dest = dist_info_dir / dest + dest.parent.mkdir(exist_ok=True, parents=True) + shutil.copy2(orig, dest) + + if not self.keep_egg_info: + shutil.rmtree(egg_info_dir) + + def _license_paths(self): + for file in self.distribution.metadata.license_files or (): + yield os.path.basename(file), file From 48b12b921836e87c29cea7eb8ac4e78befaf960f Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 9 Mar 2023 16:10:07 +0000 Subject: [PATCH 2/4] Adapt tests to the new dist-info implementation --- setuptools/tests/test_dist_info.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py index a76dbeb3f2..556d16fc9f 100644 --- a/setuptools/tests/test_dist_info.py +++ b/setuptools/tests/test_dist_info.py @@ -5,11 +5,13 @@ import shutil import subprocess import sys +from email import message_from_string from functools import partial import pytest import pkg_resources +from setuptools import _reqs from setuptools.archive_util import unpack_archive from .textwrap import DALS @@ -131,7 +133,7 @@ def test_output_dir(self, tmp_path, keep_egg_info): class TestWheelCompatibility: """Make sure the .dist-info directory produced with the ``dist_info`` command - is the same as the one produced by ``bdist_wheel``. + is the same(ish) as the one produced by ``bdist_wheel``. """ SETUPCFG = DALS( @@ -189,8 +191,30 @@ def test_dist_info_is_the_same_as_in_wheel( assert dist_info.name == wheel_dist_info.name assert dist_info.name.startswith(f"{name.replace('-', '_')}-{version}{suffix}") - for file in "METADATA", "entry_points.txt": - assert read(dist_info / file) == read(wheel_dist_info / file) + + assert (dist_info / "entry_points.txt").read_text(encoding="utf-8") == ( + wheel_dist_info / "entry_points.txt" + ).read_text(encoding="utf-8") + + wheel_metadata = (wheel_dist_info / "METADATA").read_text(encoding="utf-8") + metadata = (dist_info / "METADATA").read_text(encoding="utf-8") + + # Compare metadata but normalize requirements formatting + wheel_msg = message_from_string(wheel_metadata) + wheel_deps = set(_reqs.parse(wheel_msg.get_all("Requires-Dist"))) + wheel_extras = set(wheel_msg.get_all("Provides-Extra")) + del wheel_msg["Requires-Dist"] + del wheel_msg["Provides-Extra"] + + metadata_msg = message_from_string(metadata) + metadata_deps = set(_reqs.parse(metadata_msg.get_all("Requires-Dist"))) + metadata_extras = set(metadata_msg.get_all("Provides-Extra")) + del metadata_msg["Requires-Dist"] + del metadata_msg["Provides-Extra"] + + assert metadata_msg.as_string() == wheel_msg.as_string() + assert metadata_deps == wheel_deps + assert metadata_extras == wheel_extras def run_command_inner(*cmd, **kwargs): From ebc980f6fd5051eb9344ee90c55fc4bd04f0e680 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Fri, 21 Apr 2023 16:42:39 +0100 Subject: [PATCH 3/4] Small adjustments in dist_info --- setuptools/command/dist_info.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py index 615bf26d37..b89138c013 100644 --- a/setuptools/command/dist_info.py +++ b/setuptools/command/dist_info.py @@ -75,7 +75,7 @@ def finalize_options(self): project_dir = dist.src_root or os.curdir self.output_dir = Path(self.output_dir or project_dir) - egg_info = self.reinitialize_command("egg_info") + egg_info = self.reinitialize_command("egg_info", reinit_subcommands=True) egg_info.egg_base = str(self.output_dir) self._sync_tag_details(egg_info) egg_info.finalize_options() @@ -110,7 +110,9 @@ def run(self): # If in the future we don't want to use egg_info, we have to create the files: # METADATA, entry-points.txt shutil.copytree(egg_info_dir, dist_info_dir, ignore=lambda _, __: _IGNORE) - shutil.copy2(egg_info_dir / "PKG-INFO", dist_info_dir / "METADATA") + metadata_file = dist_info_dir / "METADATA" + shutil.copy2(egg_info_dir / "PKG-INFO", metadata_file) + log.debug(f"creating {str(os.path.abspath(metadata_file))!r}") if self.distribution.dependency_links: shutil.copy2(egg_info_dir / "dependency_links.txt", dist_info_dir) From c03fd735e7501eabb4de4620fabad1d4588f75f0 Mon Sep 17 00:00:00 2001 From: Anderson Bravalheri Date: Thu, 20 Apr 2023 16:40:59 +0100 Subject: [PATCH 4/4] Use dir_util in dist_info command for consistency --- setuptools/command/dist_info.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py index b89138c013..81d59c3833 100644 --- a/setuptools/command/dist_info.py +++ b/setuptools/command/dist_info.py @@ -7,6 +7,7 @@ import shutil from distutils import log from distutils.core import Command +from distutils import dir_util # prefer dir_util for log/cache consistency from pathlib import Path from .. import _normalization @@ -49,9 +50,10 @@ class dist_info(Command): ('tag-build=', 'b', "Specify explicit tag to add to version number"), ('no-date', 'D', "Don't include date stamp [default]"), ('keep-egg-info', None, "*TRANSITIONAL* will be removed in the future"), + ('use-cached', None, "*TRANSITIONAL* will be removed in the future"), ] - boolean_options = ['tag-date', 'keep-egg-info'] + boolean_options = ['tag-date', 'keep-egg-info', 'use-cached'] negative_opt = {'no-date': 'tag-date'} def initialize_options(self): @@ -62,6 +64,7 @@ def initialize_options(self): self.tag_date = None self.tag_build = None self.keep_egg_info = False + self.use_cached = False def finalize_options(self): if self.egg_base: @@ -98,7 +101,10 @@ def _sync_tag_details(self, egg_info): self.tag_build = egg_info.tag_build def run(self): - self.output_dir.mkdir(parents=True, exist_ok=True) + if self.use_cached and (self.dist_info_dir / "METADATA").is_file(): + return + + self.mkpath(str(self.output_dir)) self.egg_info.run() egg_info_dir = Path(self.egg_info.egg_info) dist_info_dir = self.dist_info_dir @@ -111,18 +117,17 @@ def run(self): # METADATA, entry-points.txt shutil.copytree(egg_info_dir, dist_info_dir, ignore=lambda _, __: _IGNORE) metadata_file = dist_info_dir / "METADATA" - shutil.copy2(egg_info_dir / "PKG-INFO", metadata_file) - log.debug(f"creating {str(os.path.abspath(metadata_file))!r}") + self.copy_file(egg_info_dir / "PKG-INFO", metadata_file) if self.distribution.dependency_links: - shutil.copy2(egg_info_dir / "dependency_links.txt", dist_info_dir) + self.copy_file(egg_info_dir / "dependency_links.txt", dist_info_dir) for dest, orig in self._license_paths(): dest = dist_info_dir / dest - dest.parent.mkdir(exist_ok=True, parents=True) - shutil.copy2(orig, dest) + self.mkpath(str(dest.parent)) + self.copy_file(orig, dest) if not self.keep_egg_info: - shutil.rmtree(egg_info_dir) + dir_util.remove_tree(egg_info_dir, self.verbose, self.dry_run) def _license_paths(self): for file in self.distribution.metadata.license_files or ():