From 06319f213f004baf82275525284512a201127da5 Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Thu, 2 May 2024 13:27:42 +0100
Subject: [PATCH 1/3] Fix #12666 by pre-loading script wrapper code
---
src/pip/_vendor/distlib/scripts.py | 26 ++++++++++++++++++++------
tests/functional/test_self_update.py | 22 ++++++++++++++++++++++
2 files changed, 42 insertions(+), 6 deletions(-)
create mode 100644 tests/functional/test_self_update.py
diff --git a/src/pip/_vendor/distlib/scripts.py b/src/pip/_vendor/distlib/scripts.py
index cfa45d2af18..e16292b8330 100644
--- a/src/pip/_vendor/distlib/scripts.py
+++ b/src/pip/_vendor/distlib/scripts.py
@@ -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:
@@ -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
diff --git a/tests/functional/test_self_update.py b/tests/functional/test_self_update.py
new file mode 100644
index 00000000000..c507552208a
--- /dev/null
+++ b/tests/functional/test_self_update.py
@@ -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
From e5a11b9afceb821adc266f28cbeb8556a2389210 Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Thu, 2 May 2024 13:35:37 +0100
Subject: [PATCH 2/3] Make the distlib changes into a patch
---
tools/vendoring/patches/distlib.patch | 47 +++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
create mode 100644 tools/vendoring/patches/distlib.patch
diff --git a/tools/vendoring/patches/distlib.patch b/tools/vendoring/patches/distlib.patch
new file mode 100644
index 00000000000..de2834710a3
--- /dev/null
+++ b/tools/vendoring/patches/distlib.patch
@@ -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
+
From 8fe52159b4182e5658bd15624409e27e1611e655 Mon Sep 17 00:00:00 2001
From: Paul Moore
Date: Thu, 2 May 2024 13:51:09 +0100
Subject: [PATCH 3/3] Add a news file
---
news/12666.bugfix.rst | 1 +
1 file changed, 1 insertion(+)
create mode 100644 news/12666.bugfix.rst
diff --git a/news/12666.bugfix.rst b/news/12666.bugfix.rst
new file mode 100644
index 00000000000..72a4a65155e
--- /dev/null
+++ b/news/12666.bugfix.rst
@@ -0,0 +1 @@
+Fix intermittent "cannot locate t64.exe" errors when upgrading pip.