Allow using pydevd as a regular dependency.

This commit is contained in:
Nicolas Graves 2024-05-17 01:42:33 +02:00
parent 6a7f7c0f2d
commit c9edf762df
No known key found for this signature in database
6 changed files with 131 additions and 77 deletions

View file

@ -11,6 +11,10 @@ import subprocess
import sys
# pydevd is unbundled iff BUNDLE_DEBUGPY is set to 0
BUNDLE_DEBUGPY = bool(os.getenv("BUNDLE_DEBUGPY", "1").strip().lower() != "0")
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import versioneer # noqa
@ -18,12 +22,14 @@ 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 BUNDLE_DEBUGPY:
import debugpy._vendored
del sys.path[0]
PYDEVD_ROOT = debugpy._vendored.project_root("pydevd")
if BUNDLE_DEBUGPY:
PYDEVD_ROOT = 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 BUNDLE_DEBUGPY
def override_build(cmds):
@ -147,7 +153,28 @@ if __name__ == "__main__":
cmds = versioneer.get_cmdclass()
override_build(cmds)
override_build_py(cmds)
if BUNDLE_DEBUGPY:
override_build_py(cmds)
data = {"debugpy": ["ThirdPartyNotices.txt"]}
packages = [
"debugpy",
"debugpy.adapter",
"debugpy.common",
"debugpy.launcher",
"debugpy.server",
]
if BUNDLE_DEBUGPY:
data.update(
{
"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.append("debugpy._vendored")
setuptools.setup(
name="debugpy",
@ -177,23 +204,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: BUNDLE_DEBUGPY,
cmdclass=cmds,
# allow the user to call "debugpy" instead of "python -m debugpy"
entry_points={"console_scripts": ["debugpy = debugpy.server.cli:main"]},

View file

@ -24,6 +24,13 @@ __all__ = [ # noqa
import sys
try:
import debugpy._vendored # noqa
is_pydevd_bundled = True
except ImportError:
is_pydevd_bundled = False
assert sys.version_info >= (3, 7), (
"Python 3.6 and below is not supported by this version of debugpy; "
"use debugpy 1.5.1 or earlier."

View file

@ -3,7 +3,6 @@
# for license information.
from importlib import import_module
import os
import warnings
from . import check_modules, prefix_matcher, preimport, vendored
@ -17,23 +16,12 @@ if _unvendored:
# raise ImportError(msg)
warnings.warn(msg + ':\n {}'.format('\n '.join(_unvendored)))
# 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")
# 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.
# due to heavy use of "from" in them.
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=DeprecationWarning)
with vendored('pydevd'):
pydevd_constants = import_module('_pydevd_bundle.pydevd_constants')
# We limit representation size in our representation provider when needed.
pydevd_constants.MAXIMUM_VARIABLE_REPRESENTATION_SIZE = 2 ** 32
# Now make sure all the top-level modules and packages in pydevd are
# loaded. Any pydevd modules that aren't loaded at this point, will
@ -50,32 +38,3 @@ with warnings.catch_warnings():
'pydevd_plugins',
'pydevd',
])
# 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
# Enable some defaults related to debugpy such as sending a single notification when
# threads pause and stopping on any exception.
pydevd_defaults.PydevdCustomization.DEBUG_MODE = 'debugpy-dap'
# This is important when pydevd attaches automatically to a subprocess. In this case, we have to
# make sure that debugpy is properly put back in the game for users to be able to use it.
pydevd_defaults.PydevdCustomization.PREIMPORT = '%s;%s' % (
os.path.dirname(os.path.dirname(debugpy.__file__)),
'debugpy._vendored.force_pydevd'
)

View file

@ -2,6 +2,69 @@
# Licensed under the MIT License. See LICENSE in the project root
# for license information.
from __future__ import absolute_import, division, print_function, unicode_literals
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 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"
)
# 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.
if debugpy.is_pydevd_bundled:
try:
import debugpy._vendored.force_pydevd # noqa
pydevd_constants = debugpy._vendored.force_pydevd.pydevd_constants
except Exception as e:
raise e
else:
pydevd_constants = import_module("_pydevd_bundle.pydevd_constants")
# We limit representation size in our representation provider when needed.
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
)
# Enable some defaults related to debugpy such as sending a single notification when
# threads pause and stopping on any exception.
pydevd_defaults.PydevdCustomization.DEBUG_MODE = "debugpy-dap"
# This is important when pydevd attaches automatically to a subprocess. In this case, we have to
# make sure that debugpy is properly put back in the game for users to be able to use it.
if not(debugpy.is_pydevd_bundled):
pydevd_defaults.PydevdCustomization.PREIMPORT = "%s;%s" % (
os.path.dirname(os.path.dirname(debugpy.__file__)),
"debugpy._vendored.force_pydevd",
)

View file

@ -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 not debugpy.is_pydevd_bundled:
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 debugpy.is_pydevd_bundled:
# NOTE: At this point it should be safe to remove this.
sys.path.remove(pydevd_attach_to_process_path)
except:
import traceback

View file

@ -3,6 +3,12 @@
# for license information.
import pytest
import debugpy
@pytest.mark.skipif(not debugpy.is_pydevd_bundled, reason="pydevd is not bundled")
def test_vendoring(pyfile):
@pyfile
def import_debugpy():