77import pathlib
88import sys
99import textwrap
10- import zipfile
1110from collections import OrderedDict
1211from sysconfig import get_paths
1312from types import TracebackType
2928
3029logger = logging .getLogger (__name__ )
3130
31+ PIP_RUNNER = """
32+ import importlib.util
33+ import os
34+ import runpy
35+ import sys
36+
37+
38+ class PipImportRedirectingFinder:
39+
40+ @classmethod
41+ def find_spec(cls, fullname, path=None, target=None):
42+ if not fullname.startswith("pip."):
43+ return None
44+
45+ # Import pip from the current source directory
46+ location = os.path.join({source!r}, *fullname.split("."))
47+ return importlib.util.spec_from_file_location(fullname, location)
48+
49+
50+ sys.meta_path.insert(0, PipImportRedirectingFinder())
51+ runpy.run_module("pip", run_name="__main__")
52+ """
53+
3254
3355class _Prefix :
3456 def __init__ (self , path : str ) -> None :
@@ -42,29 +64,25 @@ def __init__(self, path: str) -> None:
4264
4365
4466@contextlib .contextmanager
45- def _create_standalone_pip () -> Generator [str , None , None ]:
46- """Create a "standalone pip" zip file.
67+ def _create_runnable_pip () -> Generator [str , None , None ]:
68+ """Create a "pip runner" file.
4769
48- The zip file's content is identical to the currently-running pip.
70+ The runner file ensures that import for pip happen using the currently-running pip.
4971 It will be used to install requirements into the build environment.
5072 """
5173 source = pathlib .Path (pip_location ).resolve ().parent
5274
53- # Return the current instance if `source` is not a directory. We can't build
54- # a zip from this, and it likely means the instance is already standalone.
75+ # Return the current instance if `source` is not a directory. It likely
76+ # means that this executable is already standalone.
5577 if not source .is_dir ():
5678 yield str (source )
5779 return
5880
5981 with TempDirectory (kind = "standalone-pip" ) as tmp_dir :
60- pip_zip = os .path .join (tmp_dir .path , "__env_pip__.zip" )
61- kwargs = {}
62- if sys .version_info >= (3 , 8 ):
63- kwargs ["strict_timestamps" ] = False
64- with zipfile .ZipFile (pip_zip , "w" , ** kwargs ) as zf :
65- for child in source .rglob ("*" ):
66- zf .write (child , child .relative_to (source .parent ).as_posix ())
67- yield os .path .join (pip_zip , "pip" )
82+ pip_runner = os .path .join (tmp_dir .path , "__pip-runner__.py" )
83+ with open (pip_runner , "w" , encoding = "utf8" ) as f :
84+ f .write (PIP_RUNNER .format (source = os .fsdecode (source )))
85+ yield pip_runner
6886
6987
7088class BuildEnvironment :
@@ -206,7 +224,7 @@ def install_requirements(
206224 if not requirements :
207225 return
208226 with contextlib .ExitStack () as ctx :
209- pip_runnable = ctx .enter_context (_create_standalone_pip ())
227+ pip_runnable = ctx .enter_context (_create_runnable_pip ())
210228 self ._install_requirements (
211229 pip_runnable ,
212230 finder ,
0 commit comments