--- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -1,3 +1,4 @@ +import glob import os import os.path import pkgutil @@ -8,13 +9,10 @@ import tempfile __all__ = ["version", "bootstrap"] -_SETUPTOOLS_VERSION = "41.2.0" - -_PIP_VERSION = "19.2.3" - _PROJECTS = [ - ("setuptools", _SETUPTOOLS_VERSION), - ("pip", _PIP_VERSION), + "setuptools", + "pip", + "pkg_resources", ] @@ -24,15 +22,22 @@ def _run_pip(args, additional_paths=None sys.path = additional_paths + sys.path # Install the bundled software - import pip._internal - return pip._internal.main(args) + try: + import pip._internal + return pip._internal.main(args) + except ImportError: + import pip + return pip.main(args) def version(): """ Returns a string specifying the bundled version of pip. """ - return _PIP_VERSION + wheel_names = glob.glob('/usr/share/python-wheels/pip-*.whl') + assert len(wheel_names) == 1, wheel_names + return os.path.basename(wheel_names[0]).split('-')[1] + def _disable_pip_configuration_settings(): # We deliberately ignore all pip environment variables @@ -90,20 +95,44 @@ def _bootstrap(*, root=None, upgrade=Fal # omit pip and easy_install os.environ["ENSUREPIP_OPTIONS"] = "install" + # Debian: The bundled wheels are useless to us because we must use ones + # crafted from source code in the archive. As we build the virtual + # environment, copy the wheels from the system location into the virtual + # environment, and place those wheels on sys.path. + def copy_wheels(wheels, destdir, paths): + for project in wheels: + wheel_names = glob.glob( + '/usr/share/python-wheels/{}-*.whl'.format(project)) + if len(wheel_names) == 0: + raise RuntimeError('missing dependency wheel %s' % project) + assert len(wheel_names) == 1, wheel_names + wheel_name = os.path.basename(wheel_names[0]) + path = os.path.join('/usr/share/python-wheels', wheel_name) + with open(path, 'rb') as fp: + whl = fp.read() + dest = os.path.join(destdir, wheel_name) + with open(dest, 'wb') as fp: + fp.write(whl) + paths.append(dest) + with tempfile.TemporaryDirectory() as tmpdir: + # This directory is a "well known directory" which Debian has patched + # pip to look in when attempting to locate wheels to use to satisfy + # the dependencies that pip normally bundles but Debian has debundled. + # This is critically important and if this directory changes then both + # python-pip and python-virtualenv needs updated to match. + venv_wheel_dir = os.path.join(sys.prefix, 'share', 'python-wheels') + os.makedirs(venv_wheel_dir, exist_ok=True) + dependencies = [ + os.path.basename(whl).split('-')[0] + for whl in glob.glob('/usr/share/python-wheels/*.whl') + ] + copy_wheels(dependencies, venv_wheel_dir, sys.path) + # Put our bundled wheels into a temporary directory and construct the # additional paths that need added to sys.path additional_paths = [] - for project, version in _PROJECTS: - wheel_name = "{}-{}-py2.py3-none-any.whl".format(project, version) - whl = pkgutil.get_data( - "ensurepip", - "_bundled/{}".format(wheel_name), - ) - with open(os.path.join(tmpdir, wheel_name), "wb") as fp: - fp.write(whl) - - additional_paths.append(os.path.join(tmpdir, wheel_name)) + copy_wheels(_PROJECTS, tmpdir, additional_paths) # Construct the arguments to be passed to the pip command args = ["install", "--no-index", "--find-links", tmpdir] @@ -116,7 +145,7 @@ def _bootstrap(*, root=None, upgrade=Fal if verbosity: args += ["-" + "v" * verbosity] - return _run_pip(args + [p[0] for p in _PROJECTS], additional_paths) + return _run_pip(args + _PROJECTS, additional_paths) def _uninstall_helper(*, verbosity=0): """Helper to support a clean default uninstall process on Windows @@ -130,7 +159,8 @@ def _uninstall_helper(*, verbosity=0): return # If the pip version doesn't match the bundled one, leave it alone - if pip.__version__ != _PIP_VERSION: + # Disabled for Debian, always using the version from the python3-pip package. + if False and pip.__version__ != _PIP_VERSION: msg = ("ensurepip will only uninstall a matching version " "({!r} installed, {!r} bundled)") print(msg.format(pip.__version__, _PIP_VERSION), file=sys.stderr) @@ -143,7 +173,7 @@ def _uninstall_helper(*, verbosity=0): if verbosity: args += ["-" + "v" * verbosity] - return _run_pip(args + [p[0] for p in reversed(_PROJECTS)]) + return _run_pip(args + reversed(_PROJECTS)) def _main(argv=None):