summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnderson Bravalheri <andersonbravalheri@gmail.com>2022-04-04 10:09:08 +0100
committerAnderson Bravalheri <andersonbravalheri@gmail.com>2022-04-04 10:09:08 +0100
commit3465caca4fb3b9fe2c41c378cb693655a87c1f33 (patch)
tree4807a17b77361f561014a00ed2f797ca9c88a897
parentcde42d6303f170eafd4e925665701e88a4d65e0d (diff)
parentb1dca400264fb4c1471d4040fd63d5c76ed38a83 (diff)
downloadpython-setuptools-git-3465caca4fb3b9fe2c41c378cb693655a87c1f33.tar.gz
Fix version produced by dist_info (#3230)
-rw-r--r--changelog.d/3088.misc.rst1
-rw-r--r--setuptools/command/dist_info.py34
-rw-r--r--setuptools/command/egg_info.py14
-rw-r--r--setuptools/tests/test_dist_info.py84
4 files changed, 130 insertions, 3 deletions
diff --git a/changelog.d/3088.misc.rst b/changelog.d/3088.misc.rst
new file mode 100644
index 00000000..c507a824
--- /dev/null
+++ b/changelog.d/3088.misc.rst
@@ -0,0 +1 @@
+Fixed duplicated tag with the ``dist-info`` command.
diff --git a/setuptools/command/dist_info.py b/setuptools/command/dist_info.py
index c45258fa..8b8509f3 100644
--- a/setuptools/command/dist_info.py
+++ b/setuptools/command/dist_info.py
@@ -4,9 +4,13 @@ As defined in the wheel specification
"""
import os
+import re
+import warnings
+from inspect import cleandoc
from distutils.core import Command
from distutils import log
+from setuptools.extern import packaging
class dist_info(Command):
@@ -29,8 +33,36 @@ class dist_info(Command):
egg_info.egg_base = self.egg_base
egg_info.finalize_options()
egg_info.run()
- dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info'
+ name = _safe(self.distribution.get_name())
+ version = _version(self.distribution.get_version())
+ base = self.egg_base or os.curdir
+ dist_info_dir = os.path.join(base, f"{name}-{version}.dist-info")
log.info("creating '{}'".format(os.path.abspath(dist_info_dir)))
bdist_wheel = self.get_finalized_command('bdist_wheel')
bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir)
+
+
+def _safe(component: str) -> str:
+ """Escape a component used to form a wheel name according to PEP 491"""
+ return re.sub(r"[^\w\d.]+", "_", component)
+
+
+def _version(version: str) -> str:
+ """Convert an arbitrary string to a version string."""
+ v = version.replace(' ', '.')
+ try:
+ return str(packaging.version.Version(v)).replace("-", "_")
+ except packaging.version.InvalidVersion:
+ msg = f"""!!\n\n
+ ###################
+ # Invalid version #
+ ###################
+ {version!r} is not valid according to PEP 440.\n
+ Please make sure specify a valid version for your package.
+ Also note that future releases of setuptools may halt the build process
+ if an invalid version is given.
+ \n\n!!
+ """
+ warnings.warn(cleandoc(msg))
+ return _safe(v).strip("_")
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 63389654..c37ab81f 100644
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -136,11 +136,21 @@ class InfoCommon:
in which case the version string already contains all tags.
"""
return (
- version if self.vtags and version.endswith(self.vtags)
+ version if self.vtags and self._already_tagged(version)
else version + self.vtags
)
- def tags(self):
+ def _already_tagged(self, version: str) -> bool:
+ # Depending on their format, tags may change with version normalization.
+ # So in addition the regular tags, we have to search for the normalized ones.
+ return version.endswith(self.vtags) or version.endswith(self._safe_tags())
+
+ def _safe_tags(self) -> str:
+ # To implement this we can rely on `safe_version` pretending to be version 0
+ # followed by tags. Then we simply discard the starting 0 (fake version number)
+ return safe_version(f"0{self.vtags}")[1:]
+
+ def tags(self) -> str:
version = ''
if self.tag_build:
version += self.tag_build
diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py
index 29fbd09d..813ef51d 100644
--- a/setuptools/tests/test_dist_info.py
+++ b/setuptools/tests/test_dist_info.py
@@ -1,12 +1,21 @@
"""Test .dist-info style distributions.
"""
+import pathlib
+import re
+import subprocess
+import sys
+from functools import partial
import pytest
import pkg_resources
+from setuptools.archive_util import unpack_archive
from .textwrap import DALS
+read = partial(pathlib.Path.read_text, encoding="utf-8")
+
+
class TestDistInfo:
metadata_base = DALS("""
@@ -72,3 +81,78 @@ class TestDistInfo:
pkg_resources.Requirement.parse('quux>=1.1;extra=="baz"'),
]
assert d.extras == ['baz']
+
+ def test_invalid_version(self, tmp_path):
+ config = "[metadata]\nname=proj\nversion=42\n[egg_info]\ntag_build=invalid!!!\n"
+ (tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
+ msg = re.compile("invalid version", re.M | re.I)
+ output = run_command("dist_info", cwd=tmp_path)
+ assert msg.search(output)
+ dist_info = next(tmp_path.glob("*.dist-info"))
+ assert dist_info.name.startswith("proj-42")
+
+
+class TestWheelCompatibility:
+ """Make sure the .dist-info directory produced with the ``dist_info`` command
+ is the same as the one produced by ``bdist_wheel``.
+ """
+ SETUPCFG = DALS("""
+ [metadata]
+ name = {name}
+ version = {version}
+
+ [options]
+ install_requires = foo>=12; sys_platform != "linux"
+
+ [options.extras_require]
+ test = pytest
+
+ [options.entry_points]
+ console_scripts =
+ executable-name = my_package.module:function
+ discover =
+ myproj = my_package.other_module:function
+ """)
+
+ EGG_INFO_OPTS = [
+ # Related: #3088 #2872
+ ("", ""),
+ (".post", "[egg_info]\ntag_build = post\n"),
+ (".post", "[egg_info]\ntag_build = .post\n"),
+ (".post", "[egg_info]\ntag_build = post\ntag_date = 1\n"),
+ (".dev", "[egg_info]\ntag_build = .dev\n"),
+ (".dev", "[egg_info]\ntag_build = .dev\ntag_date = 1\n"),
+ ("a1", "[egg_info]\ntag_build = .a1\n"),
+ ("+local", "[egg_info]\ntag_build = +local\n"),
+ ]
+
+ @pytest.mark.parametrize("name", "my-proj my_proj my.proj My.Proj".split())
+ @pytest.mark.parametrize("version", ["0.42.13"])
+ @pytest.mark.parametrize("suffix, cfg", EGG_INFO_OPTS)
+ def test_dist_info_is_the_same_as_in_wheel(
+ self, name, version, tmp_path, suffix, cfg
+ ):
+ config = self.SETUPCFG.format(name=name, version=version) + cfg
+
+ for i in "dir_wheel", "dir_dist":
+ (tmp_path / i).mkdir()
+ (tmp_path / i / "setup.cfg").write_text(config, encoding="utf-8")
+
+ run_command("bdist_wheel", cwd=tmp_path / "dir_wheel")
+ wheel = next(tmp_path.glob("dir_wheel/dist/*.whl"))
+ unpack_archive(wheel, tmp_path / "unpack")
+ wheel_dist_info = next(tmp_path.glob("unpack/*.dist-info"))
+
+ run_command("dist_info", cwd=tmp_path / "dir_dist")
+ dist_info = next(tmp_path.glob("dir_dist/*.dist-info"))
+
+ 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)
+
+
+def run_command(*cmd, **kwargs):
+ opts = {"stderr": subprocess.STDOUT, "text": True, **kwargs}
+ cmd = [sys.executable, "-c", "__import__('setuptools').setup()", *cmd]
+ return subprocess.check_output(cmd, **opts)