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

Improve debugpy's capabilities (Cherry-pick of #15946) #15980

Merged
Merged
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
21 changes: 1 addition & 20 deletions src/python/pants/backend/python/goals/pytest_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
from pants.engine.unions import UnionMembership, UnionRule, union
from pants.option.global_options import GlobalOptions
from pants.util.logging import LogLevel
from pants.util.strutil import softwrap

logger = logging.getLogger()

Expand Down Expand Up @@ -410,32 +409,14 @@ async def debugpy_python_test(
pytest: PyTest,
) -> TestDebugAdapterRequest:
debugpy_pex = await Get(Pex, PexRequest, debugpy.to_pex_request())
if pytest.main.spec != "pytest":
logger.warning(
softwrap(
"""
Ignoring custom [pytest].console_script/entry_point when using the debug adapter.
`debugpy` (Python's Debug Adapter) doesn't support this use-case yet.
"""
)
)

setup = await Get(
TestSetup,
TestSetupRequest(
field_set,
is_debug=True,
main=debugpy.main,
prepend_argv=(
"--listen",
f"{debug_adapter.host}:{debug_adapter.port}",
"--wait-for-client",
# @TODO: Techincally we should use `pytest.main`, however `debugpy` doesn't support
# launching an entry_point.
# https://github.com/microsoft/debugpy/issues/955
"-m",
"pytest",
),
prepend_argv=debugpy.get_args(debug_adapter, pytest.main),
additional_pexes=(debugpy_pex,),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import os
import re
import unittest.mock
from textwrap import dedent

import pytest
Expand Down Expand Up @@ -610,7 +611,7 @@ def test_debug_adaptor_request_argv(rule_runner: RuleRunner) -> None:
"--listen",
"127.0.0.1:5678",
"--wait-for-client",
"-m",
"pytest",
"-c",
unittest.mock.ANY,
"tests/python/pants_test/test_foo.py",
)
36 changes: 35 additions & 1 deletion src/python/pants/backend/python/subsystems/debugpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
from pants.backend.python.goals.lockfile import GeneratePythonLockfile
from pants.backend.python.subsystems.python_tool_base import PythonToolBase
from pants.backend.python.subsystems.setup import PythonSetup
from pants.backend.python.target_types import EntryPoint
from pants.backend.python.target_types import ConsoleScript, EntryPoint, MainSpecification
from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel
from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem
from pants.engine.rules import collect_rules, rule
from pants.engine.unions import UnionRule
from pants.option.option_types import ArgsListOption
from pants.util.docutil import git_url


class DebugPy(PythonToolBase):
options_scope = "debugpy"
name = options_scope
help = "An implementation of the Debug Adapter Protocol for Python (https://github.com/microsoft/debugpy)."

default_version = "debugpy==1.6.0"
Expand All @@ -29,6 +32,37 @@ class DebugPy(PythonToolBase):
default_lockfile_path = "src/python/pants/backend/python/subsystems/debugpy.lock"
default_lockfile_url = git_url(default_lockfile_path)

args = ArgsListOption(example="--log-to-stderr")

@staticmethod
def _get_main_spec_args(main: MainSpecification) -> tuple[str, ...]:
if isinstance(main, EntryPoint):
if main.function:
return ("-c", f"import {main.module};{main.module}.{main.function}();")
return ("-m", main.module)

assert isinstance(main, ConsoleScript)
# NB: This is only necessary while debugpy supports Py 3.7, as `importlib.metadata` was
# added in Py 3.8 and would allow us to resolve the console_script using just the stdlib.
return (
"-c",
(
"from pex.third_party.pkg_resources import working_set;"
+ f"(next(working_set.iter_entry_points('console_scripts', '{main.name}')).resolve())()"
),
)

def get_args(
self, debug_adapter: DebugAdapterSubsystem, main: MainSpecification
) -> tuple[str, ...]:
return (
"--listen",
f"{debug_adapter.host}:{debug_adapter.port}",
"--wait-for-client",
*self.args,
*self._get_main_spec_args(main),
)


class DebugPyLockfileSentinel(GenerateToolLockfileSentinel):
resolve_name = DebugPy.options_scope
Expand Down