1+ import collections
12import os
23import os .path
4+ import subprocess
35import sys
4- import runpy
6+ import sysconfig
57import tempfile
6- import subprocess
78from importlib import resources
89
9- from . import _bundled
10-
1110
1211
1312__all__ = ["version" , "bootstrap" ]
1918 ("pip" , _PIP_VERSION , "py3" ),
2019]
2120
21+ # Packages bundled in ensurepip._bundled have wheel_name set.
22+ # Packages from WHEEL_PKG_DIR have wheel_path set.
23+ _Package = collections .namedtuple ('Package' ,
24+ ('version' , 'wheel_name' , 'wheel_path' ))
25+
26+ # Directory of system wheel packages. Some Linux distribution packaging
27+ # policies recommend against bundling dependencies. For example, Fedora
28+ # installs wheel packages in the /usr/share/python-wheels/ directory and don't
29+ # install the ensurepip._bundled package.
30+ _WHEEL_PKG_DIR = sysconfig .get_config_var ('WHEEL_PKG_DIR' )
31+
32+
33+ def _find_packages (path ):
34+ packages = {}
35+ try :
36+ filenames = os .listdir (path )
37+ except OSError :
38+ # Ignore: path doesn't exist or permission error
39+ filenames = ()
40+ # Make the code deterministic if a directory contains multiple wheel files
41+ # of the same package, but don't attempt to implement correct version
42+ # comparison since this case should not happen.
43+ filenames = sorted (filenames )
44+ for filename in filenames :
45+ # filename is like 'pip-20.2.3-py2.py3-none-any.whl'
46+ if not filename .endswith (".whl" ):
47+ continue
48+ for name in _PACKAGE_NAMES :
49+ prefix = name + '-'
50+ if filename .startswith (prefix ):
51+ break
52+ else :
53+ continue
54+
55+ # Extract '20.2.2' from 'pip-20.2.2-py2.py3-none-any.whl'
56+ version = filename [len (prefix ):].partition ('-' )[0 ]
57+ wheel_path = os .path .join (path , filename )
58+ packages [name ] = _Package (version , None , wheel_path )
59+ return packages
60+
61+
62+ def _get_packages ():
63+ global _PACKAGES , _WHEEL_PKG_DIR
64+ if _PACKAGES is not None :
65+ return _PACKAGES
66+
67+ packages = {}
68+ for name , version , py_tag in _PROJECTS :
69+ wheel_name = f"{ name } -{ version } -{ py_tag } -none-any.whl"
70+ packages [name ] = _Package (version , wheel_name , None )
71+ if _WHEEL_PKG_DIR :
72+ dir_packages = _find_packages (_WHEEL_PKG_DIR )
73+ # only used the wheel package directory if all packages are found there
74+ if all (name in dir_packages for name in _PACKAGE_NAMES ):
75+ packages = dir_packages
76+ _PACKAGES = packages
77+ return packages
78+ _PACKAGES = None
79+
2280
2381def _run_pip (args , additional_paths = None ):
2482 # Run the bootstraping in a subprocess to avoid leaking any state that happens
@@ -44,7 +102,8 @@ def version():
44102 """
45103 Returns a string specifying the bundled version of pip.
46104 """
47- return _PIP_VERSION
105+ return _get_packages ()['pip' ].version
106+
48107
49108def _disable_pip_configuration_settings ():
50109 # We deliberately ignore all pip environment variables
@@ -106,16 +165,23 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
106165 # Put our bundled wheels into a temporary directory and construct the
107166 # additional paths that need added to sys.path
108167 additional_paths = []
109- for project , version , py_tag in _PROJECTS :
110- wheel_name = "{}-{}-{}-none-any.whl" .format (project , version , py_tag )
111- whl = resources .read_binary (
112- _bundled ,
113- wheel_name ,
114- )
115- with open (os .path .join (tmpdir , wheel_name ), "wb" ) as fp :
168+ for name , package in _get_packages ().items ():
169+ if package .wheel_name :
170+ # Use bundled wheel package
171+ from ensurepip import _bundled
172+ wheel_name = package .wheel_name
173+ whl = resources .read_binary (_bundled , wheel_name )
174+ else :
175+ # Use the wheel package directory
176+ with open (package .wheel_path , "rb" ) as fp :
177+ whl = fp .read ()
178+ wheel_name = os .path .basename (package .wheel_path )
179+
180+ filename = os .path .join (tmpdir , wheel_name )
181+ with open (filename , "wb" ) as fp :
116182 fp .write (whl )
117183
118- additional_paths .append (os . path . join ( tmpdir , wheel_name ) )
184+ additional_paths .append (filename )
119185
120186 # Construct the arguments to be passed to the pip command
121187 args = ["install" , "--no-cache-dir" , "--no-index" , "--find-links" , tmpdir ]
@@ -128,7 +194,7 @@ def _bootstrap(*, root=None, upgrade=False, user=False,
128194 if verbosity :
129195 args += ["-" + "v" * verbosity ]
130196
131- return _run_pip (args + [ p [ 0 ] for p in _PROJECTS ], additional_paths )
197+ return _run_pip ([ * args , * _PACKAGE_NAMES ], additional_paths )
132198
133199def _uninstall_helper (* , verbosity = 0 ):
134200 """Helper to support a clean default uninstall process on Windows
@@ -141,11 +207,14 @@ def _uninstall_helper(*, verbosity=0):
141207 except ImportError :
142208 return
143209
144- # If the pip version doesn't match the bundled one, leave it alone
145- if pip .__version__ != _PIP_VERSION :
146- msg = ("ensurepip will only uninstall a matching version "
147- "({!r} installed, {!r} bundled)" )
148- print (msg .format (pip .__version__ , _PIP_VERSION ), file = sys .stderr )
210+ # If the installed pip version doesn't match the available one,
211+ # leave it alone
212+ available_version = version ()
213+ if pip .__version__ != available_version :
214+ print (f"ensurepip will only uninstall a matching version "
215+ f"({ pip .__version__ !r} installed, "
216+ f"{ available_version !r} available)" ,
217+ file = sys .stderr )
149218 return
150219
151220 _disable_pip_configuration_settings ()
@@ -155,7 +224,7 @@ def _uninstall_helper(*, verbosity=0):
155224 if verbosity :
156225 args += ["-" + "v" * verbosity ]
157226
158- return _run_pip (args + [ p [ 0 ] for p in reversed (_PROJECTS )])
227+ return _run_pip ([ * args , * reversed (_PACKAGE_NAMES )])
159228
160229
161230def _main (argv = None ):
0 commit comments