-
-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathrun.py
More file actions
275 lines (224 loc) · 8.15 KB
/
Copy pathrun.py
File metadata and controls
275 lines (224 loc) · 8.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
import asyncio
import functools
import os
import threading
import time
from concurrent.futures import CancelledError
from typing import (
TYPE_CHECKING,
Callable,
List,
Optional,
Sequence,
Union,
cast,
)
import click
from robotcode.core.concurrent import run_as_debugpy_hidden_task
from robotcode.core.types import ServerMode, TcpParams
from robotcode.core.utils.debugpy import (
enable_debugpy,
is_debugpy_installed,
wait_for_debugpy_connected,
)
from robotcode.core.utils.logging import LoggingDescriptor
from robotcode.core.utils.net import find_free_port
from robotcode.plugin import Application
from .dap_types import Event
from .debugger import Debugger
_logger = LoggingDescriptor(name=__package__)
if TYPE_CHECKING:
from .server import DebugAdapterServer
server_lock = threading.RLock()
_server: Optional["DebugAdapterServer"] = None
def get_server() -> Optional["DebugAdapterServer"]:
with server_lock:
return _server
def set_server(value: "DebugAdapterServer") -> None:
with server_lock:
global _server
_server = value
@_logger.call
def wait_for_server(timeout: float = 10) -> "DebugAdapterServer":
start_time = time.monotonic()
while get_server() is None and time.monotonic() - start_time < timeout:
time.sleep(0.005)
result = get_server()
if result is None:
raise RuntimeError("Timeout to get server instance.")
return result
@_logger.call
async def _debug_adapter_server_async(
on_config_done_callback: Optional[Callable[["DebugAdapterServer"], None]],
mode: ServerMode,
addresses: Union[str, Sequence[str], None],
port: int,
pipe_name: Optional[str],
) -> None:
from .server import DebugAdapterServer
async with DebugAdapterServer(
mode=mode,
tcp_params=TcpParams(addresses or "127.0.0.1", port),
pipe_name=pipe_name,
) as server:
if on_config_done_callback is not None:
server.protocol.received_configuration_done_callback = functools.partial(on_config_done_callback, server)
set_server(server)
await server.serve()
def _debug_adapter_server(
on_config_done_callback: Optional[Callable[["DebugAdapterServer"], None]],
mode: ServerMode,
addresses: Union[str, Sequence[str], None],
port: int,
pipe_name: Optional[str],
) -> None:
asyncio.run(_debug_adapter_server_async(on_config_done_callback, mode, addresses, port, pipe_name))
DEFAULT_TIMEOUT = 15.0
config_done_callback: Optional[Callable[["DebugAdapterServer"], None]] = None
debugpy_connected = threading.Event()
@_logger.call
def start_debugpy(
app: Application,
debugpy_port: Optional[int] = None,
addresses: Union[Sequence[str], str, None] = None,
wait_for_debugpy_client: bool = False,
wait_for_client_timeout: float = DEFAULT_TIMEOUT,
) -> None:
port = find_free_port(debugpy_port)
if port != debugpy_port:
_logger.warning(lambda: f"start debugpy session on port {port}")
# remove unwanted env variables
for env_var in ["DEBUGPY_ADAPTER_ENDPOINTS", "VSCODE_DEBUGPY_ADAPTER_ENDPOINTS"]:
if env_var in os.environ:
del os.environ[env_var]
if enable_debugpy(port, addresses):
global config_done_callback
def connect_debugpy(server: "DebugAdapterServer") -> None:
server.protocol.send_event(
Event(
event="debugpyStarted",
body={
"port": port,
"addresses": addresses,
"processId": os.getpid(),
},
)
)
if wait_for_debugpy_client:
app.verbose(f"Wait for debugpy incomming connections listening on {addresses}:{port}")
if not wait_for_debugpy_connected(wait_for_client_timeout):
app.warning("No debugpy client connected")
else:
app.verbose("Debugpy client connected")
debugpy_connected.set()
config_done_callback = connect_debugpy
@_logger.call
def run_debugger(
ctx: click.Context,
app: Application,
args: List[str],
mode: ServerMode,
addresses: Union[str, Sequence[str], None],
port: int,
pipe_name: Optional[str] = None,
debug: bool = False,
stop_on_entry: bool = False,
wait_for_client: bool = False,
wait_for_client_timeout: float = DEFAULT_TIMEOUT,
configuration_done_timeout: float = DEFAULT_TIMEOUT,
debugpy: bool = False,
debugpy_wait_for_client: bool = False,
debugpy_port: Optional[int] = None,
output_messages: bool = False,
output_log: bool = False,
output_timestamps: bool = False,
group_output: bool = False,
) -> int:
if debug and debugpy and not is_debugpy_installed():
app.warning("Debugpy not installed")
if debug and debugpy:
app.verbose("Try to start debugpy session")
start_debugpy(
app,
debugpy_port,
addresses,
debugpy_wait_for_client,
wait_for_client_timeout,
)
app.verbose("Start robotcode debugger thread")
run_as_debugpy_hidden_task(
_debug_adapter_server,
config_done_callback,
mode,
addresses,
port,
pipe_name,
)
server = wait_for_server()
exit_code = 255
try:
if wait_for_client:
app.verbose("Wait for incomming connections")
try:
server.protocol.wait_for_client(wait_for_client_timeout)
except TimeoutError as e:
raise ConnectionError("No incomming connection from a debugger client") from e
server.protocol.wait_for_initialized(wait_for_client_timeout)
if wait_for_client:
app.verbose("Wait for debug configuration.")
try:
server.protocol.wait_for_configuration_done(configuration_done_timeout)
except TimeoutError as e:
raise ConnectionError("Timeout to get configuration from client") from e
if debugpy and debugpy_wait_for_client:
debugpy_connected.wait(wait_for_client_timeout)
args = [
"--listener",
"robotcode.debugger.listeners.ListenerV3",
"--listener",
"robotcode.debugger.listeners.ListenerV2",
*args,
]
Debugger.instance.stop_on_entry = stop_on_entry
Debugger.instance.output_messages = output_messages
Debugger.instance.output_log = output_log
Debugger.instance.group_output = group_output
Debugger.instance.output_timestamps = output_timestamps
Debugger.instance.colored_output = app.colored
Debugger.instance.debug = debug
Debugger.instance.set_main_thread(threading.current_thread())
app.verbose("Start the debugger instance")
Debugger.instance.start()
exit_code = 0
try:
from robotcode.runner.cli.robot import robot
app.verbose("Start robot")
try:
app.verbose(f"Create robot context with args: {args}")
robot_ctx = robot.make_context("robot", args, parent=ctx)
robot.invoke(robot_ctx)
except SystemExit as e:
exit_code = cast(int, e.code)
finally:
if server.protocol.connected:
server.protocol.send_event(
Event(
event="robotExited",
body={
"reportFile": Debugger.instance.robot_report_file,
"logFile": Debugger.instance.robot_log_file,
"outputFile": Debugger.instance.robot_output_file,
"exitCode": exit_code,
},
)
)
except CancelledError:
pass
finally:
if server.protocol.connected:
server.protocol.terminate()
server.protocol.exit(exit_code)
if not server.protocol.wait_for_disconnected():
app.verbose("Timeout to get disconnected from client")
server.loop.stop()
return exit_code