Allow using pydevd as a regular dependency. Submitted upstream at: https://github.com/microsoft/debugpy/pull/902 diff -rup a/setup.py b/setup.py --- a/setup.py 2024-11-29 23:38:58.549980612 +0100 +++ b/setup.py 2024-11-30 00:04:14.281540335 +0100 @@ -11,6 +11,9 @@ import subprocess import sys +DEBUGPY_BUNDLING_DISABLED = bool(os.getenv('DEBUGPY_BUNDLING_DISABLED')) + + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import versioneer # noqa @@ -18,12 +21,15 @@ del sys.path[0] sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")) import debugpy -import debugpy._vendored + +if not DEBUGPY_BUNDLING_DISABLED: + import debugpy._vendored del sys.path[0] -PYDEVD_ROOT = debugpy._vendored.project_root("pydevd") +PYDEVD_ROOT = (None if DEBUGPY_BUNDLING_DISABLED else + debugpy._vendored.project_root("pydevd")) DEBUGBY_ROOT = os.path.dirname(os.path.abspath(debugpy.__file__)) @@ -46,7 +52,7 @@ def get_buildplatform(): # relevant setuptools versions. class ExtModules(list): def __bool__(self): - return True + return not DEBUGPY_BUNDLING_DISABLED def override_build(cmds): @@ -72,6 +78,8 @@ def override_build_py(cmds): # in data files for binary builds. def finalize_options(self): original(self) + if DEBUGPY_BUNDLING_DISABLED: + return True # Ensure that pydevd extensions are present for inclusion into data_files. self.announce( @@ -140,6 +148,20 @@ with open("DESCRIPTION.md", "r") as fh: if __name__ == "__main__": + if debugpy.__bundling_disabled__ != DEBUGPY_BUNDLING_DISABLED: + + with open(os.path.join(DEBUGBY_ROOT, '__init__.py'), 'r') as f: + lines = f.readlines() + with open(os.path.join(DEBUGBY_ROOT, '__init__.py'), 'w') as f: + edited = [] + for line in lines: + if line.startswith('__bundling_disabled__'): + edited.append( + f'__bundling_disabled__ = {DEBUGPY_BUNDLING_DISABLED}\n') + else: + edited.append(line) + f.writelines(edited) + extras = {} platforms = get_buildplatform() if platforms is not None: @@ -149,6 +171,18 @@ if __name__ == "__main__": override_build(cmds) override_build_py(cmds) + data = {"debugpy": ["ThirdPartyNotices.txt"]} + packages = [ + "debugpy", + "debugpy.adapter", + "debugpy.common", + "debugpy.launcher", + "debugpy.server", + ] + if not DEBUGPY_BUNDLING_DISABLED: + data.update({"debugpy._vendored": list(iter_vendored_files())}) + packages.append("debugpy._vendored") + setuptools.setup( name="debugpy", version=versioneer.get_version(), @@ -177,23 +211,10 @@ if __name__ == "__main__": "License :: OSI Approved :: MIT License", ], package_dir={"": "src"}, - packages=[ - "debugpy", - "debugpy.adapter", - "debugpy.common", - "debugpy.launcher", - "debugpy.server", - "debugpy._vendored", - ], - package_data={ - "debugpy": ["ThirdPartyNotices.txt"], - "debugpy._vendored": [ - # pydevd extensions must be built before this list can be computed properly, - # so it is populated in the overridden build_py.finalize_options(). - ], - }, + packages=packages, + package_data=data, ext_modules=ExtModules(), - has_ext_modules=lambda: True, + has_ext_modules=lambda: not DEBUGPY_BUNDLING_DISABLED, cmdclass=cmds, # allow the user to call "debugpy" instead of "python -m debugpy" entry_points={"console_scripts": ["debugpy = debugpy.server.cli:main"]}, diff -rup a/src/debugpy/__init__.py b/src/debugpy/__init__.py --- a/src/debugpy/__init__.py 2024-11-29 23:38:58.553980600 +0100 +++ b/src/debugpy/__init__.py 2024-11-29 23:49:38.776095806 +0100 @@ -34,5 +34,6 @@ assert sys.version_info >= (3, 7), ( # SyntaxError on Python 2 and preventing the above version check from executing. from debugpy.public_api import * # noqa from debugpy.public_api import __version__ +__bundling_disabled__ = False del sys diff -rup a/src/debugpy/server/attach_pid_injected.py b/src/debugpy/server/attach_pid_injected.py --- a/src/debugpy/server/attach_pid_injected.py 2024-11-29 23:38:58.553980600 +0100 +++ b/src/debugpy/server/attach_pid_injected.py 2024-11-29 23:39:48.933831730 +0100 @@ -6,6 +6,7 @@ import os +import debugpy __file__ = os.path.abspath(__file__) _debugpy_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) @@ -28,25 +29,29 @@ def attach(setup): def on_critical(msg): print(msg, file=sys.stderr) - pydevd_attach_to_process_path = os.path.join( - _debugpy_dir, - "debugpy", - "_vendored", - "pydevd", - "pydevd_attach_to_process", - ) - assert os.path.exists(pydevd_attach_to_process_path) - sys.path.insert(0, pydevd_attach_to_process_path) + if debugpy.__bundling_disabled__: + from pydevd_attach_to_process import attach_script + else: + pydevd_attach_to_process_path = os.path.join( + _debugpy_dir, + "debugpy", + "_vendored", + "pydevd", + "pydevd_attach_to_process", + ) + assert os.path.exists(pydevd_attach_to_process_path) + sys.path.insert(0, pydevd_attach_to_process_path) - # NOTE: that it's not a part of the pydevd PYTHONPATH - import attach_script + # NOTE: that it's not a part of the pydevd PYTHONPATH + import attach_script attach_script.fix_main_thread_id( on_warn=on_warn, on_exception=on_exception, on_critical=on_critical ) - # NOTE: At this point it should be safe to remove this. - sys.path.remove(pydevd_attach_to_process_path) + if not debugpy.__bundling_disabled__: + # NOTE: At this point it should be safe to remove this. + sys.path.remove(pydevd_attach_to_process_path) except: import traceback diff -rup a/src/debugpy/server/__init__.py b/src/debugpy/server/__init__.py --- a/src/debugpy/server/__init__.py 2024-11-29 23:38:58.553980600 +0100 +++ b/src/debugpy/server/__init__.py 2024-11-29 23:39:48.933831730 +0100 @@ -2,6 +2,50 @@ # Licensed under the MIT License. See LICENSE in the project root # for license information. +from importlib import import_module +import os + # "force_pydevd" must be imported first to ensure (via side effects) # that the debugpy-vendored copy of pydevd gets used. -import debugpy._vendored.force_pydevd # noqa +import debugpy +if debugpy.__bundling_disabled__: + # Do what force_pydevd.py does, but using the system-provided + # pydevd. + + # XXX: This is copied here so that the whole '_vendored' directory + # can be deleted when DEBUGPY_BUNDLING_DISABLED is set. + + # If debugpy logging is enabled, enable it for pydevd as well + if "DEBUGPY_LOG_DIR" in os.environ: + os.environ[str("PYDEVD_DEBUG")] = str("True") + os.environ[str("PYDEVD_DEBUG_FILE")] = \ + os.environ["DEBUGPY_LOG_DIR"] + str("/debugpy.pydevd.log") + + # Work around https://github.com/microsoft/debugpy/issues/346. + # Disable pydevd frame-eval optimizations only if unset, to allow opt-in. + if "PYDEVD_USE_FRAME_EVAL" not in os.environ: + os.environ[str("PYDEVD_USE_FRAME_EVAL")] = str("NO") + + # Constants must be set before importing any other pydevd module + # due to heavy use of "from" in them. + pydevd_constants = import_module('_pydevd_bundle.pydevd_constants') + # The default pydevd value is 1000. + pydevd_constants.MAXIMUM_VARIABLE_REPRESENTATION_SIZE = 2 ** 32 + + # When pydevd is imported it sets the breakpoint behavior, but it needs to be + # overridden because by default pydevd will connect to the remote debugger using + # its own custom protocol rather than DAP. + import pydevd # noqa + import debugpy # noqa + + def debugpy_breakpointhook(): + debugpy.breakpoint() + + pydevd.install_breakpointhook(debugpy_breakpointhook) + + # Ensure that pydevd uses JSON protocol + from _pydevd_bundle import pydevd_constants + from _pydevd_bundle import pydevd_defaults + pydevd_defaults.PydevdCustomization.DEFAULT_PROTOCOL = pydevd_constants.HTTP_JSON_PROTOCOL +else: + import debugpy._vendored.force_pydevd # noqa diff -rup a/tests/tests/test_vendoring.py b/tests/tests/test_vendoring.py --- a/tests/tests/test_vendoring.py 2024-11-29 23:38:58.549980612 +0100 +++ b/tests/tests/test_vendoring.py 2024-11-29 23:50:22.879966500 +0100 @@ -3,6 +3,11 @@ # for license information. +import pytest + +import debugpy + +@pytest.mark.skipif(debugpy.__bundling_disabled__, reason='Bundling disabled') def test_vendoring(pyfile): @pyfile def import_debugpy():