Repository: pypa/virtualenv

Issue:
pip installed in virtualenv matches virtualenv installation python not python in virtualenv
This one's a little esoteric - but if you install virtualenv>20 with python2, then create a virtualenv with --python=python3 - the pip commands installed in the virtualenv are pip and pip2 rather than pip and pip3. Basically, the suffix is matching the python that the virtualenv is running with, not the python that the virtualenv is installing.

For example:

```
# pip2 install 'virtualenv==20.0.3'
# /usr/bin/virtualenv -p python3 blerg . # this is in a centos7 container, pip2 installs into /usr/bin
# ls blerg/bin/pip*
pip pip2 pip2.7
# rm -rf blerg
# pip2 install 'virtualenv<20'
# /usr/bin/virtualenv -p python3 blerg
# ls blerg/bin/pip*
pip pip3 pip3.6
```


Discussion:
We need to convince our script maker to use the target environments version, not the host pythons version at https://github.com/pypa/virtualenv/blob/master/src/virtualenv/seed/via_app_data/pip_install/base.py#L133-L140
I believe distlib needs a feature to make this happen: https://bitbucket.org/pypa/distlib/issues/134/scriptmaker-ability-to-select-the-version

Resolving patch:
diff --git a/src/virtualenv/seed/via_app_data/pip_install/base.py b/src/virtualenv/seed/via_app_data/pip_install/base.py
--- a/src/virtualenv/seed/via_app_data/pip_install/base.py
+++ b/src/virtualenv/seed/via_app_data/pip_install/base.py
@@ -4,9 +4,12 @@
 import os
 import re
 import shutil
+import sys
 import zipfile
 from abc import ABCMeta, abstractmethod
+from contextlib import contextmanager
 from tempfile import mkdtemp
+from threading import Lock
 
 from six import PY3, add_metaclass
 
@@ -17,6 +20,8 @@
 
 @add_metaclass(ABCMeta)
 class PipInstall(object):
+    lock = Lock()
+
     def __init__(self, wheel, creator, image_folder):
         self._wheel = wheel
         self._creator = creator
@@ -29,7 +34,7 @@ def __init__(self, wheel, creator, image_folder):
     def _sync(self, src, dst):
         raise NotImplementedError
 
-    def install(self):
+    def install(self, version_info):
         self._extracted = True
         # sync image
         for filename in self._image_dir.iterdir():
@@ -44,7 +49,7 @@ def install(self):
         consoles = set()
         script_dir = self._creator.script_dir
         for name, module in self._console_scripts.items():
-            consoles.update(self._create_console_entry_point(name, module, script_dir))
+            consoles.update(self._create_console_entry_point(name, module, script_dir, version_info))
         logging.debug("generated console scripts %s", " ".join(i.name for i in consoles))
 
     def build_image(self):
@@ -77,10 +82,11 @@ def _generate_new_files(self):
         try:
             to_folder = Path(folder)
             rel = os.path.relpath(ensure_text(str(self._creator.script_dir)), ensure_text(str(self._creator.purelib)))
+            version_info = self._creator.interpreter.version_info
             for name, module in self._console_scripts.items():
                 new_files.update(
                     Path(os.path.normpath(ensure_text(str(self._image_dir / rel / i.name))))
-                    for i in self._create_console_entry_point(name, module, to_folder)
+                    for i in self._create_console_entry_point(name, module, to_folder, version_info)
                 )
         finally:
             shutil.rmtree(folder, ignore_errors=True)
@@ -123,7 +129,7 @@ def _console_scripts(self):
                         self._console_entry_points[name] = value
         return self._console_entry_points
 
-    def _create_console_entry_point(self, name, value, to_folder):
+    def _create_console_entry_point(self, name, value, to_folder, version_info):
         result = []
         from distlib.scripts import ScriptMaker
 
@@ -133,10 +139,25 @@ def _create_console_entry_point(self, name, value, to_folder):
         maker.set_mode = True  # ensure they are executable
         maker.executable = str(self._creator.exe)
         specification = "{} = {}".format(name, value)
-        new_files = maker.make(specification)
+        with self.switch_sys_version(version_info):
+            new_files = maker.make(specification)
         result.extend(Path(i) for i in new_files)
         return result
 
+    @contextmanager
+    def switch_sys_version(self, version_info):
+        """
+        Patch until upstream distutils supports creating scripts with different python target
+        https://bitbucket.org/pypa/distlib/issues/134/allow-specifying-the-version-information
+        """
+        previous = sys.version_info
+        with self.lock:
+            sys.version_info = version_info
+            try:
+                yield
+            finally:
+                sys.version_info = previous
+
     def clear(self):
         if self._image_dir.exists():
             shutil.rmtree(ensure_text(str(self._image_dir)))
diff --git a/src/virtualenv/seed/via_app_data/via_app_data.py b/src/virtualenv/seed/via_app_data/via_app_data.py
--- a/src/virtualenv/seed/via_app_data/via_app_data.py
+++ b/src/virtualenv/seed/via_app_data/via_app_data.py
@@ -60,7 +60,7 @@ def _install(name, wheel):
                     installer.clear()
                 if not installer.has_image():
                     installer.build_image()
-                installer.install()
+                installer.install(creator.interpreter.version_info)
 
             threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items())
             for thread in threads:


Test patch:
diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py
--- a/tests/unit/create/test_creator.py
+++ b/tests/unit/create/test_creator.py
@@ -290,13 +290,17 @@ def test_cross_major(cross_python, coverage_env, tmp_path, current_fastest):
         "-p",
         ensure_text(cross_python.executable),
         ensure_text(str(tmp_path)),
-        "--no-seed",
+        "--no-setuptools",
+        "--no-wheel",
         "--activators",
         "",
         "--creator",
         current_fastest,
     ]
     result = cli_run(cmd)
+    pip_scripts = {i.name.replace(".exe", "") for i in result.creator.script_dir.iterdir() if i.name.startswith("pip")}
+    major, minor = cross_python.version_info[0:2]
+    assert pip_scripts == {"pip", "pip-{}.{}".format(major, minor), "pip{}".format(major)}
     coverage_env()
     env = PythonInfo.from_exe(str(result.creator.exe))
     assert env.version_info.major != CURRENT.version_info.major