Repository: pypa/pip

Issue:
Allow specification of alternative pip version
CI systems might be using get-pip.py, but if the latest release breaks something it would be nice to be able to simply specify a standard version string, something like `./get-pip.py pip==7.1.2`.

This comes from #2393 which is a pull request.

Related to #1087.

Improve get pip
Allow passing versions to pip/setuptools/wheel.

<!-- Reviewable:start -->

[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/pypa/pip/3337)

<!-- Reviewable:end -->



Discussion:

@dstufft any idea why `/home/travis/build/pypa/pip` is leaking inside the test virtualenvs ?
cf https://travis-ci.org/pypa/pip/jobs/100001794

I'm going to kick this out of 8.0, just because I don't believe that we need it in 8.0. We can update `get-pip.py` at any time outside of a release.

In that vein, I'm not sure we can write tests for it, or if we do if they should exist inside of the pip test directory. 


Resolving patch:
diff --git a/contrib/get-pip.py b/contrib/get-pip.py
--- a/contrib/get-pip.py
+++ b/contrib/get-pip.py
@@ -81,6 +81,7 @@ def bootstrap(tmpdir=None):
     # Import pip so we can use it to install pip and maybe setuptools too
     import pip
     from pip.commands.install import InstallCommand
+    from pip.req import InstallRequirement
 
     # Wrapper to provide default certificate with the lowest priority
     class CertInstallCommand(InstallCommand):
@@ -95,31 +96,47 @@ def parse_args(self, args):
 
     pip.commands_dict["install"] = CertInstallCommand
 
-    # We always want to install pip
-    packages = ["pip"]
+    implicit_pip = True
+    implicit_setuptools = True
+    implicit_wheel = True
 
     # Check if the user has requested us not to install setuptools
     if "--no-setuptools" in sys.argv or os.environ.get("PIP_NO_SETUPTOOLS"):
         args = [x for x in sys.argv[1:] if x != "--no-setuptools"]
+        implicit_setuptools = False
     else:
         args = sys.argv[1:]
 
-        # We want to see if setuptools is available before attempting to
-        # install it
-        try:
-            import setuptools  # noqa
-        except ImportError:
-            packages += ["setuptools"]
-
     # Check if the user has requested us not to install wheel
     if "--no-wheel" in args or os.environ.get("PIP_NO_WHEEL"):
         args = [x for x in args if x != "--no-wheel"]
-    else:
-        # We want to see if wheel is available before attempting to install it.
+        implicit_wheel = False
+
+    # We want to support people passing things like 'pip<8' to get-pip.py which
+    # will let them install a specific version. However because of the dreaded
+    # DoubleRequirement error if any of the args look like they might be a
+    # specific for one of our packages, then we'll turn off the implicit
+    # install of them.
+    for arg in args:
         try:
-            import wheel  # noqa
-        except ImportError:
-            args += ["wheel"]
+            req = InstallRequirement.from_line(arg)
+        except:
+            continue
+
+        if implicit_pip and req.name == "pip":
+            implicit_pip = False
+        elif implicit_setuptools and req.name == "setuptools":
+            implicit_setuptools = False
+        elif implicit_wheel and req.name == "wheel":
+            implicit_wheel = False
+
+    # Add any implicit installations to the end of our args
+    if implicit_pip:
+        args += ["pip"]
+    if implicit_setuptools:
+        args += ["setuptools"]
+    if implicit_wheel:
+        args += ["wheel"]
 
     delete_tmpdir = False
     try:
@@ -137,7 +154,7 @@ def parse_args(self, args):
 
         # Execute the included pip and use it to install the latest pip and
         # setuptools from PyPI
-        sys.exit(pip.main(["install", "--upgrade"] + packages + args))
+        sys.exit(pip.main(["install", "--upgrade"] + args))
     finally:
         # Remove our temporary directory
         if delete_tmpdir and tmpdir:
diff --git a/tasks/generate.py b/tasks/generate.py
--- a/tasks/generate.py
+++ b/tasks/generate.py
@@ -124,6 +124,7 @@ def bootstrap(tmpdir=None):
     # Import pip so we can use it to install pip and maybe setuptools too
     import pip
     from pip.commands.install import InstallCommand
+    from pip.req import InstallRequirement
 
     # Wrapper to provide default certificate with the lowest priority
     class CertInstallCommand(InstallCommand):
@@ -138,31 +139,47 @@ def parse_args(self, args):
 
     pip.commands_dict["install"] = CertInstallCommand
 
-    # We always want to install pip
-    packages = ["pip"]
+    implicit_pip = True
+    implicit_setuptools = True
+    implicit_wheel = True
 
     # Check if the user has requested us not to install setuptools
     if "--no-setuptools" in sys.argv or os.environ.get("PIP_NO_SETUPTOOLS"):
         args = [x for x in sys.argv[1:] if x != "--no-setuptools"]
+        implicit_setuptools = False
     else:
         args = sys.argv[1:]
 
-        # We want to see if setuptools is available before attempting to
-        # install it
-        try:
-            import setuptools  # noqa
-        except ImportError:
-            packages += ["setuptools"]
-
     # Check if the user has requested us not to install wheel
     if "--no-wheel" in args or os.environ.get("PIP_NO_WHEEL"):
         args = [x for x in args if x != "--no-wheel"]
-    else:
-        # We want to see if wheel is available before attempting to install it.
+        implicit_wheel = False
+
+    # We want to support people passing things like 'pip<8' to get-pip.py which
+    # will let them install a specific version. However because of the dreaded
+    # DoubleRequirement error if any of the args look like they might be a
+    # specific for one of our packages, then we'll turn off the implicit
+    # install of them.
+    for arg in args:
         try:
-            import wheel  # noqa
-        except ImportError:
-            args += ["wheel"]
+            req = InstallRequirement.from_line(arg)
+        except:
+            continue
+
+        if implicit_pip and req.name == "pip":
+            implicit_pip = False
+        elif implicit_setuptools and req.name == "setuptools":
+            implicit_setuptools = False
+        elif implicit_wheel and req.name == "wheel":
+            implicit_wheel = False
+
+    # Add any implicit installations to the end of our args
+    if implicit_pip:
+        args += ["pip"]
+    if implicit_setuptools:
+        args += ["setuptools"]
+    if implicit_wheel:
+        args += ["wheel"]
 
     delete_tmpdir = False
     try:
@@ -180,7 +197,7 @@ def parse_args(self, args):
 
         # Execute the included pip and use it to install the latest pip and
         # setuptools from PyPI
-        sys.exit(pip.main(["install", "--upgrade"] + packages + args))
+        sys.exit(pip.main(["install", "--upgrade"] + args))
     finally:
         # Remove our temporary directory
         if delete_tmpdir and tmpdir:


Test patch: