From 15d9408ef9d5e5550470df7d5d16f2532ddb83b7 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Thu, 2 May 2024 21:08:26 +0100 Subject: [PATCH 1/2] Preload script wrappers at import time --- distlib/scripts.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/distlib/scripts.py b/distlib/scripts.py index 01f225c..378f83d 100644 --- a/distlib/scripts.py +++ b/distlib/scripts.py @@ -48,6 +48,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: @@ -386,14 +404,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: - msg = ('Unable to find resource %s in package %s' % (name, distlib_package)) + 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 40c8a807dc963a5ca9c3baa92bdc43b3bc74c341 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Thu, 2 May 2024 21:35:40 +0100 Subject: [PATCH 2/2] Only preload on Windows, which is the only place the wrappers are used --- distlib/scripts.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/distlib/scripts.py b/distlib/scripts.py index 378f83d..0cf6640 100644 --- a/distlib/scripts.py +++ b/distlib/scripts.py @@ -56,15 +56,16 @@ # 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] +if os.name == 'nt' or (os.name == 'java' and os._name == 'nt'): + # 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") -} + WRAPPERS = { + r.name: r.bytes + for r in finder(DISTLIB_PACKAGE).iterator("") + if r.name.endswith(".exe") + } def enquote_executable(executable): @@ -406,7 +407,7 @@ def _get_launcher(self, kind): name = '%s%s%s.exe' % (kind, bits, platform_suffix) if name not in WRAPPERS: msg = ('Unable to find resource %s in package %s' % - (name, distlib_package)) + (name, DISTLIB_PACKAGE)) raise ValueError(msg) return WRAPPERS[name]