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.