Format: text/x-python
⬇ Download File (3.9 KB)
   1  # Copyright (c) Microsoft Corporation. All rights reserved.
   2  # Licensed under the MIT License. See LICENSE in the project root
   3  # for license information.
   4  
   5  import contextlib
   6  from importlib import import_module
   7  import os
   8  import sys
   9  
  10  from . import _util
  11  
  12  
  13  VENDORED_ROOT = os.path.dirname(os.path.abspath(__file__))
  14  # TODO: Move the "pydevd" git submodule to the debugpy/_vendored directory
  15  # and then drop the following fallback.
  16  if "pydevd" not in os.listdir(VENDORED_ROOT):
  17      VENDORED_ROOT = os.path.dirname(VENDORED_ROOT)
  18  
  19  
  20  def list_all(resolve=False):
  21      """Return the list of vendored projects."""
  22      # TODO: Derive from os.listdir(VENDORED_ROOT)?
  23      projects = ["pydevd"]
  24      if not resolve:
  25          return projects
  26      return [project_root(name) for name in projects]
  27  
  28  
  29  def project_root(project):
  30      """Return the path the root dir of the vendored project.
  31  
  32      If "project" is an empty string then the path prefix for vendored
  33      projects (e.g. "debugpy/_vendored/") will be returned.
  34      """
  35      if not project:
  36          project = ""
  37      return os.path.join(VENDORED_ROOT, project)
  38  
  39  
  40  def iter_project_files(project, relative=False, **kwargs):
  41      """Yield (dirname, basename, filename) for all files in the project."""
  42      if relative:
  43          with _util.cwd(VENDORED_ROOT):
  44              for result in _util.iter_all_files(project, **kwargs):
  45                  yield result
  46      else:
  47          root = project_root(project)
  48          for result in _util.iter_all_files(root, **kwargs):
  49              yield result
  50  
  51  
  52  def iter_packaging_files(project):
  53      """Yield the filenames for all files in the project.
  54  
  55      The filenames are relative to "debugpy/_vendored".  This is most
  56      useful for the "package data" in a setup.py.
  57      """
  58      # TODO: Use default filters?  __pycache__ and .pyc?
  59      prune_dir = None
  60      exclude_file = None
  61      try:
  62          mod = import_module("._{}_packaging".format(project), __name__)
  63      except ImportError:
  64          pass
  65      else:
  66          prune_dir = getattr(mod, "prune_dir", prune_dir)
  67          exclude_file = getattr(mod, "exclude_file", exclude_file)
  68      results = iter_project_files(
  69          project, relative=True, prune_dir=prune_dir, exclude_file=exclude_file
  70      )
  71      for _, _, filename in results:
  72          yield filename
  73  
  74  
  75  def prefix_matcher(*prefixes):
  76      """Return a module match func that matches any of the given prefixes."""
  77      assert prefixes
  78  
  79      def match(name, module):
  80          for prefix in prefixes:
  81              if name.startswith(prefix):
  82                  return True
  83          else:
  84              return False
  85  
  86      return match
  87  
  88  
  89  def check_modules(project, match, root=None):
  90      """Verify that only vendored modules have been imported."""
  91      if root is None:
  92          root = project_root(project)
  93      extensions = []
  94      unvendored = {}
  95      for modname, mod in list(sys.modules.items()):
  96          if not match(modname, mod):
  97              continue
  98          try:
  99              filename = getattr(mod, "__file__", None)
 100          except:  # In theory it's possible that any error is raised when accessing __file__
 101              filename = None
 102          if not filename:  # extension module
 103              extensions.append(modname)
 104          elif not filename.startswith(root):
 105              unvendored[modname] = filename
 106      return unvendored, extensions
 107  
 108  
 109  @contextlib.contextmanager
 110  def vendored(project, root=None):
 111      """A context manager under which the vendored project will be imported."""
 112      if root is None:
 113          root = project_root(project)
 114      # Add the vendored project directory, so that it gets tried first.
 115      sys.path.insert(0, root)
 116      try:
 117          yield root
 118      finally:
 119          sys.path.remove(root)
 120  
 121  
 122  def preimport(project, modules, **kwargs):
 123      """Import each of the named modules out of the vendored project."""
 124      with vendored(project, **kwargs):
 125          for name in modules:
 126              import_module(name)
 127