Repository: pypa/virtualenv

Issue:
RuntimeError: 'Exec format error' when using pipenv+pyenv-installed python 3.8.1 to install environment
Thanks for submitting an issue!

If submitting a BUG please provide:

- [X ] Minimal reproducible example or detailed descriptions
```
/Users/mhjeon/.local/share/virtualenvs/nowwow-0D8RInBf/bin/python3 -mvirtualenv /tmp/env -p /Users/mhjeon/.local/share/virtualenvs/nowwow-0D8RInBf/bin/python3
```
This gives me an output of
```
RuntimeError: failed to query /Users/mhjeon/.pyenv/versions/3.8.1/Python.framework/Versions/3.8/python with code 8 err: 'Exec format error'
```
- [ ] OS and `pip list` output
using virtualenv 20.0.0b2
 
Please see [this issue for more detail](https://github.com/pre-commit/pre-commit/issues/1321)

Discussion:

Patch:
diff --git a/docs/changelog/1545.bugfix.rst b/docs/changelog/1545.bugfix.rst
new file mode 100644
index 00000000..ce432d7a
--- /dev/null
+++ b/docs/changelog/1545.bugfix.rst
@@ -0,0 +1,6 @@
+Improve python discovery mechanism:
+
+- do not fail if there are executables that fail to query (e.g. for not having execute access to it) on the ``PATH``,
+- beside the prefix folder also try with the platform dependent binary folder within that,
+
+by :user:`gaborbernat`.
diff --git a/docs/changelog/1574.bugfix.rst b/docs/changelog/1574.bugfix.rst
new file mode 100644
index 00000000..af5a17ea
--- /dev/null
+++ b/docs/changelog/1574.bugfix.rst
@@ -0,0 +1,3 @@
+While discovering a python executables interpreters that cannot be queried are now displayed with info level rather
+than warning, so now they're no longer shown by default (these can be just executables to which we don't have access
+or that are broken, don't warn if it's not the target Python we want) - by :user:`gaborbernat`.
diff --git a/docs/changelog/1575.bugfix.rst b/docs/changelog/1575.bugfix.rst
new file mode 100644
index 00000000..2dd2d72e
--- /dev/null
+++ b/docs/changelog/1575.bugfix.rst
@@ -0,0 +1,1 @@
+Respect the :option:`copies` flag inside the ``app-data`` :option:`seeder` by :user:`gaborbernat`.
diff --git a/src/virtualenv/discovery/cached_py_info.py b/src/virtualenv/discovery/cached_py_info.py
index 73b2310d..7e28018f 100644
--- a/src/virtualenv/discovery/cached_py_info.py
+++ b/src/virtualenv/discovery/cached_py_info.py
@@ -36,7 +36,7 @@ def from_exe(cls, exe, raise_on_error=True, ignore_cache=False):
         if raise_on_error:
             raise result
         else:
-            logging.warning("%s", str(result))
+            logging.info("%s", str(result))
         result = None
     return result
 
diff --git a/src/virtualenv/discovery/py_info.py b/src/virtualenv/discovery/py_info.py
index eb153414..211778c4 100644
--- a/src/virtualenv/discovery/py_info.py
+++ b/src/virtualenv/discovery/py_info.py
@@ -338,7 +338,9 @@ class PythonInfo(object):
             for name in possible_names:
                 exe_path = os.path.join(folder, name)
                 if os.path.exists(exe_path):
-                    info = self.from_exe(exe_path, resolve_to_host=False)
+                    info = self.from_exe(exe_path, resolve_to_host=False, raise_on_error=False)
+                    if info is None:  # ignore if for some reason we can't query
+                        continue
                     for item in ["implementation", "architecture", "version_info"]:
                         found = getattr(info, item)
                         searched = getattr(self, item)
@@ -411,6 +413,9 @@ class PythonInfo(object):
 
         # or at root level
         candidate_folder[inside_folder] = None
+        if self.executable.startswith(self.prefix):
+            binary_within = os.path.relpath(os.path.dirname(self.executable), self.prefix)
+            candidate_folder[os.path.join(inside_folder, binary_within)] = None
 
         return list(candidate_folder.keys())
 
diff --git a/src/virtualenv/seed/via_app_data/via_app_data.py b/src/virtualenv/seed/via_app_data/via_app_data.py
index 069a9c1c..9bfd9d12 100644
--- a/src/virtualenv/seed/via_app_data/via_app_data.py
+++ b/src/virtualenv/seed/via_app_data/via_app_data.py
@@ -9,7 +9,6 @@ from threading import Lock, Thread
 import six
 
 from virtualenv.dirs import default_data_dir
-from virtualenv.info import IS_WIN
 from virtualenv.seed.embed.base_embed import BaseEmbed
 from virtualenv.seed.embed.wheels.acquire import get_wheels
 
@@ -22,6 +21,7 @@ class FromAppData(BaseEmbed):
         super(FromAppData, self).__init__(options)
         self.clear = options.clear_app_data
         self.app_data_dir = default_data_dir() / "seed-v1"
+        self.symlinks = getattr(options, "copies", False) is False
 
     @classmethod
     def add_parser_arguments(cls, parser, interpreter):
@@ -86,10 +86,8 @@ class FromAppData(BaseEmbed):
 
             yield name_to_whl
 
-    @staticmethod
-    def installer_class(pip_version):
-        # tbd: on Windows symlinks are unreliable, we have junctions for folders, however pip does not work well with it
-        if not IS_WIN:
+    def installer_class(self, pip_version):
+        if self.symlinks:
             # symlink support requires pip 19.3+
             pip_version_int = tuple(int(i) for i in pip_version.split(".")[0:2])
             if pip_version_int >= (19, 3):


Test patch:
diff --git a/tests/unit/discovery/py_info/test_py_info.py b/tests/unit/discovery/py_info/test_py_info.py
index 1b3fe38f..b5272e47 100644
--- a/tests/unit/discovery/py_info/test_py_info.py
+++ b/tests/unit/discovery/py_info/test_py_info.py
@@ -205,7 +205,12 @@ def test_system_executable_no_exact_match(target, discovered, position, tmp_path
     target_py_info = _make_py_info(target)
     mocker.patch.object(target_py_info, "_find_possible_exe_names", return_value=names)
     mocker.patch.object(target_py_info, "_find_possible_folders", return_value=[str(tmp_path)])
-    mocker.patch.object(target_py_info, "from_exe", side_effect=lambda k, resolve_to_host: discovered_with_path[k])
+
+    # noinspection PyUnusedLocal
+    def func(k, resolve_to_host, raise_on_error):
+        return discovered_with_path[k]
+
+    mocker.patch.object(target_py_info, "from_exe", side_effect=func)
     target_py_info.real_prefix = str(tmp_path)
 
     target_py_info.system_executable = None