Skip to content

Commit 58b97a8

Browse files
committed
start support for remote debugging
1 parent 1e7f1e5 commit 58b97a8

File tree

7 files changed

+321
-101
lines changed

7 files changed

+321
-101
lines changed

.vscode/launch.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@
103103
"args": [
104104
"-p",
105105
"6612",
106+
"-b",
107+
"localhost",
108+
"-b",
109+
"192.168.178.83",
106110
"-w",
107111
"--",
108112
"-d",
@@ -237,6 +241,22 @@
237241
],
238242
"console": "integratedTerminal",
239243
"justMyCode": false
244+
},
245+
{
246+
"name": "Python: Remote-Attach",
247+
"type": "python",
248+
"request": "attach",
249+
"connect": {
250+
"host": "localhost",
251+
"port": 5678
252+
},
253+
"pathMappings": [
254+
{
255+
"localRoot": "${workspaceFolder}",
256+
"remoteRoot": "."
257+
}
258+
],
259+
"justMyCode": true
240260
}
241261
],
242262
"inputs": [
@@ -247,4 +267,4 @@
247267
"default": "5678",
248268
}
249269
]
250-
}
270+
}

package.json

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,75 @@
618618
"robotframework"
619619
],
620620
"configurationAttributes": {
621+
"attach": {
622+
"properties": {
623+
"connect": {
624+
"label": "Attach by connecting to debugpy over a socket.",
625+
"properties": {
626+
"host": {
627+
"default": "127.0.0.1",
628+
"description": "Hostname or IP address to connect to.",
629+
"type": "string"
630+
},
631+
"port": {
632+
"description": "Port to connect to.",
633+
"type": "number"
634+
}
635+
},
636+
"required": [
637+
"port"
638+
],
639+
"type": "object"
640+
},
641+
"host": {
642+
"default": "127.0.0.1",
643+
"description": "Hostname or IP address to connect to.",
644+
"type": "string"
645+
},
646+
"pathMappings": {
647+
"default": [],
648+
"items": {
649+
"label": "Path mapping",
650+
"properties": {
651+
"localRoot": {
652+
"default": "${workspaceFolder}",
653+
"label": "Local source root.",
654+
"type": "string"
655+
},
656+
"remoteRoot": {
657+
"default": "",
658+
"label": "Remote source root.",
659+
"type": "string"
660+
}
661+
},
662+
"required": [
663+
"localRoot",
664+
"remoteRoot"
665+
],
666+
"type": "object"
667+
},
668+
"label": "Path mappings.",
669+
"type": "array"
670+
},
671+
"port": {
672+
"description": "Port to connect to.",
673+
"type": "number"
674+
},
675+
"attachPython": {
676+
"type": "boolean",
677+
"description": "Attach also the python debugger if a robot test starts.",
678+
"default": false
679+
},
680+
"pythonConfiguration": {
681+
"type": [
682+
"object",
683+
"string"
684+
],
685+
"description": "Defines a template for the python launch configuration.",
686+
"default": {}
687+
}
688+
}
689+
},
621690
"launch": {
622691
"properties": {
623692
"target": {
@@ -893,6 +962,38 @@
893962
},
894963
"purpose": "default"
895964
}
965+
},
966+
{
967+
"label": "RobotCode: Default",
968+
"description": "Default configuration.",
969+
"body": {
970+
"name": "RobotCode: Default",
971+
"type": "robotcode",
972+
"request": "launch",
973+
"presentation": {
974+
"hidden": true
975+
},
976+
"purpose": "default"
977+
}
978+
},
979+
{
980+
"label": "RobotCode: Remote-Attach",
981+
"description": "Attach to a running remote debug server.",
982+
"body": {
983+
"name": "RobotCode: Remote-Attach",
984+
"type": "robotcode",
985+
"request": "attach",
986+
"connect": {
987+
"host": "localhost",
988+
"port": 6612
989+
},
990+
"pathMappings": [
991+
{
992+
"localRoot": "^\"\\${workspaceFolder}\"",
993+
"remoteRoot": "."
994+
}
995+
]
996+
}
896997
}
897998
]
898999
}
@@ -950,4 +1051,4 @@
9501051
"webpack": "^5.74.0",
9511052
"webpack-cli": "^4.10.0"
9521053
}
953-
}
1054+
}

robotcode/debugger/__main__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import threading
88
from logging.handlers import RotatingFileHandler
99
from pathlib import Path
10-
from typing import TYPE_CHECKING, Any, List, Optional, cast
10+
from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Union, cast
1111

1212
__file__ = os.path.abspath(__file__)
1313
if __file__.endswith((".pyc", ".pyo")):
@@ -74,7 +74,7 @@ async def _debug_adapter_server_(host: str, port: int) -> None:
7474
await server.serve()
7575

7676

77-
DEFAULT_TIMEOUT = 5.0
77+
DEFAULT_TIMEOUT = 10.0
7878

7979

8080
@_logger.call
@@ -110,6 +110,7 @@ async def start_debugpy_async(
110110
async def run_robot(
111111
port: int,
112112
args: List[str],
113+
bind: Union[Sequence[str], str, None] = None,
113114
no_debug: bool = False,
114115
wait_for_client: bool = False,
115116
wait_for_client_timeout: float = DEFAULT_TIMEOUT,
@@ -245,6 +246,13 @@ def main() -> None:
245246

246247
parser.add_argument("--version", action="store_true", help="shows the version and exits")
247248
parser.add_argument("-p", "--port", default=6612, help="server listen port (tcp)", type=int)
249+
parser.add_argument(
250+
"-b",
251+
"--bind",
252+
action="append",
253+
help="Specify alternate bind address. If not specified '127.0.0.1' is used",
254+
metavar="ADDRESS",
255+
)
248256
parser.add_argument("-w", "--wait-for-client", action="store_true", help="waits for an debug client to connect")
249257
parser.add_argument(
250258
"-t",
@@ -277,7 +285,7 @@ def main() -> None:
277285
)
278286
parser.add_argument("-d", "--debugpy", action="store_true", help="starts a debugpy session")
279287
parser.add_argument(
280-
"-dp", "--debugpy-port", default=6613, help="sets the port for debugpy session", type=int, metavar="PORT"
288+
"-dp", "--debugpy-port", default=5678, help="sets the port for debugpy session", type=int, metavar="PORT"
281289
)
282290
parser.add_argument(
283291
"-dw", "--debugpy-wait-for-client", action="store_true", help="waits for debugpy client to connect"
@@ -366,6 +374,7 @@ def main() -> None:
366374
run_robot(
367375
args.port,
368376
robot_args,
377+
args.bind,
369378
args.no_debug,
370379
args.wait_for_client,
371380
args.wait_for_client_timeout,

robotcode/debugger/dap_types.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,27 @@ class InitializeRequest(Request, _InitializeRequest):
318318
command: str = "initialize"
319319

320320

321+
@dataclass
322+
class AttachRequestArguments(Model):
323+
restart: Optional[Any] = field(default=None, metadata={"alias": "__restart"})
324+
325+
326+
@dataclass
327+
class _AttachRequest(Model):
328+
arguments: AttachRequestArguments
329+
330+
331+
@dataclass
332+
class AttachRequest(Request, _AttachRequest):
333+
arguments: AttachRequestArguments = field()
334+
command: str = "attach"
335+
336+
337+
@dataclass
338+
class AttachResponse(Response):
339+
pass
340+
341+
321342
@dataclass
322343
class ExceptionBreakpointsFilter(Model):
323344
filter: str

robotcode/debugger/debugger.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ def __init__(self) -> None:
242242
self.stop_on_entry = False
243243
self.no_debug = False
244244
self.terminated = False
245+
self.attached = False
245246

246247
@property
247248
def debug(self) -> bool:
@@ -549,6 +550,7 @@ def process_end_state(self, status: str, filter_id: Set[str], description: str,
549550
)
550551
):
551552
self.state = State.Paused
553+
552554
self.send_event(
553555
self,
554556
StoppedEvent(
@@ -565,8 +567,9 @@ def process_end_state(self, status: str, filter_id: Set[str], description: str,
565567

566568
@_logger.call
567569
def wait_for_running(self) -> None:
568-
with self.condition:
569-
self.condition.wait_for(lambda: self.state in [State.Running, State.Stopped])
570+
if self.attached:
571+
with self.condition:
572+
self.condition.wait_for(lambda: self.state in [State.Running, State.Stopped])
570573

571574
def start_output_group(self, name: str, attributes: Dict[str, Any], type: Optional[str] = None) -> None:
572575
if self.group_output:

robotcode/debugger/server.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from ..utils.async_tools import run_coroutine_from_thread
99
from ..utils.logging import LoggingDescriptor
1010
from .dap_types import (
11+
AttachRequestArguments,
12+
Capabilities,
1113
ConfigurationDoneArguments,
1214
ContinueArguments,
1315
ContinueResponseBody,
@@ -16,9 +18,11 @@
1618
EvaluateArguments,
1719
EvaluateResponseBody,
1820
Event,
21+
ExceptionBreakpointsFilter,
1922
ExitedEvent,
2023
ExitedEventBody,
2124
InitializedEvent,
25+
InitializeRequestArguments,
2226
NextArguments,
2327
PauseArguments,
2428
ScopesArguments,
@@ -117,6 +121,56 @@ async def wait_for_disconnected(self, timeout: float = 60) -> bool:
117121

118122
return self._connected
119123

124+
@rpc_method(name="initialize", param_type=InitializeRequestArguments)
125+
async def _initialize(self, arguments: InitializeRequestArguments, *args: Any, **kwargs: Any) -> Capabilities:
126+
self._initialized = True
127+
128+
return Capabilities(
129+
supports_configuration_done_request=True,
130+
supports_conditional_breakpoints=True,
131+
supports_hit_conditional_breakpoints=True,
132+
support_terminate_debuggee=True,
133+
# support_suspend_debuggee=True,
134+
supports_evaluate_for_hovers=True,
135+
supports_terminate_request=True,
136+
supports_log_points=True,
137+
supports_set_expression=True,
138+
supports_set_variable=True,
139+
supports_value_formatting_options=True,
140+
exception_breakpoint_filters=[
141+
ExceptionBreakpointsFilter(
142+
filter="failed_keyword",
143+
label="Failed Keywords",
144+
description="Breaks on failed keywords",
145+
default=False,
146+
),
147+
ExceptionBreakpointsFilter(
148+
filter="uncaught_failed_keyword",
149+
label="Uncaught Failed Keywords",
150+
description="Breaks on uncaught failed keywords",
151+
default=True,
152+
),
153+
ExceptionBreakpointsFilter(
154+
filter="failed_test",
155+
label="Failed Test",
156+
description="Breaks on failed tests",
157+
default=False,
158+
),
159+
ExceptionBreakpointsFilter(
160+
filter="failed_suite",
161+
label="Failed Suite",
162+
description="Breaks on failed suite",
163+
default=False,
164+
),
165+
],
166+
supports_exception_options=True,
167+
supports_exception_filter_options=True,
168+
)
169+
170+
@rpc_method(name="attach", param_type=AttachRequestArguments)
171+
async def _attach(self, arguments: AttachRequestArguments, *args: Any, **kwargs: Any) -> None:
172+
pass
173+
120174
@_logger.call
121175
async def initialized(self) -> None:
122176
await self.send_event_async(InitializedEvent())
@@ -173,6 +227,7 @@ async def _configuration_done(
173227
) -> None:
174228
self._received_configuration_done = True
175229
self._received_configuration_done_event.set()
230+
Debugger.instance().attached = True
176231

177232
@_logger.call
178233
async def wait_for_configuration_done(self, timeout: float = 5) -> bool:

0 commit comments

Comments
 (0)