Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/12666.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix intermittent "cannot locate t64.exe" errors when upgrading pip.
26 changes: 20 additions & 6 deletions src/pip/_vendor/distlib/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@
sys.exit(%(func)s())
'''

# Pre-fetch the contents of all executable wrapper stubs.
# This is to address https://github.com/pypa/pip/issues/12666.
# When updating pip, we rename the old pip in place before installing the
# new version. If we try to fetch a wrapper *after* that rename, the finder
# machinery will be confused as the package is no longer available at the
# location where it was imported from. So we load everything into memory in
# advance.

# Issue 31: don't hardcode an absolute package name, but
# determine it relative to the current package
distlib_package = __name__.rsplit('.', 1)[0]

WRAPPERS = {
r.name: r.bytes
for r in finder(distlib_package).iterator("")
if r.name.endswith(".exe")
}


def enquote_executable(executable):
if ' ' in executable:
Expand Down Expand Up @@ -409,15 +427,11 @@ def _get_launcher(self, kind):
bits = '32'
platform_suffix = '-arm' if get_platform() == 'win-arm64' else ''
name = '%s%s%s.exe' % (kind, bits, platform_suffix)
# Issue 31: don't hardcode an absolute package name, but
# determine it relative to the current package
distlib_package = __name__.rsplit('.', 1)[0]
resource = finder(distlib_package).find(name)
if not resource:
if name not in WRAPPERS:
msg = ('Unable to find resource %s in package %s' %
(name, distlib_package))
raise ValueError(msg)
return resource.bytes
return WRAPPERS[name]

# Public API follows

Expand Down
22 changes: 22 additions & 0 deletions tests/functional/test_self_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Check that pip can update itself correctly

from typing import Any


def test_self_update_editable(script: Any, pip_src: Any) -> None:
# Test that if we have an environment with pip installed in non-editable
# mode, that pip can safely update itself to an editable install.
# See https://github.com/pypa/pip/issues/12666 for details.

# Step 1. Install pip as non-editable. This is expected to succeed as
# the existing pip in the environment is installed in editable mode, so
# it only places a .pth file in the environment.
proc = script.pip("install", pip_src)
assert proc.returncode == 0
# Step 2. Using the pip we just installed, install pip *again*, but
# in editable mode. This could fail, as we'll need to uninstall the running
# pip in order to install the new copy, and uninstalling pip while it's
# running could fail. This test is specifically to ensure that doesn't
# happen...
proc = script.pip("install", "-e", pip_src)
assert proc.returncode == 0
47 changes: 47 additions & 0 deletions tools/vendoring/patches/distlib.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
diff --git a/src/pip/_vendor/distlib/scripts.py b/src/pip/_vendor/distlib/scripts.py
index cfa45d2af..e16292b83 100644
--- a/src/pip/_vendor/distlib/scripts.py
+++ b/src/pip/_vendor/distlib/scripts.py
@@ -49,6 +49,24 @@ if __name__ == '__main__':
sys.exit(%(func)s())
'''

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

def enquote_executable(executable):
if ' ' in executable:
@@ -409,15 +427,11 @@ class ScriptMaker(object):
bits = '32'
platform_suffix = '-arm' if get_platform() == 'win-arm64' else ''
name = '%s%s%s.exe' % (kind, bits, platform_suffix)
- # Issue 31: don't hardcode an absolute package name, but
- # determine it relative to the current package
- distlib_package = __name__.rsplit('.', 1)[0]
- resource = finder(distlib_package).find(name)
- if not resource:
+ if name not in WRAPPERS:
msg = ('Unable to find resource %s in package %s' %
(name, distlib_package))
raise ValueError(msg)
- return resource.bytes
+ return WRAPPERS[name]

# Public API follows