From 27ae8bdcc0aabafa689f3c29d793e7e4499035bc Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Tue, 14 Jun 2022 19:26:55 -0500 Subject: [PATCH 01/18] Add debug adapter to run # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../pants/backend/docker/goals/run_image.py | 11 +++++++- .../pants/backend/go/goals/run_binary.py | 11 +++++++- .../backend/python/goals/run_pex_binary.py | 11 +++++++- .../python/packaging/pyoxidizer/rules.py | 11 +++++++- .../pants/backend/shell/shell_command.py | 11 +++++++- src/python/pants/core/goals/run.py | 27 ++++++++++++++++++- src/python/pants/core/goals/test.py | 1 + src/python/pants/jvm/run_deploy_jar.py | 11 +++++++- 8 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/python/pants/backend/docker/goals/run_image.py b/src/python/pants/backend/docker/goals/run_image.py index 9182d1175a6..dfa5da63e79 100644 --- a/src/python/pants/backend/docker/goals/run_image.py +++ b/src/python/pants/backend/docker/goals/run_image.py @@ -9,7 +9,7 @@ from pants.backend.docker.subsystems.docker_options import DockerOptions from pants.backend.docker.util_rules.docker_binary import DockerBinary from pants.core.goals.package import BuiltPackage, PackageFieldSet -from pants.core.goals.run import RunRequest +from pants.core.goals.run import RunDebugAdapterRequest, RunRequest from pants.engine.environment import Environment, EnvironmentRequest from pants.engine.rules import Get, MultiGet, collect_rules, rule @@ -28,5 +28,14 @@ async def docker_image_run_request( return RunRequest(args=run.argv, digest=image.digest, extra_env=run.env) +@rule +async def docker_image_run_debug_adapter_request( + field_set: DockerFieldSet, +) -> RunDebugAdapterRequest: + raise NotImplementedError( + "Debugging a Docker image using a debug adapter has not yet been implemented." + ) + + def rules(): return collect_rules() diff --git a/src/python/pants/backend/go/goals/run_binary.py b/src/python/pants/backend/go/goals/run_binary.py index b931f54ddcd..e0552b44b4f 100644 --- a/src/python/pants/backend/go/goals/run_binary.py +++ b/src/python/pants/backend/go/goals/run_binary.py @@ -5,7 +5,7 @@ from pants.backend.go.goals.package_binary import GoBinaryFieldSet from pants.core.goals.package import BuiltPackage, PackageFieldSet -from pants.core.goals.run import RunFieldSet, RunRequest +from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest from pants.engine.internals.selectors import Get from pants.engine.rules import collect_rules, rule from pants.engine.unions import UnionRule @@ -19,5 +19,14 @@ async def create_go_binary_run_request(field_set: GoBinaryFieldSet) -> RunReques return RunRequest(digest=binary.digest, args=(os.path.join("{chroot}", artifact_relpath),)) +@rule +async def go_binary_run_debug_adapter_request( + field_set: GoBinaryFieldSet, +) -> RunDebugAdapterRequest: + raise NotImplementedError( + "Debugging a Go binary using a debug adapter has not yet been implemented." + ) + + def rules(): return [*collect_rules(), UnionRule(RunFieldSet, GoBinaryFieldSet)] diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index aa9f3c6f85b..ef48d1bca63 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -21,7 +21,7 @@ PythonSourceFiles, PythonSourceFilesRequest, ) -from pants.core.goals.run import RunFieldSet, RunRequest +from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest from pants.engine.fs import Digest, MergeDigests from pants.engine.rules import Get, MultiGet, collect_rules, rule from pants.engine.target import TransitiveTargets, TransitiveTargetsRequest @@ -121,5 +121,14 @@ def in_chroot(relpath: str) -> str: return RunRequest(digest=merged_digest, args=args, extra_env=extra_env) +@rule +async def run_pex_debug_adapter_binary( + field_set: PexBinaryFieldSet, +) -> RunDebugAdapterRequest: + raise NotImplementedError( + "Debugging a Pex Binary using a debug adapter has not yet been implemented." + ) + + def rules(): return [*collect_rules(), UnionRule(RunFieldSet, PexBinaryFieldSet)] diff --git a/src/python/pants/backend/python/packaging/pyoxidizer/rules.py b/src/python/pants/backend/python/packaging/pyoxidizer/rules.py index a4a88efb14b..213c44ea57d 100644 --- a/src/python/pants/backend/python/packaging/pyoxidizer/rules.py +++ b/src/python/pants/backend/python/packaging/pyoxidizer/rules.py @@ -23,7 +23,7 @@ from pants.backend.python.target_types import GenerateSetupField, WheelField from pants.backend.python.util_rules.pex import Pex, PexProcess, PexRequest from pants.core.goals.package import BuiltPackage, BuiltPackageArtifact, PackageFieldSet -from pants.core.goals.run import RunFieldSet, RunRequest +from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest from pants.core.util_rules.system_binaries import BashBinary from pants.engine.fs import ( AddPrefix, @@ -232,6 +232,15 @@ def is_executable_binary(artifact_relpath: str | None) -> bool: return RunRequest(digest=binary.digest, args=(os.path.join("{chroot}", artifact.relpath),)) +@rule +async def run_pyoxidizer_debug_adapter_binary( + field_set: PyOxidizerFieldSet, +) -> RunDebugAdapterRequest: + raise NotImplementedError( + "Debugging a PyOxidizer binary using a debug adapter has not yet been implemented." + ) + + def rules(): return ( *collect_rules(), diff --git a/src/python/pants/backend/shell/shell_command.py b/src/python/pants/backend/shell/shell_command.py index 9fc9ac0d0e3..f74af012777 100644 --- a/src/python/pants/backend/shell/shell_command.py +++ b/src/python/pants/backend/shell/shell_command.py @@ -23,7 +23,7 @@ ShellCommandToolsField, ) from pants.core.goals.package import BuiltPackage, PackageFieldSet -from pants.core.goals.run import RunFieldSet, RunRequest +from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest from pants.core.target_types import FileSourceField from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest from pants.core.util_rules.system_binaries import ( @@ -248,6 +248,15 @@ async def run_shell_command_request(shell_command: RunShellCommand) -> RunReques ) +@rule +async def run_shell_debug_adapter_binary( + field_set: RunShellCommand, +) -> RunDebugAdapterRequest: + raise NotImplementedError( + "Debugging a shell command using a debug adapter has not yet been implemented." + ) + + def rules(): return [ *collect_rules(), diff --git a/src/python/pants/core/goals/run.py b/src/python/pants/core/goals/run.py index 708554daf35..004fff8f717 100644 --- a/src/python/pants/core/goals/run.py +++ b/src/python/pants/core/goals/run.py @@ -69,6 +69,13 @@ def __init__( self.extra_env = FrozenDict(extra_env or {}) +class RunDebugAdapterRequest(RunRequest): + """Like RunRequest, but launches the process using the relevant Debug Adapter server. + + The process should be launched waiting for the client to connect. + """ + + class RunSubsystem(GoalSubsystem): name = "run" help = softwrap( @@ -107,6 +114,20 @@ def activated(cls, union_membership: UnionMembership) -> bool: """ ), ) + # See also `test.py`'s same option + debug_adapter = BoolOption( + "--debug-adapter", + default=False, + help=softwrap( + """ + Run the interactive process using a Debug Adapter + (https://microsoft.github.io/debug-adapter-protocol/) for the language if supported. + + The interactive process used will be immediately blocked waiting for a client before + continuing. + """ + ), + ) class Run(Goal): @@ -131,7 +152,11 @@ async def run( ), ) field_set = targets_to_valid_field_sets.field_sets[0] - request = await Get(RunRequest, RunFieldSet, field_set) + request = await ( + Get(RunRequest, RunFieldSet, field_set) + if not run_subsystem.debug_adapter + else Get(RunDebugAdapterRequest, RunFieldSet, field_set) + ) wrapped_target = await Get( WrappedTarget, WrappedTargetRequest(field_set.address, description_of_origin="") ) diff --git a/src/python/pants/core/goals/test.py b/src/python/pants/core/goals/test.py index 11b3987db58..3e5bfe4d6e7 100644 --- a/src/python/pants/core/goals/test.py +++ b/src/python/pants/core/goals/test.py @@ -318,6 +318,7 @@ def activated(cls, union_membership: UnionMembership) -> bool: """ ), ) + # See also `run.py`'s same option debug_adapter = BoolOption( "--debug-adapter", default=False, diff --git a/src/python/pants/jvm/run_deploy_jar.py b/src/python/pants/jvm/run_deploy_jar.py index 91e6acbd2a4..2854ae13b1d 100644 --- a/src/python/pants/jvm/run_deploy_jar.py +++ b/src/python/pants/jvm/run_deploy_jar.py @@ -7,7 +7,7 @@ from typing import Iterable from pants.core.goals.package import BuiltPackage -from pants.core.goals.run import RunFieldSet, RunRequest +from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest from pants.engine.fs import EMPTY_DIGEST, Digest, MergeDigests from pants.engine.internals.native_engine import AddPrefix from pants.engine.process import Process, ProcessResult @@ -106,6 +106,15 @@ def prefixed(arg: str, prefixes: Iterable[str]) -> str: ) +@rule +async def run_deploy_jar_debug_adapter_binary( + field_set: DeployJarFieldSet, +) -> RunDebugAdapterRequest: + raise NotImplementedError( + "Debugging a deploy JAR using a debug adapter has not yet been implemented." + ) + + @rule async def ensure_jdk_for_pants_run(jdk: JdkEnvironment) -> __RuntimeJvm: # `tools.jar` is distributed with the JDK, so we can rely on it existing. From 03346c2c6c3be20dd2fb3a69629f1aed0e3b3b7c Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Tue, 14 Jun 2022 20:15:23 -0500 Subject: [PATCH 02/18] test # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/python/pants/core/goals/run_test.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/python/pants/core/goals/run_test.py b/src/python/pants/core/goals/run_test.py index 0bbc032c21d..e7d2fd94bb0 100644 --- a/src/python/pants/core/goals/run_test.py +++ b/src/python/pants/core/goals/run_test.py @@ -8,7 +8,14 @@ import pytest from pants.base.build_root import BuildRoot -from pants.core.goals.run import Run, RunFieldSet, RunRequest, RunSubsystem, run +from pants.core.goals.run import ( + Run, + RunDebugAdapterRequest, + RunFieldSet, + RunRequest, + RunSubsystem, + run, +) from pants.engine.addresses import Address from pants.engine.fs import CreateDigest, Digest, FileContent, Workspace from pants.engine.process import InteractiveProcess, InteractiveProcessResult @@ -43,6 +50,12 @@ def create_mock_run_request(rule_runner: RuleRunner, program_text: bytes) -> Run return RunRequest(digest=digest, args=(os.path.join("{chroot}", "program.py"),)) +def create_mock_run_debug_adapter_request( + rule_runner: RuleRunner, program_text: bytes +) -> RunDebugAdapterRequest: + return cast(RunDebugAdapterRequest, create_mock_run_request(rule_runner, program_text)) + + def single_target_run( rule_runner: RuleRunner, address: Address, @@ -65,7 +78,7 @@ class TestBinaryTarget(Target): res = run_rule_with_mocks( run, rule_args=[ - create_goal_subsystem(RunSubsystem, args=[], cleanup=True), + create_goal_subsystem(RunSubsystem, args=[], cleanup=True, debug_adapter=False), create_subsystem( GlobalOptions, pants_workdir=rule_runner.pants_workdir, process_cleanup=True ), @@ -89,6 +102,11 @@ class TestBinaryTarget(Target): input_type=TestRunFieldSet, mock=lambda _: create_mock_run_request(rule_runner, program_text), ), + MockGet( + output_type=RunDebugAdapterRequest, + input_type=TestRunFieldSet, + mock=lambda _: create_mock_run_debug_adapter_request(rule_runner, program_text), + ), MockEffect( output_type=InteractiveProcessResult, input_type=InteractiveProcess, From 74fc7531ae6c8a2356e5e074d91d209f3a9e4123 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 17 Jun 2022 19:17:44 -0500 Subject: [PATCH 03/18] almost there --- .../backend/python/goals/run_pex_binary.py | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index ef48d1bca63..6e2a7e62d1d 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -1,9 +1,11 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). +from ast import arg import os from pants.backend.python.goals.package_pex_binary import PexBinaryFieldSet +from pants.backend.python.subsystems.debugpy import DebugPy from pants.backend.python.target_types import ( PexBinaryDefaults, ResolvedPexEntryPoint, @@ -11,7 +13,7 @@ ) from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints from pants.backend.python.util_rules.local_dists import LocalDistsPex, LocalDistsPexRequest -from pants.backend.python.util_rules.pex import Pex +from pants.backend.python.util_rules.pex import Pex, PexRequest from pants.backend.python.util_rules.pex_environment import PexEnvironment from pants.backend.python.util_rules.pex_from_targets import ( InterpreterConstraintsRequest, @@ -23,12 +25,16 @@ ) from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest from pants.engine.fs import Digest, MergeDigests -from pants.engine.rules import Get, MultiGet, collect_rules, rule +from pants.engine.rules import Get, MultiGet, collect_rules, rule, rule_helper from pants.engine.target import TransitiveTargets, TransitiveTargetsRequest from pants.engine.unions import UnionRule from pants.util.logging import LogLevel +def _in_chroot(relpath: str) -> str: + return os.path.join("{chroot}", relpath) + + @rule(level=LogLevel.DEBUG) async def create_pex_binary_run_request( field_set: PexBinaryFieldSet, pex_binary_defaults: PexBinaryDefaults, pex_env: PexEnvironment @@ -99,13 +105,12 @@ async def create_pex_binary_run_request( ] merged_digest = await Get(Digest, MergeDigests(input_digests)) - def in_chroot(relpath: str) -> str: - return os.path.join("{chroot}", relpath) - complete_pex_env = pex_env.in_workspace() - args = complete_pex_env.create_argv(in_chroot(pex.name), python=pex.python) + # NB. If this changes, please consider how it affects the `DebugRequest` below + # (which is not easy to write automated tests for) + args = complete_pex_env.create_argv(_in_chroot(pex.name), python=pex.python) - chrooted_source_roots = [in_chroot(sr) for sr in sources.source_roots] + chrooted_source_roots = [_in_chroot(sr) for sr in sources.source_roots] # The order here is important: we want the in-repo sources to take precedence over their # copies in the sandbox (see above for why those copies exist even in non-sandboxed mode). source_roots = [ @@ -114,7 +119,7 @@ def in_chroot(relpath: str) -> str: ] extra_env = { **complete_pex_env.environment_dict(python_configured=pex.python is not None), - "PEX_PATH": in_chroot(local_dists.pex.name), + "PEX_PATH": _in_chroot(local_dists.pex.name), "PEX_EXTRA_SYS_PATH": os.pathsep.join(source_roots), } @@ -124,11 +129,28 @@ def in_chroot(relpath: str) -> str: @rule async def run_pex_debug_adapter_binary( field_set: PexBinaryFieldSet, + debugpy: DebugPy, ) -> RunDebugAdapterRequest: - raise NotImplementedError( - "Debugging a Pex Binary using a debug adapter has not yet been implemented." + regular_run_request, debugpy_pex = await MultiGet( + Get(RunRequest, PexBinaryFieldSet, field_set), + Get(Pex, PexRequest, debugpy.to_pex_request()), ) + merged_digest = await Get( + Digest, MergeDigests([regular_run_request.digest, debugpy_pex.digest]) + ) + args = [ + regular_run_request.args[0], # python executable + _in_chroot(debugpy_pex.name), + "--listen", + f"{debugpy.host}:{debugpy.port}", + "--wait-for-client", + *regular_run_request.args[1:], # built pex args + ] + extra_env = regular_run_request.extra_env + + return RunDebugAdapterRequest(digest=merged_digest, args=args, extra_env=extra_env) + def rules(): return [*collect_rules(), UnionRule(RunFieldSet, PexBinaryFieldSet)] From fa9c4131254b661ea67ef05e2960d03d43dffd67 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sun, 19 Jun 2022 19:55:24 -0500 Subject: [PATCH 04/18] working PoC --- build-support/bin/BUILD | 1 + .../backend/python/goals/run_pex_binary.py | 32 ++++++++++++++++--- src/python/pants/core/goals/run.py | 3 ++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/build-support/bin/BUILD b/build-support/bin/BUILD index 55544172258..ae199341da3 100644 --- a/build-support/bin/BUILD +++ b/build-support/bin/BUILD @@ -31,4 +31,5 @@ pex_binaries( "_release_helper.py", "reversion.py", ], + execution_mode="venv", ) diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index 6e2a7e62d1d..522742ae8f7 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -1,7 +1,7 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). -from ast import arg + import os from pants.backend.python.goals.package_pex_binary import PexBinaryFieldSet @@ -25,7 +25,7 @@ ) from pants.core.goals.run import RunDebugAdapterRequest, RunFieldSet, RunRequest from pants.engine.fs import Digest, MergeDigests -from pants.engine.rules import Get, MultiGet, collect_rules, rule, rule_helper +from pants.engine.rules import Get, MultiGet, collect_rules, rule from pants.engine.target import TransitiveTargets, TransitiveTargetsRequest from pants.engine.unions import UnionRule from pants.util.logging import LogLevel @@ -131,7 +131,17 @@ async def run_pex_debug_adapter_binary( field_set: PexBinaryFieldSet, debugpy: DebugPy, ) -> RunDebugAdapterRequest: - regular_run_request, debugpy_pex = await MultiGet( + if field_set.run_in_sandbox: + # @TODO: Impove error + raise ValueError( + "Cannot use --debug-adapter if `run_in_sandbox` is `True`. Your breakpoints won't be hit." + ) + + entry_point, regular_run_request, debugpy_pex = await MultiGet( + Get( + ResolvedPexEntryPoint, + ResolvePexEntryPointRequest(field_set.entry_point), + ), Get(RunRequest, PexBinaryFieldSet, field_set), Get(Pex, PexRequest, debugpy.to_pex_request()), ) @@ -139,15 +149,27 @@ async def run_pex_debug_adapter_binary( merged_digest = await Get( Digest, MergeDigests([regular_run_request.digest, debugpy_pex.digest]) ) + extra_env = dict(regular_run_request.extra_env) + extra_env["PEX_PATH"] = os.pathsep.join( + [ + extra_env["PEX_PATH"], + # For debugpy to work properly, we need to have just one "environment" for our + # command to run in. Therefore, we cobble one together by exeucting debugpy's PEX, and + # shoehorning in the original PEX through PEX_PATH. + _in_chroot(os.path.basename(regular_run_request.args[1])), + ] + ) args = [ regular_run_request.args[0], # python executable _in_chroot(debugpy_pex.name), "--listen", f"{debugpy.host}:{debugpy.port}", "--wait-for-client", - *regular_run_request.args[1:], # built pex args + # @TODO: Only works for module, function support means we'll need to use -c. + # See https://github.com/microsoft/debugpy/issues/955 + "-m", + entry_point.val.module, ] - extra_env = regular_run_request.extra_env return RunDebugAdapterRequest(digest=merged_digest, args=args, extra_env=extra_env) diff --git a/src/python/pants/core/goals/run.py b/src/python/pants/core/goals/run.py index 004fff8f717..9879ccfebf1 100644 --- a/src/python/pants/core/goals/run.py +++ b/src/python/pants/core/goals/run.py @@ -177,6 +177,9 @@ async def run( args = (arg.format(chroot=tmpdir) for arg in request.args) env = {**complete_env, **{k: v.format(chroot=tmpdir) for k, v in request.extra_env.items()}} + if run_subsystem.debug_adapter: + logger.info("Launching debug adapter, which will wait for a client connection...") + result = await Effect( InteractiveProcessResult, InteractiveProcess( From ec6a519daea42f7b1a79c8037cc3a57c0f756c27 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sun, 19 Jun 2022 20:31:18 -0500 Subject: [PATCH 05/18] doesnt gotta be venv # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- build-support/bin/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/build-support/bin/BUILD b/build-support/bin/BUILD index ae199341da3..55544172258 100644 --- a/build-support/bin/BUILD +++ b/build-support/bin/BUILD @@ -31,5 +31,4 @@ pex_binaries( "_release_helper.py", "reversion.py", ], - execution_mode="venv", ) From 904b76e178f6c472c8440197dac14b7aace7c019 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 24 Jun 2022 10:31:35 -0500 Subject: [PATCH 06/18] Handle anything # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../backend/python/goals/run_pex_binary.py | 30 ++++++++++++++----- .../backend/python/subsystems/debugpy.py | 21 ++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index 522742ae8f7..94303a8ba57 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -2,6 +2,8 @@ # Licensed under the Apache License, Version 2.0 (see LICENSE). +import dataclasses +import logging import os from pants.backend.python.goals.package_pex_binary import PexBinaryFieldSet @@ -29,6 +31,9 @@ from pants.engine.target import TransitiveTargets, TransitiveTargetsRequest from pants.engine.unions import UnionRule from pants.util.logging import LogLevel +from pants.util.strutil import softwrap + +logger = logging.getLogger(__name__) def _in_chroot(relpath: str) -> str: @@ -132,20 +137,32 @@ async def run_pex_debug_adapter_binary( debugpy: DebugPy, ) -> RunDebugAdapterRequest: if field_set.run_in_sandbox: - # @TODO: Impove error - raise ValueError( - "Cannot use --debug-adapter if `run_in_sandbox` is `True`. Your breakpoints won't be hit." + logger.warning( + softwrap( + """ + Using --debug-adapter with `run_in_sandbox` set to `True` will likely cause your + breakpoints to not be hit, as your code will be run under the sandbox's path. + """ + ) ) + debugpy_pex_request = debugpy.to_pex_request() + debugpy_pex_request = dataclasses.replace( + debugpy_pex_request, + additional_args=debugpy_pex_request.additional_args + ("--no-strip-pex-env",), + ) + entry_point, regular_run_request, debugpy_pex = await MultiGet( Get( ResolvedPexEntryPoint, ResolvePexEntryPointRequest(field_set.entry_point), ), Get(RunRequest, PexBinaryFieldSet, field_set), - Get(Pex, PexRequest, debugpy.to_pex_request()), + Get(Pex, PexRequest, debugpy_pex_request), ) + entry_point_or_script = entry_point.val or field_set.script.value + assert entry_point_or_script is not None merged_digest = await Get( Digest, MergeDigests([regular_run_request.digest, debugpy_pex.digest]) ) @@ -165,10 +182,7 @@ async def run_pex_debug_adapter_binary( "--listen", f"{debugpy.host}:{debugpy.port}", "--wait-for-client", - # @TODO: Only works for module, function support means we'll need to use -c. - # See https://github.com/microsoft/debugpy/issues/955 - "-m", - entry_point.val.module, + *debugpy.main_spec_args(entry_point_or_script), ] return RunDebugAdapterRequest(digest=merged_digest, args=args, extra_env=extra_env) diff --git a/src/python/pants/backend/python/subsystems/debugpy.py b/src/python/pants/backend/python/subsystems/debugpy.py index d876bbadcd9..8d2828a4aa5 100644 --- a/src/python/pants/backend/python/subsystems/debugpy.py +++ b/src/python/pants/backend/python/subsystems/debugpy.py @@ -7,7 +7,7 @@ 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 from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel from pants.engine.rules import collect_rules, rule from pants.engine.unions import UnionRule @@ -39,6 +39,25 @@ class DebugPy(PythonToolBase): help="The port to use when launching the debugpy server.", ) + def main_spec_args(self, value: EntryPoint | ConsoleScript) -> tuple[str, ...]: + return ( + "-c", + # NB: Use PEX itself to execute the value + ( + "import os;" + + "from pex.pex import PEX;" + + "pex = PEX(pex=os.environ['PEX']);" + + ( + ( + "from pex.dist_metadata import EntryPoint;" + + f"pex.execute_entry(EntryPoint.parse('run={value.spec}'));" + ) + if isinstance(value, EntryPoint) + else f"pex.execute_script('{value.name}');" + ) + ), + ) + class DebugPyLockfileSentinel(GenerateToolLockfileSentinel): resolve_name = DebugPy.options_scope From 27e63eb96f4b1cd7d121f7759ce245e19dfd031c Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 24 Jun 2022 10:33:40 -0500 Subject: [PATCH 07/18] comment # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/python/pants/backend/python/subsystems/debugpy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/python/pants/backend/python/subsystems/debugpy.py b/src/python/pants/backend/python/subsystems/debugpy.py index 8d2828a4aa5..bdd2533170f 100644 --- a/src/python/pants/backend/python/subsystems/debugpy.py +++ b/src/python/pants/backend/python/subsystems/debugpy.py @@ -42,7 +42,8 @@ class DebugPy(PythonToolBase): def main_spec_args(self, value: EntryPoint | ConsoleScript) -> tuple[str, ...]: return ( "-c", - # NB: Use PEX itself to execute the value + # NB: Use PEX itself to execute the value, since it already has code which can handle + # the possible cases ( "import os;" + "from pex.pex import PEX;" From c094252b94f9f6a27797f80223696eb985567d84 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 24 Jun 2022 11:34:46 -0500 Subject: [PATCH 08/18] new subsystem # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../backend/python/goals/pytest_runner.py | 4 ++- .../backend/python/subsystems/debugpy.py | 10 ------- src/python/pants/core/goals/test.py | 14 ++++++++-- src/python/pants/core/goals/test_test.py | 9 +++++- .../pants/core/subsystems/debug_adapter.py | 28 +++++++++++++++++++ 5 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 src/python/pants/core/subsystems/debug_adapter.py diff --git a/src/python/pants/backend/python/goals/pytest_runner.py b/src/python/pants/backend/python/goals/pytest_runner.py index e869fd2f07d..094124b4c65 100644 --- a/src/python/pants/backend/python/goals/pytest_runner.py +++ b/src/python/pants/backend/python/goals/pytest_runner.py @@ -34,6 +34,7 @@ TestResult, TestSubsystem, ) +from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem from pants.core.util_rules.config_files import ConfigFiles, ConfigFilesRequest from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest from pants.engine.addresses import Address @@ -405,6 +406,7 @@ async def debug_python_test(field_set: PythonTestFieldSet) -> TestDebugRequest: async def debugpy_python_test( field_set: PythonTestFieldSet, debugpy: DebugPy, + debug_adapter: DebugAdapterSubsystem, pytest: PyTest, ) -> TestDebugAdapterRequest: debugpy_pex = await Get(Pex, PexRequest, debugpy.to_pex_request()) @@ -426,7 +428,7 @@ async def debugpy_python_test( main=debugpy.main, prepend_argv=( "--listen", - f"{debugpy.host}:{debugpy.port}", + 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. diff --git a/src/python/pants/backend/python/subsystems/debugpy.py b/src/python/pants/backend/python/subsystems/debugpy.py index d876bbadcd9..a4aa946ef7e 100644 --- a/src/python/pants/backend/python/subsystems/debugpy.py +++ b/src/python/pants/backend/python/subsystems/debugpy.py @@ -11,7 +11,6 @@ from pants.core.goals.generate_lockfiles import GenerateToolLockfileSentinel from pants.engine.rules import collect_rules, rule from pants.engine.unions import UnionRule -from pants.option.option_types import IntOption, StrOption from pants.util.docutil import git_url @@ -30,15 +29,6 @@ class DebugPy(PythonToolBase): default_lockfile_path = "src/python/pants/backend/python/subsystems/debugpy.lock" default_lockfile_url = git_url(default_lockfile_path) - host = StrOption( - "--host", default="127.0.0.1", help="The hostname to use when launching the debugpy server." - ) - port = IntOption( - "--port", - default=5678, # The canonical port - help="The port to use when launching the debugpy server.", - ) - class DebugPyLockfileSentinel(GenerateToolLockfileSentinel): resolve_name = DebugPy.options_scope diff --git a/src/python/pants/core/goals/test.py b/src/python/pants/core/goals/test.py index 11b3987db58..6cd8e55d77e 100644 --- a/src/python/pants/core/goals/test.py +++ b/src/python/pants/core/goals/test.py @@ -12,6 +12,7 @@ from typing import Any, ClassVar, TypeVar, cast from pants.core.goals.package import BuiltPackage, PackageFieldSet +from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem from pants.core.util_rules.distdir import DistDir from pants.engine.addresses import Address, UnparsedAddressInputs from pants.engine.collection import Collection @@ -413,6 +414,7 @@ class Test(Goal): @rule_helper async def _run_debug_tests( test_subsystem: TestSubsystem, + debug_adapter: DebugAdapterSubsystem, ) -> Test: targets_to_valid_field_sets = await Get( TargetRootsToFieldSets, @@ -435,7 +437,14 @@ async def _run_debug_tests( exit_code = 0 for debug_request in debug_requests: if test_subsystem.debug_adapter: - logger.info("Launching debug adapter, which will wait for a client connection...") + logger.info( + softwrap( + f""" + Launching debug adapter at '{debug_adapter.host}:{debug_adapter.port}', + which will wait for a client connection... + """ + ) + ) debug_result = await Effect( InteractiveProcessResult, InteractiveProcess, debug_request.process @@ -449,13 +458,14 @@ async def _run_debug_tests( async def run_tests( console: Console, test_subsystem: TestSubsystem, + debug_adapter: DebugAdapterSubsystem, workspace: Workspace, union_membership: UnionMembership, distdir: DistDir, run_id: RunId, ) -> Test: if test_subsystem.debug or test_subsystem.debug_adapter: - return await _run_debug_tests(test_subsystem) + return await _run_debug_tests(test_subsystem, debug_adapter) shard, num_shards = parse_shard_spec(test_subsystem.shard, "the [test].shard option") targets_to_valid_field_sets = await Get( diff --git a/src/python/pants/core/goals/test_test.py b/src/python/pants/core/goals/test_test.py index e81c647ea4d..b2e2b0d1f24 100644 --- a/src/python/pants/core/goals/test_test.py +++ b/src/python/pants/core/goals/test_test.py @@ -34,6 +34,7 @@ build_runtime_package_dependencies, run_tests, ) +from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem from pants.core.util_rules.distdir import DistDir from pants.engine.addresses import Address from pants.engine.console import Console @@ -55,7 +56,7 @@ TargetRootsToFieldSetsRequest, ) from pants.engine.unions import UnionMembership -from pants.testutil.option_util import create_goal_subsystem +from pants.testutil.option_util import create_goal_subsystem, create_subsystem from pants.testutil.rule_runner import ( MockEffect, MockGet, @@ -167,6 +168,11 @@ def run_test_rule( extra_env_vars=[], shard="", ) + debug_adapter_subsystem = create_subsystem( + DebugAdapterSubsystem, + host="127.0.0.1", + port="5678", + ) workspace = Workspace(rule_runner.scheduler, _enforce_effects=False) union_membership = UnionMembership( { @@ -207,6 +213,7 @@ def mock_coverage_report_generation( rule_args=[ console, test_subsystem, + debug_adapter_subsystem, workspace, union_membership, DistDir(relpath=Path("dist")), diff --git a/src/python/pants/core/subsystems/debug_adapter.py b/src/python/pants/core/subsystems/debug_adapter.py new file mode 100644 index 00000000000..33ab9865372 --- /dev/null +++ b/src/python/pants/core/subsystems/debug_adapter.py @@ -0,0 +1,28 @@ +# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import annotations + +from pants.option.option_types import IntOption, StrOption +from pants.option.subsystem import Subsystem +from pants.util.strutil import softwrap + + +class DebugAdapterSubsystem(Subsystem): + options_scope = "debug-adapter" + help = softwrap( + """ + Options used to configure and launch a Debug Adapter server. + + See https://microsoft.github.io/debug-adapter-protocol/ for more information. + """ + ) + + host = StrOption( + "--host", default="127.0.0.1", help="The hostname to use when launching the server." + ) + port = IntOption( + "--port", + default=5678, + help="The port to use when launching the server.", + ) From ec6184511068eb4d3afa2f7030e0fb4bd4ab6339 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sat, 25 Jun 2022 12:43:51 -0500 Subject: [PATCH 09/18] Generecise # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../backend/python/goals/pytest_runner.py | 21 +---------- .../backend/python/subsystems/debugpy.py | 35 ++++++++++++++++++- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/python/pants/backend/python/goals/pytest_runner.py b/src/python/pants/backend/python/goals/pytest_runner.py index 094124b4c65..bf1b43c3071 100644 --- a/src/python/pants/backend/python/goals/pytest_runner.py +++ b/src/python/pants/backend/python/goals/pytest_runner.py @@ -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() @@ -410,15 +409,6 @@ 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, @@ -426,16 +416,7 @@ async def debugpy_python_test( 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,), ), ) diff --git a/src/python/pants/backend/python/subsystems/debugpy.py b/src/python/pants/backend/python/subsystems/debugpy.py index a4aa946ef7e..fb57e840f60 100644 --- a/src/python/pants/backend/python/subsystems/debugpy.py +++ b/src/python/pants/backend/python/subsystems/debugpy.py @@ -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" @@ -29,6 +32,36 @@ 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 ( + ( + "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 From d94e1a23602728506b5ba05547f176b632426ae4 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sat, 25 Jun 2022 19:23:03 -0500 Subject: [PATCH 10/18] tests too # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../backend/python/goals/pytest_runner_integration_test.py | 5 +++-- src/python/pants/backend/python/subsystems/debugpy.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/python/pants/backend/python/goals/pytest_runner_integration_test.py b/src/python/pants/backend/python/goals/pytest_runner_integration_test.py index 23b3c45e0ba..716942ff225 100644 --- a/src/python/pants/backend/python/goals/pytest_runner_integration_test.py +++ b/src/python/pants/backend/python/goals/pytest_runner_integration_test.py @@ -5,6 +5,7 @@ import os import re +import unittest.mock from textwrap import dedent import pytest @@ -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", ) diff --git a/src/python/pants/backend/python/subsystems/debugpy.py b/src/python/pants/backend/python/subsystems/debugpy.py index fb57e840f60..648e79524f1 100644 --- a/src/python/pants/backend/python/subsystems/debugpy.py +++ b/src/python/pants/backend/python/subsystems/debugpy.py @@ -45,6 +45,7 @@ def _get_main_spec_args(main: MainSpecification) -> tuple[str, ...]: # 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())()" From c03a839d37ec1654bf39c25d634d2b4d85bdac2c Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sun, 26 Jun 2022 19:17:05 -0500 Subject: [PATCH 11/18] docs --- .../Python/python-goals/python-run-goal.md | 30 ++++++++++++------- .../Python/python-goals/python-test-goal.md | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/markdown/Python/python-goals/python-run-goal.md b/docs/markdown/Python/python-goals/python-run-goal.md index becd4716fa7..02619676c58 100644 --- a/docs/markdown/Python/python-goals/python-run-goal.md +++ b/docs/markdown/Python/python-goals/python-run-goal.md @@ -29,11 +29,11 @@ You may only run one target at a time. The program will have access to the same environment used by the parent `./pants` process, so you can set environment variables in the external environment, e.g. `FOO=bar ./pants run project/app.py`. (Pants will auto-set some values like `$PATH`). > 📘 Tip: check the return code -> +> > Pants will propagate the return code from the underlying executable. Run `echo $?` after the Pants run to see the return code. > 🚧 Issues finding files? -> +> > Run `./pants dependencies --transitive path/to/binary.py` to ensure that all the files you need are showing up, including for any [assets](doc:assets) you intend to use. Watching the filesystem @@ -46,29 +46,37 @@ On the other hand, if your app is short lived (like a script) and you'd like to Debugging --------- +> 📘 Tip: using the VS Code (or any DAP-compliant editor) remote debugger in tests +> +> +> 1. Configure your editor's breakpoints and exception settings +> 2. Run your code with `./pants run --debug-adapter` +> 3. Connect your editor to the server (the server host and port are logged, and can be configured +> using the `[debug-adapter]` subsystem). + > 📘 Tip: Using the IntelliJ/PyCharm remote debugger -> +> > First, add the following target in some BUILD file (e.g., the one containing your other 3rd-party dependencies): -> +> > ``` > python_requirement( > name = "pydevd-pycharm", > requirements=["pydevd-pycharm==203.5419.8"], # Or whatever version you choose. > ) > ``` -> +> > You can check this into your repo, for convenience. -> +> > Now, use the remote debugger as usual: -> +> > 1. Start a Python remote debugging session in PyCharm, say on port 5000. > 2. Add the following code at the point where you want execution to pause and connect to the debugger: -> +> > ``` > import pydevd_pycharm > pydevd_pycharm.settrace('localhost', port=5000, stdoutToServer=True, stderrToServer=True) > ``` -> -> Run your executable with `./pants run` as usual. -> +> +> Run your executable with `./pants run` as usual. +> > Note: The first time you do so you may see some extra dependency resolution work, as `pydevd-pycharm` has now been added to the binary's dependencies, via inference. If you have dependency inference turned off in your repo, you will have to manually add a temporary explicit dependency in your binary target on the `pydevd-pycharm` target. diff --git a/docs/markdown/Python/python-goals/python-test-goal.md b/docs/markdown/Python/python-goals/python-test-goal.md index ae6147d82fc..8f279103962 100644 --- a/docs/markdown/Python/python-goals/python-test-goal.md +++ b/docs/markdown/Python/python-goals/python-test-goal.md @@ -229,7 +229,7 @@ If you use multiple files with `test --debug`, they will run sequentially rather > ❯ ./pants test --debug -- -s > ``` -> 📘 Tip: using the VS Code (or any debug adatper-compliant editor) remote debugger in tests +> 📘 Tip: using the VS Code (or any DAP-compliant editor) remote debugger in tests > > > 1. Configure your editor's breakpoints and exception settings From f5cc7759bcec5ad373f9b8b2ee0646cc400274db Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sun, 26 Jun 2022 19:17:37 -0500 Subject: [PATCH 12/18] docs # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- docs/markdown/Python/python-goals/python-run-goal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/markdown/Python/python-goals/python-run-goal.md b/docs/markdown/Python/python-goals/python-run-goal.md index 02619676c58..11e33ef45e4 100644 --- a/docs/markdown/Python/python-goals/python-run-goal.md +++ b/docs/markdown/Python/python-goals/python-run-goal.md @@ -46,7 +46,7 @@ On the other hand, if your app is short lived (like a script) and you'd like to Debugging --------- -> 📘 Tip: using the VS Code (or any DAP-compliant editor) remote debugger in tests +> 📘 Tip: using the VS Code (or any DAP-compliant editor) remote debugger > > > 1. Configure your editor's breakpoints and exception settings From 244dc45e1dcbbdcaf0846e18904f6ac81a7f2bea Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sun, 26 Jun 2022 19:22:14 -0500 Subject: [PATCH 13/18] no replace # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- .../pants/backend/python/goals/run_pex_binary.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index 439efc0379e..3dc012f8f8d 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -1,8 +1,6 @@ # Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). # Licensed under the Apache License, Version 2.0 (see LICENSE). - -import dataclasses import logging import os @@ -148,19 +146,13 @@ async def run_pex_debug_adapter_binary( ) ) - debugpy_pex_request = debugpy.to_pex_request() - debugpy_pex_request = dataclasses.replace( - debugpy_pex_request, - additional_args=debugpy_pex_request.additional_args + ("--no-strip-pex-env",), - ) - entry_point, regular_run_request, debugpy_pex = await MultiGet( Get( ResolvedPexEntryPoint, ResolvePexEntryPointRequest(field_set.entry_point), ), Get(RunRequest, PexBinaryFieldSet, field_set), - Get(Pex, PexRequest, debugpy_pex_request), + Get(Pex, PexRequest, debugpy.to_pex_request()), ) entry_point_or_script = entry_point.val or field_set.script.value From 4a5cabddeb2ed262726bcf422605f9cc74b4e984 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sun, 26 Jun 2022 19:24:31 -0500 Subject: [PATCH 14/18] better log # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/python/pants/core/goals/run.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/python/pants/core/goals/run.py b/src/python/pants/core/goals/run.py index 9879ccfebf1..daf2ac065e3 100644 --- a/src/python/pants/core/goals/run.py +++ b/src/python/pants/core/goals/run.py @@ -7,6 +7,7 @@ from typing import Iterable, Mapping, Optional, Tuple from pants.base.build_root import BuildRoot +from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem from pants.engine.environment import CompleteEnvironment from pants.engine.fs import Digest, Workspace from pants.engine.goal import Goal, GoalSubsystem @@ -137,6 +138,7 @@ class Run(Goal): @goal_rule async def run( run_subsystem: RunSubsystem, + debug_adapter: DebugAdapterSubsystem, global_options: GlobalOptions, workspace: Workspace, build_root: BuildRoot, @@ -178,7 +180,14 @@ async def run( args = (arg.format(chroot=tmpdir) for arg in request.args) env = {**complete_env, **{k: v.format(chroot=tmpdir) for k, v in request.extra_env.items()}} if run_subsystem.debug_adapter: - logger.info("Launching debug adapter, which will wait for a client connection...") + logger.info( + softwrap( + f""" + Launching debug adapter at '{debug_adapter.host}:{debug_adapter.port}', + which will wait for a client connection... + """ + ) + ) result = await Effect( InteractiveProcessResult, From d86cf33e45d8836e3d013ddbe61bd22435708008 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sun, 26 Jun 2022 20:36:06 -0500 Subject: [PATCH 15/18] fix test # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/python/pants/core/goals/run_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/python/pants/core/goals/run_test.py b/src/python/pants/core/goals/run_test.py index e7d2fd94bb0..9e85c83071b 100644 --- a/src/python/pants/core/goals/run_test.py +++ b/src/python/pants/core/goals/run_test.py @@ -16,6 +16,7 @@ RunSubsystem, run, ) +from pants.core.subsystems.debug_adapter import DebugAdapterSubsystem from pants.engine.addresses import Address from pants.engine.fs import CreateDigest, Digest, FileContent, Workspace from pants.engine.process import InteractiveProcess, InteractiveProcessResult @@ -79,6 +80,11 @@ class TestBinaryTarget(Target): run, rule_args=[ create_goal_subsystem(RunSubsystem, args=[], cleanup=True, debug_adapter=False), + create_subsystem( + DebugAdapterSubsystem, + host="127.0.0.1", + port="5678", + ), create_subsystem( GlobalOptions, pants_workdir=rule_runner.pants_workdir, process_cleanup=True ), From 667d17b87c27fb6b3e41108a14e09de22a1e13a2 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Mon, 27 Jun 2022 10:47:56 -0500 Subject: [PATCH 16/18] Eric's # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- docs/markdown/Python/python-goals/python-run-goal.md | 9 ++++----- docs/markdown/Python/python-goals/python-test-goal.md | 9 ++++----- src/python/pants/backend/python/goals/run_pex_binary.py | 7 ++++--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/markdown/Python/python-goals/python-run-goal.md b/docs/markdown/Python/python-goals/python-run-goal.md index 11e33ef45e4..4f107954b5f 100644 --- a/docs/markdown/Python/python-goals/python-run-goal.md +++ b/docs/markdown/Python/python-goals/python-run-goal.md @@ -46,13 +46,12 @@ On the other hand, if your app is short lived (like a script) and you'd like to Debugging --------- -> 📘 Tip: using the VS Code (or any DAP-compliant editor) remote debugger +> 📘 Tip: using the VS Code (or any [DAP](https://microsoft.github.io/debug-adapter-protocol/)-compliant editor) remote debugger > > -> 1. Configure your editor's breakpoints and exception settings -> 2. Run your code with `./pants run --debug-adapter` -> 3. Connect your editor to the server (the server host and port are logged, and can be configured -> using the `[debug-adapter]` subsystem). +> 1. In your editor, set your breakpoints and any other debug settings (like break-on-exception). +> 2. Run your code with `./pants run --debug-adapter`. +> 3. Connect your editor to the server. The server host and port are logged by Pants when executing `run --debug-adaptor`. (They can also be configured using the `[debug-adapter]` subsystem). > 📘 Tip: Using the IntelliJ/PyCharm remote debugger > diff --git a/docs/markdown/Python/python-goals/python-test-goal.md b/docs/markdown/Python/python-goals/python-test-goal.md index 8f279103962..88f9768136d 100644 --- a/docs/markdown/Python/python-goals/python-test-goal.md +++ b/docs/markdown/Python/python-goals/python-test-goal.md @@ -229,13 +229,12 @@ If you use multiple files with `test --debug`, they will run sequentially rather > ❯ ./pants test --debug -- -s > ``` -> 📘 Tip: using the VS Code (or any DAP-compliant editor) remote debugger in tests +> 📘 Tip: using the VS Code (or any [DAP](https://microsoft.github.io/debug-adapter-protocol/)-compliant editor) remote debugger in tests > > -> 1. Configure your editor's breakpoints and exception settings -> 2. Run your test with `./pants test --debug-adapter` -> 3. Connect your editor to the server (the server host and port are logged, and can be configured -> using the `[debug-adapter]` subsystem). +> 1. In your editor, set your breakpoints and any other debug settings (like break-on-exception). +> 2. Run your test with `./pants test --debug-adapter`. +> 3. Connect your editor to the server. The server host and port are logged by Pants when executing `test --debug-adaptor`. (They can also be configured using the `[debug-adapter]` subsystem). > Run your test with `./pants test --debug` as usual. diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index 3dc012f8f8d..917c6547c1e 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -139,9 +139,10 @@ async def run_pex_debug_adapter_binary( if field_set.run_in_sandbox: logger.warning( softwrap( - """ - Using --debug-adapter with `run_in_sandbox` set to `True` will likely cause your - breakpoints to not be hit, as your code will be run under the sandbox's path. + f""" + Using `run --debug-adapter` on the target {field_set.address}, which sets the field + `run_in_sandbox` set to `True` will likely cause your breakpoints to not be hit, as + your code will be run under the sandbox's path. """ ) ) From 3b64bee5f3850279377025e1cda8d0caff277730 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Mon, 27 Jun 2022 12:21:26 -0500 Subject: [PATCH 17/18] grammar # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/python/pants/backend/python/goals/run_pex_binary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index 917c6547c1e..8b3db4f0f44 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -141,8 +141,8 @@ async def run_pex_debug_adapter_binary( softwrap( f""" Using `run --debug-adapter` on the target {field_set.address}, which sets the field - `run_in_sandbox` set to `True` will likely cause your breakpoints to not be hit, as - your code will be run under the sandbox's path. + `run_in_sandbox` set to `True`. This will likely cause your breakpoints to not be hit, + as your code will be run under the sandbox's path. """ ) ) From 0cc49724d358357acb4ef7ca7d1588a1c34220d4 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Mon, 27 Jun 2022 15:42:59 -0500 Subject: [PATCH 18/18] set set # Rust tests and lints will be skipped. Delete if not intended. [ci skip-rust] # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/python/pants/backend/python/goals/run_pex_binary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/pants/backend/python/goals/run_pex_binary.py b/src/python/pants/backend/python/goals/run_pex_binary.py index 8b3db4f0f44..454c72edc4d 100644 --- a/src/python/pants/backend/python/goals/run_pex_binary.py +++ b/src/python/pants/backend/python/goals/run_pex_binary.py @@ -141,7 +141,7 @@ async def run_pex_debug_adapter_binary( softwrap( f""" Using `run --debug-adapter` on the target {field_set.address}, which sets the field - `run_in_sandbox` set to `True`. This will likely cause your breakpoints to not be hit, + `run_in_sandbox` to `True`. This will likely cause your breakpoints to not be hit, as your code will be run under the sandbox's path. """ )