Skip to content
Closed
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
57 changes: 40 additions & 17 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,25 @@
import sys


DEBUGPY_BUNDLING_DISABLED = bool(os.getenv('DEBUGPY_BUNDLING_DISABLED'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity, let's invert the meaning - i.e. BUNDLE_DEBUGPY which defaults to true but can be overridden.



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

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__))


Expand Down Expand Up @@ -67,7 +73,7 @@ def iter_vendored_files():
# relevant setuptools versions.
class ExtModules(list):
def __bool__(self):
return True
return not DEBUGPY_BUNDLING_DISABLED


def override_build(cmds):
Expand Down Expand Up @@ -133,9 +139,24 @@ def tail_is(*suffixes):


if __name__ == "__main__":
if not os.getenv("SKIP_CYTHON_BUILD"):
if not (os.getenv("SKIP_CYTHON_BUILD") or DEBUGPY_BUNDLING_DISABLED):
cython_build()

# Etch bundling status in the source.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary - the code can check at runtime whether debugpy._vendored is present by trying to import it and catching ImportError. This can be cached as a package global to make one-liner checks easier.

Also, if caching, I'd prefer the variable name to not use the __reserved__ naming pattern - it can be simply is_pydevd_bundled or something like that. We declare our public API surface via __all__, so the name not starting with _ is not a concern.

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:
Expand All @@ -145,6 +166,18 @@ def tail_is(*suffixes):
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(),
Expand Down Expand Up @@ -173,20 +206,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": list(iter_vendored_files()),
},
packages=packages,
package_data=data,
ext_modules=ExtModules(),
has_ext_modules=lambda: True,
has_ext_modules=lambda: not DEBUGPY_BUNDLING_DISABLED,
cmdclass=cmds,
**extras
)
2 changes: 2 additions & 0 deletions src/debugpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ def trace_this_thread(should_trace):

__version__ = _version.get_versions()["version"]

__bundling_disabled__ = False

# Force absolute path on Python 2.
__file__ = os.path.abspath(__file__)

Expand Down
46 changes: 45 additions & 1 deletion src/debugpy/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,50 @@

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.__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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of duplicated code. I think force_pydevd should be refactored to only contain the logic that is related to vendoring, while this one can do all the configuration instead, and conditionally call the vendoring part at the right point (there might be more than one, so force_pydevd might need to be split into functions).

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 @@ -8,6 +8,7 @@

import os

import debugpy

__file__ = os.path.abspath(__file__)
_debugpy_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
Expand All @@ -30,25 +31,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 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

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

Expand Down
5 changes: 5 additions & 0 deletions tests/tests/test_vendoring.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import pytest

import debugpy

@pytest.mark.skipif(debugpy.__bundling_disabled__, reason='Bundling disabled')
def test_vendoring(pyfile):
@pyfile
def import_debugpy():
Expand Down