Repository: pypa/setuptools

Issue:
tag_build breaks prepare_metadata_for_build_wheel
Previously reported in pypa/pip#9446.

This is similar to #1462, but for PEP 517 hooks, so I’m guessing a similar treatment is needed?

```ini
[egg_info]
tag_build = dev

[metadata]
name = pkg
version = 0.1
```

`egg_info` works as expected:

```console
$ py -c 'from setuptools import *; setup()' egg_info >/dev/null
$ rg '^Version:' < pkg.egg-info/PKG-INFO 
Version: 0.1.dev0
```

But not the PEP 517 hook:

```console
$ py -c 'import os; from setuptools.build_meta import *; prepare_metadata_for_build_wheel(os.getcwd())' >/dev/null
$ rg '^Version:' < pkg.dist-info/METADATA 
Version: 0.1.dev0dev
```


Discussion:

Resolving patch:
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -130,10 +130,12 @@ def _maybe_tag(self, version):
         egg_info may be called more than once for a distribution,
         in which case the version string already contains all tags.
         """
-        return (
-            version if self.vtags and version.endswith(self.vtags)
-            else version + self.vtags
-        )
+        # Remove the tags if they exist. The tags maybe have been normalized
+        # (e.g. turning .dev into .dev0) so we can't just compare strings
+        base_version = parse_version(version).base_version
+
+        # Add the tags
+        return base_version + self.vtags
 
     def tags(self):
         version = ''


Test patch:
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -6,6 +6,7 @@
 import stat
 import time
 
+from setuptools.build_meta import prepare_metadata_for_build_wheel
 from setuptools.command.egg_info import (
     egg_info, manifest_maker, EggInfoDeprecationWarning, get_pkg_info_revision,
 )
@@ -19,6 +20,26 @@
 from . import contexts
 
 
+def _run_egg_info_command(tmpdir_cwd, env, cmd=None, output=None):
+    environ = os.environ.copy().update(
+        HOME=env.paths['home'],
+    )
+    if cmd is None:
+        cmd = [
+            'egg_info',
+        ]
+    code, data = environment.run_setup_py(
+        cmd=cmd,
+        pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
+        data_stream=1,
+        env=environ,
+    )
+    assert not code, data
+
+    if output:
+        assert output in data
+
+
 class Environment(str):
     pass
 
@@ -132,7 +153,7 @@ def test_egg_info_save_version_info_setup_defaults(self, tmpdir_cwd, env):
     def test_expected_files_produced(self, tmpdir_cwd, env):
         self._create_project()
 
-        self._run_egg_info_command(tmpdir_cwd, env)
+        _run_egg_info_command(tmpdir_cwd, env)
         actual = os.listdir('foo.egg-info')
 
         expected = [
@@ -166,7 +187,7 @@ def test_license_is_a_string(self, tmpdir_cwd, env):
         # currently configured to use a subprocess, the actual traceback
         # object is lost and we need to parse it from stderr
         with pytest.raises(AssertionError) as exc:
-            self._run_egg_info_command(tmpdir_cwd, env)
+            _run_egg_info_command(tmpdir_cwd, env)
 
         # Hopefully this is not too fragile: the only argument to the
         # assertion error should be a traceback, ending with:
@@ -180,13 +201,13 @@ def test_rebuilt(self, tmpdir_cwd, env):
         """Ensure timestamps are updated when the command is re-run."""
         self._create_project()
 
-        self._run_egg_info_command(tmpdir_cwd, env)
+        _run_egg_info_command(tmpdir_cwd, env)
         timestamp_a = os.path.getmtime('foo.egg-info')
 
         # arbitrary sleep just to handle *really* fast systems
         time.sleep(.001)
 
-        self._run_egg_info_command(tmpdir_cwd, env)
+        _run_egg_info_command(tmpdir_cwd, env)
         timestamp_b = os.path.getmtime('foo.egg-info')
 
         assert timestamp_a != timestamp_b
@@ -201,7 +222,7 @@ def test_manifest_template_is_read(self, tmpdir_cwd, env):
                 'usage.rst': "Run 'hi'",
             }
         })
-        self._run_egg_info_command(tmpdir_cwd, env)
+        _run_egg_info_command(tmpdir_cwd, env)
         egg_info_dir = os.path.join('.', 'foo.egg-info')
         sources_txt = os.path.join(egg_info_dir, 'SOURCES.txt')
         with open(sources_txt) as f:
@@ -441,7 +462,7 @@ def test_requires(
             self, tmpdir_cwd, env, requires, use_setup_cfg,
             expected_requires, install_cmd_kwargs):
         self._setup_script_with_requires(requires, use_setup_cfg)
-        self._run_egg_info_command(tmpdir_cwd, env, **install_cmd_kwargs)
+        _run_egg_info_command(tmpdir_cwd, env, **install_cmd_kwargs)
         egg_info_dir = os.path.join('.', 'foo.egg-info')
         requires_txt = os.path.join(egg_info_dir, 'requires.txt')
         if os.path.exists(requires_txt):
@@ -461,14 +482,14 @@ def test_install_requires_unordered_disallowed(self, tmpdir_cwd, env):
         req = 'install_requires={"fake-factory==0.5.2", "pytz"}'
         self._setup_script_with_requires(req)
         with pytest.raises(AssertionError):
-            self._run_egg_info_command(tmpdir_cwd, env)
+            _run_egg_info_command(tmpdir_cwd, env)
 
     def test_extras_require_with_invalid_marker(self, tmpdir_cwd, env):
         tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},'
         req = tmpl.format(marker=self.invalid_marker)
         self._setup_script_with_requires(req)
         with pytest.raises(AssertionError):
-            self._run_egg_info_command(tmpdir_cwd, env)
+            _run_egg_info_command(tmpdir_cwd, env)
         assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
 
     def test_extras_require_with_invalid_marker_in_req(self, tmpdir_cwd, env):
@@ -476,7 +497,7 @@ def test_extras_require_with_invalid_marker_in_req(self, tmpdir_cwd, env):
         req = tmpl.format(marker=self.invalid_marker)
         self._setup_script_with_requires(req)
         with pytest.raises(AssertionError):
-            self._run_egg_info_command(tmpdir_cwd, env)
+            _run_egg_info_command(tmpdir_cwd, env)
         assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
 
     def test_provides_extra(self, tmpdir_cwd, env):
@@ -865,26 +886,22 @@ def test_egg_info_includes_setup_py(self, tmpdir_cwd):
             sources = f.read().split('\n')
             assert 'setup.py' in sources
 
-    def _run_egg_info_command(self, tmpdir_cwd, env, cmd=None, output=None):
-        environ = os.environ.copy().update(
-            HOME=env.paths['home'],
-        )
-        if cmd is None:
-            cmd = [
-                'egg_info',
-            ]
-        code, data = environment.run_setup_py(
-            cmd=cmd,
-            pypath=os.pathsep.join([env.paths['lib'], str(tmpdir_cwd)]),
-            data_stream=1,
-            env=environ,
-        )
-        assert not code, data
-
-        if output:
-            assert output in data
-
-    def test_egg_info_tag_only_once(self, tmpdir_cwd, env):
+    @pytest.mark.parametrize(
+        ('make_metadata_path', 'run_command'),
+        [
+            (
+                lambda env: os.path.join('.', 'foo.egg-info', 'PKG-INFO'),
+                lambda tmpdir_cwd, env: _run_egg_info_command(tmpdir_cwd, env)
+            ),
+            (
+                lambda env: os.path.join(env, 'foo.dist-info', 'METADATA'),
+                lambda tmpdir_cwd, env: prepare_metadata_for_build_wheel(env)
+            )
+        ]
+    )
+    def test_egg_info_tag_only_once(
+            self, tmpdir_cwd, env, make_metadata_path, run_command
+    ):
         self._create_project()
         build_files({
             'setup.cfg': DALS("""
@@ -894,11 +911,10 @@ def test_egg_info_tag_only_once(self, tmpdir_cwd, env):
                               tag_svn_revision = 0
                               """),
         })
-        self._run_egg_info_command(tmpdir_cwd, env)
-        egg_info_dir = os.path.join('.', 'foo.egg-info')
-        with open(os.path.join(egg_info_dir, 'PKG-INFO')) as pkginfo_file:
-            pkg_info_lines = pkginfo_file.read().split('\n')
-        assert 'Version: 0.0.0.dev0' in pkg_info_lines
+        run_command(tmpdir_cwd, env)
+        with open(make_metadata_path(env)) as metadata_file:
+            metadata_lines = metadata_file.read().split('\n')
+        assert 'Version: 0.0.0.dev0' in metadata_lines
 
     def test_get_pkg_info_revision_deprecated(self):
         pytest.warns(EggInfoDeprecationWarning, get_pkg_info_revision)