github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/please_pex/pex_main.py (about) 1 """Zipfile entry point which supports auto-extracting itself based on zip-safety.""" 2 3 import imp 4 import importlib 5 import os 6 import runpy 7 import site 8 import sys 9 10 # Put this pex on the path before anything else. 11 PEX = os.path.abspath(sys.argv[0]) 12 # This might get overridden down the line if the pex isn't zip-safe. 13 PEX_PATH = PEX 14 sys.path = [PEX_PATH] + sys.path 15 16 # These will get templated in by the build rules. 17 MODULE_DIR = '__MODULE_DIR__' 18 ENTRY_POINT = '__ENTRY_POINT__' 19 ZIP_SAFE = __ZIP_SAFE__ 20 21 22 class ModuleDirImport(object): 23 """Allows the given directory to be imported as though it was at the top level.""" 24 25 def __init__(self, package=MODULE_DIR): 26 self.modules = set(self._listdir(package.replace('.', '/'))) 27 self.prefix = package + '.' 28 29 def find_module(self, fullname, path=None): 30 """Attempt to locate module. Returns self if found, None if not.""" 31 name, _, _ = fullname.partition('.') 32 if name in self.modules: 33 return self 34 35 def load_module(self, fullname): 36 mod = importlib.import_module(self.prefix + fullname) 37 mod.__name__ = fullname # Make sure it has the expected name. 38 sys.modules[fullname] = mod 39 return mod 40 41 def _listdir(self, dirname): 42 """Yields the contents of the given directory as Python modules.""" 43 import imp, zipfile 44 suffixes = sorted([x[0] for x in imp.get_suffixes()], key=lambda x: -len(x)) 45 with zipfile.ZipFile(PEX, 'r') as zf: 46 for name in zf.namelist(): 47 if name.startswith(dirname): 48 path, _ = self.splitext(name[len(dirname)+1:], suffixes) 49 if path: 50 path, _, _ = path.partition('/') 51 yield path.replace('/', '.') 52 53 def splitext(self, path, suffixes): 54 """Similar to os.path.splitext, but splits our longest known suffix preferentially.""" 55 for suffix in suffixes: 56 if path.endswith(suffix): 57 return path[:-len(suffix)], suffix 58 return None, None 59 60 61 class SoImport(object): 62 """So import. Much binary. Such dynamic. Wow.""" 63 64 def __init__(self): 65 import zipfile 66 67 self.suffixes = {x[0]: x for x in imp.get_suffixes() if x[2] == imp.C_EXTENSION} 68 self.suffixes_by_length = sorted(self.suffixes, key=lambda x: -len(x)) 69 # Identify all the possible modules we could handle. 70 self.modules = {} 71 if zipfile.is_zipfile(sys.argv[0]): 72 zf = zipfile.ZipFile(sys.argv[0]) 73 for name in zf.namelist(): 74 path, _ = self.splitext(name) 75 if path: 76 if path.startswith('.bootstrap/'): 77 path = path[len('.bootstrap/'):] 78 self.modules.setdefault(path.replace('/', '.'), name) 79 if self.modules: 80 self.zf = zf 81 82 def find_module(self, fullname, path=None): 83 """Attempt to locate module. Returns self if found, None if not.""" 84 if fullname in self.modules: 85 return self 86 87 def load_module(self, fullname): 88 """Actually load a module that we said we'd handle in find_module.""" 89 import tempfile 90 91 filename = self.modules[fullname] 92 prefix, ext = self.splitext(filename) 93 suffix = self.suffixes[ext] 94 with tempfile.NamedTemporaryFile(suffix=ext, prefix=os.path.basename(prefix)) as f: 95 f.write(self.zf.read(filename)) 96 f.flush() 97 mod = imp.load_module(fullname, None, f.name, suffix) 98 # Make it look like module came from the original location for nicer tracebacks. 99 mod.__file__ = filename 100 return mod 101 102 def splitext(self, path): 103 """Similar to os.path.splitext, but splits our longest known suffix preferentially.""" 104 for suffix in self.suffixes_by_length: 105 if path.endswith(suffix): 106 return path[:-len(suffix)], suffix 107 return None, None 108 109 110 def override_import(package=MODULE_DIR): 111 """Augments system importer to allow importing from the given module as though it were at the top level.""" 112 sys.meta_path.insert(0, ModuleDirImport(package)) 113 114 115 def clean_sys_path(): 116 """Remove anything from sys.path that isn't either the pex or the main Python install dir. 117 118 NB: *not* site-packages or dist-packages or any of that malarkey, just the place where 119 we get the actual Python standard library packages from). 120 This would be cleaner if we could suppress loading site in the first place, but that isn't 121 as easy as all that to build into a pex, unfortunately. 122 """ 123 site_packages = site.getsitepackages() 124 sys.path = [x for x in sys.path if not any(x.startswith(pkg) for pkg in site_packages)] 125 126 127 def explode_zip(): 128 """Extracts the current pex to a temp directory where we can import everything from. 129 130 This is primarily used for binary extensions which can't be imported directly from 131 inside a zipfile. 132 """ 133 import contextlib, shutil, tempfile, zipfile 134 135 @contextlib.contextmanager 136 def _explode_zip(): 137 # We need to update the actual variable; other modules are allowed to look at 138 # these variables to find out what's going on (e.g. are we zip-safe or not). 139 global PEX_PATH 140 PEX_PATH = tempfile.mkdtemp(dir=os.environ.get('TEMP_DIR'), prefix='pex_') 141 with zipfile.ZipFile(PEX, 'r') as zf: 142 zf.extractall(PEX_PATH) 143 # Strip the pex paths so nothing accidentally imports from there. 144 sys.path = [PEX_PATH] + [x for x in sys.path if x != PEX] 145 yield 146 shutil.rmtree(PEX_PATH) 147 148 return _explode_zip 149 150 151 def profile(filename): 152 """Returns a context manager to perform profiling while the program runs. 153 154 This is triggered by setting the PEX_PROFILE_FILENAME env var to the destination file, 155 at which point this will be invoked automatically at pex startup. 156 """ 157 import contextlib, cProfile 158 159 @contextlib.contextmanager 160 def _profile(): 161 profiler = cProfile.Profile() 162 profiler.enable() 163 yield 164 profiler.disable() 165 sys.stderr.write('Writing profiler output to %s\n' % filename) 166 profiler.dump_stats(filename) 167 168 return _profile 169 170 171 def interact(main): 172 """If PEX_INTERPRETER is set, then starts an interactive console, otherwise runs main().""" 173 if os.environ.get('PEX_INTERPRETER', '0') != '0': 174 import code 175 code.interact() 176 else: 177 return main() 178 179 180 def main(): 181 """Runs the 'real' entry point of the pex. 182 183 N.B. This gets redefined by test_main to run tests instead. 184 """ 185 # Must run this as __main__ so it executes its own __name__ == '__main__' block. 186 runpy.run_module(ENTRY_POINT, run_name='__main__') 187 return 0 # unless some other exception gets raised, we're successful.