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