Repository: pypa/setuptools

Issue:
[BUG] `import pkg_resources` emits `DeprecationWarning`
### setuptools version

setuptools==59.1.0

### Python version

3.9.5

### OS

Ubuntu

### Additional environment information

_No response_

### Description

`DeprecationWarning` is emitted just by importing `pkg_resources`.
This seems like a regression in 59.1.0; this issue did not reproduce with 59.0.1.

### Expected behavior

No warnings were emitted by import.

### How to Reproduce

```
python -Werror -c 'import pkg_resources'
```

### Output

```
Traceback (most recent call last):
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/version.py", line 49, in parse
    return Version(version)
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/version.py", line 266, in __init__
    raise InvalidVersion(f"Invalid version: '{version}'")
pkg_resources.extern.packaging.version.InvalidVersion: Invalid version: '/home/maehashi/.pyenv/versions/3.9.5/envs/local'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 3257, in <module>
    def _initialize_master_working_set():
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 3231, in _call_aside
    f(*args, **kwargs)
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 3269, in _initialize_master_working_set
    working_set = WorkingSet._build_master()
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 564, in _build_master
    ws = cls()
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 557, in __init__
    self.add_entry(entry)
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 613, in add_entry
    for dist in find_distributions(entry, True):
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 2064, in find_on_path
    path_item_entries = _by_version_descending(filtered)
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 2034, in _by_version_descending
    return sorted(names, key=_by_version, reverse=True)
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 2032, in _by_version
    return [packaging.version.parse(part) for part in parts]
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/__init__.py", line 2032, in <listcomp>
    return [packaging.version.parse(part) for part in parts]
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/version.py", line 51, in parse
    return LegacyVersion(version)
  File "/home/maehashi/.pyenv/versions/local-3.9.5/lib/python3.9/site-packages/pkg_resources/_vendor/packaging/version.py", line 111, in __init__
    warnings.warn(
DeprecationWarning: Creating a LegacyVersion has been deprecated and will be removed in the next major release
```

### Code of Conduct

- [X] I agree to follow the PSF Code of Conduct


Discussion:
The warning is intentionally emitted by packaging, which was updated in this version. The use of unsupported versions was previously deprecated, so I’m slightly surprised the error wasn’t triggered at the earlier warning in parse_version.

Oh. I see that parse is being called directly, not through parse_version. I did not realize that Version.parse had its own fallback.

In that case, I’m glad this caught the deprecation warning. Now we just have to figure out why a path is being passed as a version.
It's unclear to me what factor is triggering the error. Do you know what I could do to replicate the environment where this issue occurs? I do see that `_by_version_descending` is being called with a `name` that includes a path, possibly one with a dash in it.

Oh, my! I was able to replicate the issue easily just by invoking Python in Setuptools' own test environment:

```
$ ./.tox/python/bin/python -We -c "import pkg_resources"
Traceback (most recent call last):
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/_vendor/packaging/version.py", line 49, in parse
    return Version(version)
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/_vendor/packaging/version.py", line 266, in __init__
    raise InvalidVersion(f"Invalid version: '{version}'")
pkg_resources.extern.packaging.version.InvalidVersion: Invalid version: '/Users/jaraco/code/public/pypa/setuptools/setuptools'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 3257, in <module>
    def _initialize_master_working_set():
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 3231, in _call_aside
    f(*args, **kwargs)
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 3269, in _initialize_master_working_set
    working_set = WorkingSet._build_master()
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 564, in _build_master
    ws = cls()
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 557, in __init__
    self.add_entry(entry)
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 613, in add_entry
    for dist in find_distributions(entry, True):
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 2064, in find_on_path
    path_item_entries = _by_version_descending(filtered)
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 2034, in _by_version_descending
    return sorted(names, key=_by_version, reverse=True)
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 2032, in _by_version
    return [packaging.version.parse(part) for part in parts]
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/__init__.py", line 2032, in <listcomp>
    return [packaging.version.parse(part) for part in parts]
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/_vendor/packaging/version.py", line 51, in parse
    return LegacyVersion(version)
  File "/Users/jaraco/code/public/pypa/setuptools/pkg_resources/_vendor/packaging/version.py", line 111, in __init__
    warnings.warn(
DeprecationWarning: Creating a LegacyVersion has been deprecated and will be removed in the next major release
```

I'm going to yank this version for now to limit disruption while I work out a solution later this week.
![Screen Shot 2021-11-15 at 09 08 51](https://user-images.githubusercontent.com/308610/141795808-4a19e418-1f78-49a8-b1d6-e1cbc0958f35.png)


Resolving patch:
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -2015,7 +2015,7 @@ def _by_version_descending(names):
 
     >>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'
     >>> _by_version_descending(names)
-    ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar']
+    ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'bar', 'foo']
     >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'
     >>> _by_version_descending(names)
     ['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']
@@ -2023,13 +2023,22 @@ def _by_version_descending(names):
     >>> _by_version_descending(names)
     ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']
     """
+    def try_parse(name):
+        """
+        Attempt to parse as a version or return a null version.
+        """
+        try:
+            return packaging.version.Version(name)
+        except Exception:
+            return packaging.version.Version('0')
+
     def _by_version(name):
         """
         Parse each component of the filename
         """
         name, ext = os.path.splitext(name)
         parts = itertools.chain(name.split('-'), [ext])
-        return [packaging.version.parse(part) for part in parts]
+        return [try_parse(part) for part in parts]
 
     return sorted(names, key=_by_version, reverse=True)
 
diff --git a/setuptools/config.py b/setuptools/config.py
--- a/setuptools/config.py
+++ b/setuptools/config.py
@@ -13,7 +13,7 @@
 import contextlib
 
 from distutils.errors import DistutilsOptionError, DistutilsFileError
-from setuptools.extern.packaging.version import LegacyVersion, parse
+from setuptools.extern.packaging.version import Version, InvalidVersion
 from setuptools.extern.packaging.specifiers import SpecifierSet
 
 
@@ -585,7 +585,9 @@ def _parse_version(self, value):
             version = version.strip()
             # Be strict about versions loaded from file because it's easy to
             # accidentally include newlines and other unintended content
-            if isinstance(parse(version), LegacyVersion):
+            try:
+                Version(version)
+            except InvalidVersion:
                 tmpl = (
                     'Version loaded from {value} does not '
                     'comply with PEP 440: {version}'
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -21,7 +21,7 @@
 from pkg_resources import (
     CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST,
     Environment, find_distributions, safe_name, safe_version,
-    to_filename, Requirement, DEVELOP_DIST, EGG_DIST,
+    to_filename, Requirement, DEVELOP_DIST, EGG_DIST, parse_version,
 )
 from distutils import log
 from distutils.errors import DistutilsError
@@ -294,6 +294,14 @@ def __init__(
         self.to_scan = []
         self.opener = urllib.request.urlopen
 
+    def add(self, dist):
+        # ignore invalid versions
+        try:
+            parse_version(dist.version)
+        except Exception:
+            return
+        return super().add(dist)
+
     # FIXME: 'PackageIndex.process_url' is too complex (14)
     def process_url(self, url, retrieve=False):  # noqa: C901
         """Evaluate a URL as a possible download, and maybe retrieve it"""


Test patch:
diff --git a/pkg_resources/tests/test_resources.py b/pkg_resources/tests/test_resources.py
--- a/pkg_resources/tests/test_resources.py
+++ b/pkg_resources/tests/test_resources.py
@@ -703,7 +703,7 @@ def test_requirements_with_markers(self):
         )
 
     def test_local_version(self):
-        req, = parse_requirements('foo==1.0.org1')
+        req, = parse_requirements('foo==1.0+org1')
 
     def test_spaces_between_multiple_versions(self):
         req, = parse_requirements('foo>=1.0, <3')