Repository: pypa/virtualenv

Issue:
virtualenv raises StopIteration exception when processing extra search directory wheels.
**Issue**

When creating virtualenv when using `--extra-search-directory`, ``StopIteration`` exception is raised when processing wheels.
**Environment**

Provide at least:
- OS: Centos7
- ``virtualenv`` is installed:
virtualenv version: 20.0.15
```console
Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib64/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/via_app_data/via_app_data.py", line 75, in _get
    self.app_data,
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/embed/wheels/acquire.py", line 30, in get_wheels
    acquire_from_dir(processed, for_py_version, wheel_cache_dir, extra_search_dir)
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/embed/wheels/acquire.py", line 74, in acquire_from_dir
    if wheel_support_py(filename, for_py_version):
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/embed/wheels/acquire.py", line 91, in wheel_support_py
    requires = next(i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker))
StopIteration
```



Discussion:
Can you please post the content of that extra search directory? Thanks.
```
amqp-2.4.1-py2.py3-none-any.whl
amqplib-1.0.2-py3-none-any.whl
aniso8601-8.0.0-py2.py3-none-any.whl
anyjson-0.3.3-py3-none-any.whl
appdirs-1.4.0-py2.py3-none-any.whl
asn1crypto-0.24.0-py2.py3-none-any.whl
awesome_slugify-1.6.5-py3-none-any.whl
bcrypt-3.1.7-cp34-abi3-manylinux1_x86_64.manylinux2010_x86_64.whl
beautifulsoup4-4.5.1-py3-none-any.whl
billiard-3.6.0.0-py3-none-any.whl
bleach-3.1.0-py2.py3-none-any.whl
celery-4.3.0-py2.py3-none-any.whl
certifi-2018.8.24-py2.py3-none-any.whl
cffi-1.11.5-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
chardet-3.0.4-py2.py3-none-any.whl
click-5.1-py2.py3-none-any.whl
configparser-3.5.0-py3-none-any.whl
contextlib2-0.5.5-py2.py3-none-any.whl
cryptography-2.3-cp34-abi3-manylinux1_x86_64.manylinux2010_x86_64.whl
dburi-3.3-py3-none-any.whl
decorator-4.0.2-py2.py3-none-any.whl
defusedxml-0.5.0-py2.py3-none-any.whl
depocs-1.0.1-py2.py3-none-any.whl
Django-1.11.28-py2.py3-none-any.whl
django_annoying-0.10.3-py2.py3-none-any.whl
django_appconf-1.0.1-py2.py3-none-any.whl
django_auth_ldap-2.0.0-py3-none-any.whl
django_compressor-2.1-py2.py3-none-any.whl
django_cookies_samesite-0.2.0-py2.py3-none-any.whl
django_cryptography-0.3-py2.py3-none-any.whl
django_dirtyfields-1.3.1sc-py3-none-any.whl
django_extensions-1.8.1-py2.py3-none-any.whl
django_filter-1.1.0-py2.py3-none-any.whl
django_grappelli-2.7.3-py3-none-any.whl
django_haystack-2.8.1.dev1-py3-none-any.whl
django_js_reverse-0.9.1-py2.py3-none-any.whl
django_keyvaluestore-0.5-py3-none-any.whl
django_multi_import-1.0.2-py3-none-any.whl
djangorestframework-3.9.4-py2.py3-none-any.whl
djangorestframework_csv-1.4.1-py3-none-any.whl
djangorestframework_filters-0.10.2.post0.dev0-py2.py3-none-any.whl
django_rest_multiple_models-1.6.3-py3-none-any.whl
djangosaml2-0.17.2.dev2-py2.py3-none-any.whl
django_security-0.11.3-py3-none-any.whl
django_taggit-0.22.0-py2.py3-none-any.whl
django_webpack_loader-0.6.0-py2.py3-none-any.whl
docutils-0.15.2-py3-none-any.whl
dparse-0.4.1-py3-none-any.whl
drf_extensions-0.4.0-py2.py3-none-any.whl
elasticsearch-2.4.1-py2.py3-none-any.whl
elementpath-1.3.3-py2.py3-none-any.whl
et_xmlfile-1.0.1-py3-none-any.whl
fabric-2.5.0-py2.py3-none-any.whl
Flask-1.0.2-py2.py3-none-any.whl
Flask_RESTful-0.3.5-py2.py3-none-any.whl
funcsigs-1.0.2-py2.py3-none-any.whl
future-0.16.0-py3-none-any.whl
gnupg-2.0.2-py3-none-any.whl
greenlet-0.4.15-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
gunicorn-19.9.0-py2.py3-none-any.whl
html5lib-1.0.1-py2.py3-none-any.whl
httplib2-0.10.3-py3-none-any.whl
humanfriendly-4.8-py2.py3-none-any.whl
idna-2.5-py2.py3-none-any.whl
inflection-0.3.1-py3-none-any.whl
invoke-1.3.0-py3-none-any.whl
ipaddress-1.0.16-cp36-none-any.whl
iso8601-0.1.12-py2.py3-none-any.whl
isort-4.2.15-py2.py3-none-any.whl
itsdangerous-0.24-py3-none-any.whl
jdcal-1.3-py3-none-any.whl
Jinja2-2.10.1-py2.py3-none-any.whl
kombu-4.4.0-py2.py3-none-any.whl
listen-0.1.7-py3-none-any.whl
lockfile-0.12.2-py2.py3-none-any.whl
lxml-4.4.1-cp36-cp36m-manylinux1_x86_64.manylinux2010_x86_64.whl
Markdown-2.1.1-cp36-none-any.whl
MarkupSafe-0.23-cp36-cp36m-manylinux1_x86_64.whl
mimeparse-0.1.3-py3-none-any.whl
mock-1.0.1-cp36-none-any.whl
monotonic-1.4-py2.py3-none-any.whl
nose-1.3.7-py3-none-any.whl
ntlm_auth-1.2.0-py2.py3-none-any.whl
odfpy-1.3.6-py2.py3-none-any.whl
olefile-0.44-py3-none-any.whl
openpyxl-2.4.9-py2.py3-none-any.whl
ordereddict-1.1-py3-none-any.whl
packaging-16.8-py2.py3-none-any.whl
paramiko-2.4.2-py2.py3-none-any.whl
pbr-5.4.3-py2.py3-none-any.whl
pdfkit-0.6.1-py3-none-any.whl
Pillow-6.2.1-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
pip-18.1-py2.py3-none-any.whl
psutil-4.1.0-cp36-cp36m-manylinux1_x86_64.whl
psycopg2_binary-2.8.2-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
pyasn1-0.4.4-py2.py3-none-any.whl
pyasn1_modules-0.2.2-py2.py3-none-any.whl
pycparser-2.14-cp36-none-any.whl
PyNaCl-1.3.0-cp34-abi3-manylinux1_x86_64.whl
pyOpenSSL-18.0.0-py2.py3-none-any.whl
pyparsing-2.1.10-py2.py3-none-any.whl
PyPDF2-1.26.0-py3-none-any.whl
pysaml2-4.6.2-py2.py3-none-any.whl
python_daemon-2.2.3-py2.py3-none-any.whl
python_dateutil-2.8.0-py2.py3-none-any.whl
python_ldap-3.1.0-cp36-cp36m-manylinux1_x86_64.whl
python_memcached-1.59-py2.py3-none-any.whl
python_pam-1.8.2-py3-none-any.whl
pytz-2016.4-py2.py3-none-any.whl
PyYAML-4.2b4-cp36-cp36m-manylinux1_x86_64.whl
raven-6.10.0-py2.py3-none-any.whl
rcssmin-1.0.6-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
regex-2016.4.15-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
reportlab-3.5.23-cp36-cp36m-manylinux1_x86_64.manylinux2010_x86_64.whl
requests-2.20.0-py2.py3-none-any.whl
requests_ntlm-1.1.0-py2.py3-none-any.whl
requests_unixsocket-0.1.5-py2.py3-none-any.whl
rjsmin-1.0.12-cp36-cp36m-manylinux1_x86_64.manylinux2010_x86_64.whl
ruamel.yaml-0.16.5-py2.py3-none-any.whl
ruamel.yaml.clib-0.2.0-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
see_profile-1.0-py3-none-any.whl
setuptools-41.6.0-py2.py3-none-any.whl
setuptools_scm-3.3.3-py2.py3-none-any.whl
shutilwhich-1.1.0-py3-none-any.whl
simplejson-3.17.0-cp36-cp36m-manylinux2010_x86_64.manylinux1_x86_64.whl
six-1.12.0-py2.py3-none-any.whl
sorl_thumbnail-12.4a1-py2.py3-none-any.whl
sqlitedict-1.6.0-cp36-none-any.whl
sqlparse-0.3.0-py2.py3-none-any.whl
tablib-0.12.1-py3-none-any.whl
tabulate-0.8.2-py3-none-any.whl
tblib-1.3.2-py2.py3-none-any.whl
ua_parser-0.7.3-py2.py3-none-any.whl
uhttplib-0.2-py3-none-any.whl
unicodecsv-0.14.1-py3-none-any.whl
Unidecode-0.4.14-py3-none-any.whl
urllib3-1.24.3-py2.py3-none-any.whl
vine-1.3.0-py2.py3-none-any.whl
virtualenv-13.1.2-py2.py3-none-any.whl
webencodings-0.5-py3-none-any.whl
WebOb-1.8.4-py2.py3-none-any.whl
Werkzeug-0.14-py2.py3-none-any.whl
wheel-0.24.0-py2.py3-none-any.whl
Whoosh-2.5.6.dev0-py2.py3-none-any.whl
xhtml2pdf-0.2.3-py3-none-any.whl
xlrd-1.1.0-py2.py3-none-any.whl
xlwt-1.3.0-py2.py3-none-any.whl
xmlschema-1.0.18-py2.py3-none-any.whl
yantra-0.5-py2.py3-none-any.whl

```

I added print statement before the `requires = next...` to see which wheels it is processing, and it printed out this:
```
FILE PROCESSING: /docs/sde/deployable-releases/5.4.30/tmp6uOU5N/wheel-0.24.0-py2.py3-none-any.whl
FILE PROCESSING: /docs/sde/deployable-releases/5.4.30/tmp6uOU5N/setuptools-41.6.0-py2.py3-none-any.whl
Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib64/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib64/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/via_app_data/via_app_data.py", line 75, in _get
    self.app_data,
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/embed/wheels/acquire.py", line 30, in get_wheels
    acquire_from_dir(processed, for_py_version, wheel_cache_dir, extra_search_dir)
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/embed/wheels/acquire.py", line 74, in acquire_from_dir
    if wheel_support_py(filename, for_py_version):
  File "/usr/local/lib/python3.6/site-packages/virtualenv/seed/embed/wheels/acquire.py", line 91, in wheel_support_py
    requires = next(i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker))
StopIteration

FILE PROCESSING: /docs/sde/deployable-releases/5.4.30/tmp6uOU5N/pip-18.1-py2.py3-none-any.whl
created virtual environment CPython3.6.8.final.0-64 in 205ms
  creator CPython3Posix(dest=/tmp/test_env1, clear=False, global=False)
  seeder FromAppData(extra_search_dir=/docs/sde/deployable-releases/5.4.30/tmp6uOU5N,download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=/root/.local/share/virtualenv/seed-app-data/v1.0.1)
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
```
Seems ``setuptools-41.6.0-py2.py3-none-any.whl`` does not have ``Requires-Python:`` metadata embedded, which we assumed (wrongfully it must always exist).

Resolving patch:
diff --git a/src/virtualenv/seed/embed/wheels/acquire.py b/src/virtualenv/seed/embed/wheels/acquire.py
--- a/src/virtualenv/seed/embed/wheels/acquire.py
+++ b/src/virtualenv/seed/embed/wheels/acquire.py
@@ -87,7 +87,9 @@ def wheel_support_py(filename, py_version):
     with ZipFile(ensure_text(str(filename)), "r") as zip_file:
         metadata = zip_file.read(name).decode("utf-8")
     marker = "Requires-Python:"
-    requires = next(i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker))
+    requires = next((i[len(marker) :] for i in metadata.splitlines() if i.startswith(marker)), None)
+    if requires is None:  # if it does not specify a python requires the assumption is compatible
+        return True
     py_version_int = tuple(int(i) for i in py_version.split("."))
     for require in (i.strip() for i in requires.split(",")):
         # https://www.python.org/dev/peps/pep-0345/#version-specifiers


Test patch:
diff --git a/tests/unit/config/test_env_var.py b/tests/unit/config/test_env_var.py
--- a/tests/unit/config/test_env_var.py
+++ b/tests/unit/config/test_env_var.py
@@ -37,7 +37,7 @@ def test_value_bad(monkeypatch, caplog, empty_conf):
     assert "invalid literal" in caplog.messages[0]
 
 
-def test_extra_search_dir(tmp_path, monkeypatch):
+def test_extra_search_dir_via_env_var(tmp_path, monkeypatch):
     monkeypatch.chdir(tmp_path)
     value = "a{}0{}b{}c".format(os.linesep, os.linesep, os.pathsep)
     monkeypatch.setenv(str("VIRTUALENV_EXTRA_SEARCH_DIR"), str(value))
diff --git a/tests/unit/seed/embed/wheels/test_acquire.py b/tests/unit/seed/embed/wheels/test_acquire.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/seed/embed/wheels/test_acquire.py
@@ -0,0 +1,11 @@
+from virtualenv.seed.embed.wheels.acquire import get_bundled_wheel, wheel_support_py
+
+
+def test_wheel_support_no_python_requires(mocker):
+    wheel = get_bundled_wheel(package="setuptools", version_release=None)
+    zip_mock = mocker.MagicMock()
+    mocker.patch("virtualenv.seed.embed.wheels.acquire.ZipFile", new=zip_mock)
+    zip_mock.return_value.__enter__.return_value.read = lambda name: b""
+
+    supports = wheel_support_py(wheel, "3.8")
+    assert supports is True