Repository: pypa/setuptools

Issue:
[BUG] build_meta glob *.dist-info targets unrelated .dist-info files
### setuptools version

setuptools 64+

### Python version

Python 3.10.6

### OS

Fedora Linux

### Additional environment information

_No response_

### Description

I tried to build the new version of setuptools with setuptools itself, but I got an error: 
~~~~
Traceback (most recent call last):
  File "/usr/lib/rpm/redhat/pyproject_buildrequires.py", line 501, in main
    generate_requires(
  File "/usr/lib/rpm/redhat/pyproject_buildrequires.py", line 420, in generate_requires
    generate_run_requirements(backend, requirements, build_wheel=build_wheel, wheeldir=wheeldir)
  File "/usr/lib/rpm/redhat/pyproject_buildrequires.py", line 330, in generate_run_requirements
    generate_run_requirements_hook(backend, requirements)
  File "/usr/lib/rpm/redhat/pyproject_buildrequires.py", line 286, in generate_run_requirements_hook
    dir_basename = prepare_metadata('.')
                   ^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/setuptools-65.0.0/setuptools/build_meta.py", line 372, in prepare_metadata_for_build_wheel
    return self._bubble_up_info_directory(metadata_directory, ".dist-info")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/setuptools-65.0.0/setuptools/build_meta.py", line 350, in _bubble_up_info_directory
    assert len(candidates) == 1, f"Exactly one {suffix} should have been produced"
AssertionError: Exactly one .dist-info should have been produced
~~~~
Here is the content of `candidates`:
~~~~
[PosixPath('setuptools-65.0.0.post20220815.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/attrs-22.1.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/autocommand-2.2.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/build-0.8.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/click-8.1.3.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/distlib-0.3.5.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/docutils-0.19.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/execnet-1.9.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/filelock-3.7.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/flake8-4.0.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/flake8_2020-1.6.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/importlib_metadata-4.12.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/ini2toml-0.11.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/iniconfig-1.1.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/jaraco.context-4.1.2.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/jaraco.envs-2.4.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/jaraco.functools-3.5.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/jaraco.path-3.4.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/mccabe-0.6.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/mock-4.0.3.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/more_itertools-8.13.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/mypy_extensions-0.4.3.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/packaging-21.3.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/path-16.4.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pathspec-0.9.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pep517-0.13.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pip-22.1.2.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pip_run-8.8.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/platformdirs-2.5.2.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pluggy-1.0.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/py-1.11.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pycodestyle-2.8.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pyflakes-2.4.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pyparsing-3.0.9.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest-7.1.2.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_black-0.3.12.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_checkdocs-2.7.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_cov-3.0.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_enabler-1.3.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_flake8-1.1.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_forked-1.4.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_mypy-0.9.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_perf-0.12.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytest_xdist-2.5.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/pytz-2022.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/six-1.16.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/tempora-5.0.2.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/toml-0.10.2.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/tomli-2.0.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/tomli_w-1.0.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/tox-3.25.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/typing_extensions-4.3.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/virtualenv-20.16.2.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/wheel-0.37.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/zipp-3.8.1.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/py/_vendored_packages/apipkg-2.0.0.dist-info'), PosixPath('.tox/python/lib/python3.10/site-packages/py/_vendored_packages/iniconfig-1.1.1.dist-info'), PosixPath('.tox/python/lib64/python3.10/site-packages/black-22.6.0.dist-info'), PosixPath('.tox/python/lib64/python3.10/site-packages/coverage-6.4.2.dist-info'), PosixPath('.tox/python/lib64/python3.10/site-packages/mypy-0.971.dist-info'), PosixPath('pkg_resources/_vendor/appdirs-1.4.3.dist-info'), PosixPath('pkg_resources/_vendor/importlib_resources-5.4.0.dist-info'), PosixPath('pkg_resources/_vendor/jaraco.context-4.1.1.dist-info'), PosixPath('pkg_resources/_vendor/jaraco.functools-3.5.0.dist-info'), PosixPath('pkg_resources/_vendor/jaraco.text-3.7.0.dist-info'), PosixPath('pkg_resources/_vendor/more_itertools-8.12.0.dist-info'), PosixPath('pkg_resources/_vendor/packaging-21.3.dist-info'), PosixPath('pkg_resources/_vendor/pyparsing-3.0.8.dist-info'), PosixPath('pkg_resources/_vendor/zipp-3.7.0.dist-info'), PosixPath('pkg_resources/_vendor/pyparsing-3.0.9.dist-info'), PosixPath('setuptools/_vendor/importlib_metadata-4.11.1.dist-info'), PosixPath('setuptools/_vendor/importlib_resources-5.4.0.dist-info'), PosixPath('setuptools/_vendor/jaraco.context-4.1.1.dist-info'), PosixPath('setuptools/_vendor/jaraco.functools-3.5.0.dist-info'), PosixPath('setuptools/_vendor/jaraco.text-3.7.0.dist-info'), PosixPath('setuptools/_vendor/more_itertools-8.8.0.dist-info'), PosixPath('setuptools/_vendor/ordered_set-3.1.1.dist-info'), PosixPath('setuptools/_vendor/packaging-21.3.dist-info'), PosixPath('setuptools/_vendor/tomli-2.0.1.dist-info'), PosixPath('setuptools/_vendor/typing_extensions-4.0.1.dist-info'), PosixPath('setuptools/_vendor/zipp-3.7.0.dist-info'), PosixPath('setuptools/_vendor/pyparsing-3.0.9.dist-info')]
~~~~

### Expected behavior

It seems that when I run prepare_metadata_for_build_wheel() function it returns some .dist-infos that shouldn't be returned (e.g. .dist-infos inside .tox directory). I'd expect there will be only `PosixPath('setuptools-65.0.0.post20220815.dist-info')`, because it fails with `AssertionError: Exactly one .dist-info should have been produced`.

### How to Reproduce

1. clone setuptools repository
2. activate virtual environment
3. pip install .
4. pip install wheel
5. rm bootstrap.egg-info -rf
6. run python interpreter
7. >>> from setuptools import build_meta
8. >>> build_meta.prepare_metadata_for_build_wheel(".")

### Output

```console
~/python-maint/upstream
❯ git clone git@github.com:pypa/setuptools.git
Cloning into 'setuptools'...
remote: Enumerating objects: 60033, done.
remote: Counting objects: 100% (1827/1827), done.
remote: Compressing objects: 100% (627/627), done.
remote: Total 60033 (delta 1271), reused 1666 (delta 1186), pack-reused 58206
Receiving objects: 100% (60033/60033), 40.90 MiB | 7.38 MiB/s, done.
Resolving deltas: 100% (33703/33703), done.

~/python-maint/upstream took 8s
❯ cd setuptools

setuptools on  main via 🐍 v3.10.6
❯ python3.10 -m venv env

setuptools on  main [?] via 🐍 v3.10.6 took 2s
❯ source env/bin/activate

setuptools on  main [?] via 🐍 v3.10.6 (env)
❯ pip install .
Processing /home/thrnciar/python-maint/upstream/setuptools
  Getting requirements to build wheel ... done
  Installing backend dependencies ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: setuptools
  Building wheel for setuptools (pyproject.toml) ... done
  Created wheel for setuptools: filename=setuptools-65.0.0.post20220815-py3-none-any.whl size=1217614 sha256=aa756eb335a37b577a39719bf980c72a743f81e8d3275c19d33bc3704017455e
  Stored in directory: /tmp/pip-ephem-wheel-cache-ej85rxbx/wheels/bc/4b/d8/b2ea85368b0176afa9d32e7ab6f7421d41814a8c8d62211203
Successfully built setuptools
Installing collected packages: setuptools
  Attempting uninstall: setuptools
    Found existing installation: setuptools 59.6.0
    Uninstalling setuptools-59.6.0:
      Successfully uninstalled setuptools-59.6.0
Successfully installed setuptools-65.0.0.post20220815
WARNING: You are using pip version 21.3.1; however, version 22.2.2 is available.
You should consider upgrading via the '/home/thrnciar/python-maint/upstream/setuptools/env/bin/python3.10 -m pip install --upgrade pip' command.

setuptools on  main [?] via 🐍 v3.10.6 (env) took 4s
❯ pip install wheel
Collecting wheel
  Using cached wheel-0.37.1-py2.py3-none-any.whl (35 kB)
Installing collected packages: wheel
Successfully installed wheel-0.37.1
WARNING: You are using pip version 21.3.1; however, version 22.2.2 is available.
You should consider upgrading via the '/home/thrnciar/python-maint/upstream/setuptools/env/bin/python3.10 -m pip install --upgrade pip' command.

setuptools on  main [?] via 🐍 v3.10.6 (env) took 8s
❯ rm bootstrap.egg-info -rf

setuptools on  main [✘?] via 🐍 v3.10.6 (env)
❯ python
Python 3.10.6 (main, Aug  2 2022, 00:00:00) [GCC 12.1.1 20220507 (Red Hat 12.1.1-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from setuptools import build_meta
>>> build_meta.prepare_metadata_for_build_wheel(".")
running dist_info
writing setuptools.egg-info/PKG-INFO
writing dependency_links to setuptools.egg-info/dependency_links.txt
writing entry points to setuptools.egg-info/entry_points.txt
writing requirements to setuptools.egg-info/requires.txt
writing top-level names to setuptools.egg-info/top_level.txt
writing setuptools.egg-info/PKG-INFO
writing dependency_links to setuptools.egg-info/dependency_links.txt
writing entry points to setuptools.egg-info/entry_points.txt
writing requirements to setuptools.egg-info/requires.txt
writing top-level names to setuptools.egg-info/top_level.txt
reading manifest file 'setuptools.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching '*.py' under directory 'tests'
warning: no files found matching '*.txt' under directory 'docs'
warning: no files found matching '*.conf' under directory 'docs'
warning: no files found matching '*.css' under directory 'docs'
warning: no files found matching '*.css_t' under directory 'docs'
warning: no files found matching 'Makefile' under directory 'docs'
warning: no files found matching 'indexsidebar.html' under directory 'docs'
warning: no files found matching 'msvc-build-launcher.cmd'
adding license file 'LICENSE'
writing manifest file 'setuptools.egg-info/SOURCES.txt'
creating '/home/thrnciar/python-maint/upstream/setuptools/setuptools-65.0.0.post20220815.dist-info'
adding license file "LICENSE" (matched pattern "LICEN[CS]E*")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/thrnciar/python-maint/upstream/setuptools/setuptools/build_meta.py", line 372, in prepare_metadata_for_build_wheel
    return self._bubble_up_info_directory(metadata_directory, ".dist-info")
  File "/home/thrnciar/python-maint/upstream/setuptools/setuptools/build_meta.py", line 350, in _bubble_up_info_directory
    assert len(candidates) == 1, f"Exactly one {suffix} should have been produced"
AssertionError: Exactly one .dist-info should have been produced
>>>

```



Discussion:

Resolving patch:
diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py
--- a/setuptools/build_meta.py
+++ b/setuptools/build_meta.py
@@ -40,6 +40,7 @@
 
 import setuptools
 import distutils
+from . import errors
 from ._path import same_path
 from ._reqs import parse_strings
 from ._deprecation_warning import SetuptoolsDeprecationWarning
@@ -346,16 +347,23 @@ def _bubble_up_info_directory(self, metadata_directory: str, suffix: str) -> str
 
         Returns the basename of the info directory, e.g. `proj-0.0.0.dist-info`.
         """
-        candidates = list(Path(metadata_directory).glob(f"**/*{suffix}/"))
-        assert len(candidates) == 1, f"Exactly one {suffix} should have been produced"
-        info_dir = candidates[0]
-
+        info_dir = self._find_info_directory(metadata_directory, suffix)
         if not same_path(info_dir.parent, metadata_directory):
             shutil.move(str(info_dir), metadata_directory)
             # PEP 517 allow other files and dirs to exist in metadata_directory
-
         return info_dir.name
 
+    def _find_info_directory(self, metadata_directory: str, suffix: str) -> Path:
+        for parent, dirs, _ in os.walk(metadata_directory):
+            candidates = [f for f in dirs if f.endswith(suffix)]
+
+            if len(candidates) != 0 or len(dirs) != 1:
+                assert len(candidates) == 1, f"Multiple {suffix} directories found"
+                return Path(parent, candidates[0])
+
+        msg = f"No {suffix} directory found in {metadata_directory}"
+        raise errors.InternalError(msg)
+
     def prepare_metadata_for_build_wheel(self, metadata_directory,
                                          config_settings=None):
         sys.argv = [


Test patch:
diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py
--- a/setuptools/tests/test_build_meta.py
+++ b/setuptools/tests/test_build_meta.py
@@ -485,6 +485,23 @@ def test_prepare_metadata_for_build_wheel(self, build_backend):
 
         assert os.path.isfile(os.path.join(dist_dir, dist_info, 'METADATA'))
 
+    def test_prepare_metadata_inplace(self, build_backend):
+        """
+        Some users might pass metadata_directory pre-populated with `.tox` or `.venv`.
+        See issue #3523.
+        """
+        for pre_existing in [
+            ".tox/python/lib/python3.10/site-packages/attrs-22.1.0.dist-info",
+            ".tox/python/lib/python3.10/site-packages/autocommand-2.2.1.dist-info",
+            ".nox/python/lib/python3.10/site-packages/build-0.8.0.dist-info",
+            ".venv/python3.10/site-packages/click-8.1.3.dist-info",
+            "venv/python3.10/site-packages/distlib-0.3.5.dist-info",
+            "env/python3.10/site-packages/docutils-0.19.dist-info",
+        ]:
+            os.makedirs(pre_existing, exist_ok=True)
+        dist_info = build_backend.prepare_metadata_for_build_wheel(".")
+        assert os.path.isfile(os.path.join(dist_info, 'METADATA'))
+
     def test_build_sdist_explicit_dist(self, build_backend):
         # explicitly specifying the dist folder should work
         # the folder sdist_directory and the ``--dist-dir`` can be the same
diff --git a/setuptools/tests/test_editable_install.py b/setuptools/tests/test_editable_install.py
--- a/setuptools/tests/test_editable_install.py
+++ b/setuptools/tests/test_editable_install.py
@@ -872,9 +872,8 @@ def test_access_plat_name(self, tmpdir_cwd):
         cmd = editable_wheel(dist)
         cmd.ensure_finalized()
         cmd.run()
-        wheel_file = str(next(Path().glob('dist/*')))
+        wheel_file = str(next(Path().glob('dist/*.whl')))
         assert "editable" in wheel_file
-        assert wheel_file.endswith(".whl")
 
 
 def install_project(name, venv, tmp_path, files, *opts):