Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using pydevd as a regular dependency. #1586

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading