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