Skip to content

Commit 06319f2

Browse files
committed
Fix #12666 by pre-loading script wrapper code
1 parent 3f3bc60 commit 06319f2

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

src/pip/_vendor/distlib/scripts.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@
4949
sys.exit(%(func)s())
5050
'''
5151

52+
# Pre-fetch the contents of all executable wrapper stubs.
53+
# This is to address https://github.com/pypa/pip/issues/12666.
54+
# When updating pip, we rename the old pip in place before installing the
55+
# new version. If we try to fetch a wrapper *after* that rename, the finder
56+
# machinery will be confused as the package is no longer available at the
57+
# location where it was imported from. So we load everything into memory in
58+
# advance.
59+
60+
# Issue 31: don't hardcode an absolute package name, but
61+
# determine it relative to the current package
62+
distlib_package = __name__.rsplit('.', 1)[0]
63+
64+
WRAPPERS = {
65+
r.name: r.bytes
66+
for r in finder(distlib_package).iterator("")
67+
if r.name.endswith(".exe")
68+
}
69+
5270

5371
def enquote_executable(executable):
5472
if ' ' in executable:
@@ -409,15 +427,11 @@ def _get_launcher(self, kind):
409427
bits = '32'
410428
platform_suffix = '-arm' if get_platform() == 'win-arm64' else ''
411429
name = '%s%s%s.exe' % (kind, bits, platform_suffix)
412-
# Issue 31: don't hardcode an absolute package name, but
413-
# determine it relative to the current package
414-
distlib_package = __name__.rsplit('.', 1)[0]
415-
resource = finder(distlib_package).find(name)
416-
if not resource:
430+
if name not in WRAPPERS:
417431
msg = ('Unable to find resource %s in package %s' %
418432
(name, distlib_package))
419433
raise ValueError(msg)
420-
return resource.bytes
434+
return WRAPPERS[name]
421435

422436
# Public API follows
423437

tests/functional/test_self_update.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Check that pip can update itself correctly
2+
3+
from typing import Any
4+
5+
6+
def test_self_update_editable(script: Any, pip_src: Any) -> None:
7+
# Test that if we have an environment with pip installed in non-editable
8+
# mode, that pip can safely update itself to an editable install.
9+
# See https://github.com/pypa/pip/issues/12666 for details.
10+
11+
# Step 1. Install pip as non-editable. This is expected to succeed as
12+
# the existing pip in the environment is installed in editable mode, so
13+
# it only places a .pth file in the environment.
14+
proc = script.pip("install", pip_src)
15+
assert proc.returncode == 0
16+
# Step 2. Using the pip we just installed, install pip *again*, but
17+
# in editable mode. This could fail, as we'll need to uninstall the running
18+
# pip in order to install the new copy, and uninstalling pip while it's
19+
# running could fail. This test is specifically to ensure that doesn't
20+
# happen...
21+
proc = script.pip("install", "-e", pip_src)
22+
assert proc.returncode == 0

0 commit comments

Comments
 (0)