Vendor debugpy for VS Code attach debugging
Bundles debugpy 1.7.0 directly in python/ so "Maya: Attach (debugpy)" works with no per-machine pip install step. Installed via Maya 2022's own pip so it resolved a version actually compatible with Python 3.7 (Maya 2022's interpreter), then verified import + listen() succeeds under all three target Maya Python versions (3.7/3.9/3.10). Also fixes a latent __file__-under-exec() bug in start_debug_server.py (same pitfall as Maya's own plugin loader, never hit until this exercised it) and corrects the README's Maya Python version claim -- 2022 ships Python 3.7, not 3.9 as previously stated, which was never independently verified until now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
import contextlib
|
||||
from importlib import import_module
|
||||
import os
|
||||
import sys
|
||||
|
||||
from . import _util
|
||||
|
||||
|
||||
VENDORED_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
# TODO: Move the "pydevd" git submodule to the debugpy/_vendored directory
|
||||
# and then drop the following fallback.
|
||||
if "pydevd" not in os.listdir(VENDORED_ROOT):
|
||||
VENDORED_ROOT = os.path.dirname(VENDORED_ROOT)
|
||||
|
||||
|
||||
def list_all(resolve=False):
|
||||
"""Return the list of vendored projects."""
|
||||
# TODO: Derive from os.listdir(VENDORED_ROOT)?
|
||||
projects = ["pydevd"]
|
||||
if not resolve:
|
||||
return projects
|
||||
return [project_root(name) for name in projects]
|
||||
|
||||
|
||||
def project_root(project):
|
||||
"""Return the path the root dir of the vendored project.
|
||||
|
||||
If "project" is an empty string then the path prefix for vendored
|
||||
projects (e.g. "debugpy/_vendored/") will be returned.
|
||||
"""
|
||||
if not project:
|
||||
project = ""
|
||||
return os.path.join(VENDORED_ROOT, project)
|
||||
|
||||
|
||||
def iter_project_files(project, relative=False, **kwargs):
|
||||
"""Yield (dirname, basename, filename) for all files in the project."""
|
||||
if relative:
|
||||
with _util.cwd(VENDORED_ROOT):
|
||||
for result in _util.iter_all_files(project, **kwargs):
|
||||
yield result
|
||||
else:
|
||||
root = project_root(project)
|
||||
for result in _util.iter_all_files(root, **kwargs):
|
||||
yield result
|
||||
|
||||
|
||||
def iter_packaging_files(project):
|
||||
"""Yield the filenames for all files in the project.
|
||||
|
||||
The filenames are relative to "debugpy/_vendored". This is most
|
||||
useful for the "package data" in a setup.py.
|
||||
"""
|
||||
# TODO: Use default filters? __pycache__ and .pyc?
|
||||
prune_dir = None
|
||||
exclude_file = None
|
||||
try:
|
||||
mod = import_module("._{}_packaging".format(project), __name__)
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
prune_dir = getattr(mod, "prune_dir", prune_dir)
|
||||
exclude_file = getattr(mod, "exclude_file", exclude_file)
|
||||
results = iter_project_files(
|
||||
project, relative=True, prune_dir=prune_dir, exclude_file=exclude_file
|
||||
)
|
||||
for _, _, filename in results:
|
||||
yield filename
|
||||
|
||||
|
||||
def prefix_matcher(*prefixes):
|
||||
"""Return a module match func that matches any of the given prefixes."""
|
||||
assert prefixes
|
||||
|
||||
def match(name, module):
|
||||
for prefix in prefixes:
|
||||
if name.startswith(prefix):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
return match
|
||||
|
||||
|
||||
def check_modules(project, match, root=None):
|
||||
"""Verify that only vendored modules have been imported."""
|
||||
if root is None:
|
||||
root = project_root(project)
|
||||
extensions = []
|
||||
unvendored = {}
|
||||
for modname, mod in list(sys.modules.items()):
|
||||
if not match(modname, mod):
|
||||
continue
|
||||
try:
|
||||
filename = getattr(mod, "__file__", None)
|
||||
except: # In theory it's possible that any error is raised when accessing __file__
|
||||
filename = None
|
||||
if not filename: # extension module
|
||||
extensions.append(modname)
|
||||
elif not filename.startswith(root):
|
||||
unvendored[modname] = filename
|
||||
return unvendored, extensions
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def vendored(project, root=None):
|
||||
"""A context manager under which the vendored project will be imported."""
|
||||
if root is None:
|
||||
root = project_root(project)
|
||||
# Add the vendored project directory, so that it gets tried first.
|
||||
sys.path.insert(0, root)
|
||||
try:
|
||||
yield root
|
||||
finally:
|
||||
sys.path.remove(root)
|
||||
|
||||
|
||||
def preimport(project, modules, **kwargs):
|
||||
"""Import each of the named modules out of the vendored project."""
|
||||
with vendored(project, **kwargs):
|
||||
for name in modules:
|
||||
import_module(name)
|
||||
Reference in New Issue
Block a user