Skip to content

Commit

Permalink
Allow using pydevd as a regular dependency.
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-graves committed May 17, 2024
1 parent 42853a9 commit 9543d56
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 78 deletions.
54 changes: 33 additions & 21 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,23 @@
import sys


BUNDLE_DEBUGPY = not (os.getenv("BUNDLE_DEBUGPY").strip().lower() == "0")

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import versioneer # noqa

del sys.path[0]

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src"))
import debugpy
import debugpy._vendored

del sys.path[0]
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__))


Expand All @@ -46,7 +50,7 @@ def get_buildplatform():
# relevant setuptools versions.
class ExtModules(list):
def __bool__(self):
return True
return BUNDLE_DEBUGPY


def override_build(cmds):
Expand Down Expand Up @@ -147,7 +151,28 @@ def tail_is(*suffixes):

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",
Expand Down Expand Up @@ -177,23 +202,10 @@ def tail_is(*suffixes):
"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,
**extras
)
7 changes: 7 additions & 0 deletions src/debugpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@

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."
Expand Down
43 changes: 1 addition & 42 deletions src/debugpy/_vendored/force_pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# for license information.

from importlib import import_module
import os
import warnings

from . import check_modules, prefix_matcher, preimport, vendored
Expand All @@ -17,23 +16,12 @@
# 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
Expand All @@ -50,32 +38,3 @@
'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'
)
66 changes: 65 additions & 1 deletion src/debugpy/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,70 @@
# 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")

BUNDLE_DEBUGPY = bool(os.getenv("BUNDLE_DEBUGPY"))

# Constants must be set before importing any other pydevd module
# due to heavy use of "from" in them.
if BUNDLE_DEBUGPY:
try:
import debugpy._vendored.force_pydevd # noqa
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 BUNDLE_DEBUGPY:
pydevd_defaults.PydevdCustomization.PREIMPORT = "%s;%s" % (
os.path.dirname(os.path.dirname(debugpy.__file__)),
"debugpy._vendored.force_pydevd",
)
33 changes: 19 additions & 14 deletions src/debugpy/server/attach_pid_injected.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)))
Expand All @@ -28,25 +29,29 @@ def on_exception(msg):
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)

# NOTE: that it's not a part of the pydevd PYTHONPATH
import attach_script
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

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

Expand Down
6 changes: 6 additions & 0 deletions tests/tests/test_vendoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down

0 comments on commit 9543d56

Please sign in to comment.