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.