1
- # Copyright 2024 ARM Limited
1
+ # Copyright 2024-2025 ARM Limited
2
2
#
3
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
4
# you may not use this file except in compliance with the License.
17
17
Target runner and related classes are implemented here.
18
18
"""
19
19
20
- import logging
21
20
import os
22
21
import time
22
+
23
23
from platform import machine
24
+ from typing import Optional , cast , Protocol , TYPE_CHECKING , Union
25
+ from typing_extensions import NotRequired , LiteralString , TypedDict
26
+ if TYPE_CHECKING :
27
+ from _typeshed import StrPath , BytesPath
28
+ from devlib .platform import Platform
29
+ else :
30
+ StrPath = str
31
+ BytesPath = bytes
24
32
25
33
from devlib .exception import (TargetStableError , HostError )
26
- from devlib .target import LinuxTarget
27
- from devlib .utils .misc import get_subprocess , which
34
+ from devlib .target import LinuxTarget , Target
35
+ from devlib .utils .misc import get_subprocess , which , get_logger
28
36
from devlib .utils .ssh import SshConnection
37
+ from devlib .utils .annotation_helpers import SubprocessCommand , SshUserConnectionSettings
29
38
30
39
31
40
class TargetRunner :
@@ -36,16 +45,14 @@ class TargetRunner:
36
45
(e.g., :class:`QEMUTargetRunner`).
37
46
38
47
:param target: Specifies type of target per :class:`Target` based classes.
39
- :type target: Target
40
48
"""
41
49
42
50
def __init__ (self ,
43
- target ) :
51
+ target : Target ) -> None :
44
52
self .target = target
53
+ self .logger = get_logger (self .__class__ .__name__ )
45
54
46
- self .logger = logging .getLogger (self .__class__ .__name__ )
47
-
48
- def __enter__ (self ):
55
+ def __enter__ (self ) -> 'TargetRunner' :
49
56
return self
50
57
51
58
def __exit__ (self , * _ ):
@@ -58,29 +65,25 @@ class SubprocessTargetRunner(TargetRunner):
58
65
59
66
:param runner_cmd: The command to start runner process (e.g.,
60
67
``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``).
61
- :type runner_cmd: list(str)
62
68
63
69
:param target: Specifies type of target per :class:`Target` based classes.
64
- :type target: Target
65
70
66
71
:param connect: Specifies if :class:`TargetRunner` should try to connect
67
72
target after launching it, defaults to True.
68
- :type connect: bool or None
69
73
70
74
:param boot_timeout: Timeout for target's being ready for SSH access in
71
75
seconds, defaults to 60.
72
- :type boot_timeout: int or None
73
76
74
77
:raises HostError: if it cannot execute runner command successfully.
75
78
76
79
:raises TargetStableError: if Target is inaccessible.
77
80
"""
78
81
79
82
def __init__ (self ,
80
- runner_cmd ,
81
- target ,
82
- connect = True ,
83
- boot_timeout = 60 ):
83
+ runner_cmd : SubprocessCommand ,
84
+ target : Target ,
85
+ connect : bool = True ,
86
+ boot_timeout : int = 60 ):
84
87
super ().__init__ (target = target )
85
88
86
89
self .boot_timeout = boot_timeout
@@ -90,7 +93,7 @@ def __init__(self,
90
93
try :
91
94
self .runner_process = get_subprocess (runner_cmd )
92
95
except Exception as ex :
93
- raise HostError (f'Error while running "{ runner_cmd } ": { ex } ' ) from ex
96
+ raise HostError (f'Error while running "{ runner_cmd !r } ": { ex } ' ) from ex
94
97
95
98
if connect :
96
99
self .wait_boot_complete ()
@@ -107,16 +110,16 @@ def __exit__(self, *_):
107
110
108
111
self .terminate ()
109
112
110
- def wait_boot_complete (self ):
113
+ def wait_boot_complete (self ) -> None :
111
114
"""
112
- Wait for target OS to finish boot up and become accessible over SSH in at most
113
- ``SubprocessTargetRunner. boot_timeout` ` seconds.
115
+ Wait for the target OS to finish booting and become accessible within
116
+ :attr:` boot_timeout` seconds.
114
117
115
- :raises TargetStableError: In case of timeout.
118
+ :raises TargetStableError: If the target is inaccessible after the timeout.
116
119
"""
117
120
118
121
start_time = time .time ()
119
- elapsed = 0
122
+ elapsed : float = 0. 0
120
123
while self .boot_timeout >= elapsed :
121
124
try :
122
125
self .target .connect (timeout = self .boot_timeout - elapsed )
@@ -132,9 +135,9 @@ def wait_boot_complete(self):
132
135
self .terminate ()
133
136
raise TargetStableError (f'Target is inaccessible for { self .boot_timeout } seconds!' )
134
137
135
- def terminate (self ):
138
+ def terminate (self ) -> None :
136
139
"""
137
- Terminate ``SubprocessTargetRunner.runner_process`` .
140
+ Terminate the subprocess associated with this runner .
138
141
"""
139
142
140
143
self .logger .debug ('Killing target runner...' )
@@ -147,10 +150,9 @@ class NOPTargetRunner(TargetRunner):
147
150
Class for implementing a target runner which does nothing except providing .target attribute.
148
151
149
152
:param target: Specifies type of target per :class:`Target` based classes.
150
- :type target: Target
151
153
"""
152
154
153
- def __init__ (self , target ) :
155
+ def __init__ (self , target : Target ) -> None :
154
156
super ().__init__ (target = target )
155
157
156
158
def __enter__ (self ):
@@ -159,11 +161,61 @@ def __enter__(self):
159
161
def __exit__ (self , * _ ):
160
162
pass
161
163
162
- def terminate (self ):
164
+ def terminate (self ) -> None :
163
165
"""
164
166
Nothing to terminate for NOP target runners.
165
167
Defined to be compliant with other runners (e.g., ``SubprocessTargetRunner``).
166
168
"""
169
+ pass
170
+
171
+
172
+ class QEMUTargetUserSettings (TypedDict , total = False ):
173
+ kernel_image : str
174
+ arch : NotRequired [str ]
175
+ cpu_type : NotRequired [str ]
176
+ initrd_image : str
177
+ mem_size : NotRequired [int ]
178
+ num_cores : NotRequired [int ]
179
+ num_threads : NotRequired [int ]
180
+ cmdline : NotRequired [str ]
181
+ enable_kvm : NotRequired [bool ]
182
+
183
+
184
+ class QEMUTargetRunnerSettings (TypedDict , total = False ):
185
+ kernel_image : str
186
+ arch : str
187
+ cpu_type : str
188
+ initrd_image : str
189
+ mem_size : int
190
+ num_cores : int
191
+ num_threads : int
192
+ cmdline : str
193
+ enable_kvm : bool
194
+
195
+
196
+ class SshConnectionSettings (TypedDict , total = False ):
197
+ username : str
198
+ password : str
199
+ keyfile : Optional [Union [LiteralString , StrPath , BytesPath ]]
200
+ host : str
201
+ port : int
202
+ timeout : float
203
+ platform : 'Platform'
204
+ sudo_cmd : str
205
+ strict_host_check : bool
206
+ use_scp : bool
207
+ poll_transfers : bool
208
+ start_transfer_poll_delay : int
209
+ total_transfer_timeout : int
210
+ transfer_poll_period : int
211
+
212
+
213
+ class QEMUTargetRunnerTargetFactory (Protocol ):
214
+ """
215
+ Protocol for Lambda function for creating :class:`Target` based object.
216
+ """
217
+ def __call__ (self , * , connect : bool , conn_cls , connection_settings : SshConnectionSettings ) -> Target :
218
+ ...
167
219
168
220
169
221
class QEMUTargetRunner (SubprocessTargetRunner ):
@@ -177,7 +229,7 @@ class QEMUTargetRunner(SubprocessTargetRunner):
177
229
178
230
* ``arch``: Architecture type. Defaults to ``aarch64``.
179
231
180
- * ``cpu_types ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
232
+ * ``cpu_type ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
181
233
default. This parameter is valid for Arm architectures only.
182
234
183
235
* ``initrd_image``: This points to the location of initrd image (e.g.,
@@ -197,36 +249,37 @@ class QEMUTargetRunner(SubprocessTargetRunner):
197
249
* ``enable_kvm``: Specifies if KVM will be used as accelerator in QEMU or not.
198
250
Enabled by default if host architecture matches with target's for improving
199
251
QEMU performance.
200
- :type qemu_settings: Dict
201
252
202
253
:param connection_settings: the dictionary to store connection settings
203
254
of ``Target.connection_settings``, defaults to None.
204
- :type connection_settings: Dict or None
205
255
206
256
:param make_target: Lambda function for creating :class:`Target` based object.
207
- :type make_target: func or None
208
257
209
258
:Variable positional arguments: Forwarded to :class:`TargetRunner`.
210
259
211
260
:raises FileNotFoundError: if QEMU executable, kernel or initrd image cannot be found.
212
261
"""
213
262
214
263
def __init__ (self ,
215
- qemu_settings ,
216
- connection_settings = None ,
217
- make_target = LinuxTarget ,
218
- ** args ):
264
+ qemu_settings : QEMUTargetUserSettings ,
265
+ connection_settings : Optional [ SshUserConnectionSettings ] = None ,
266
+ make_target : QEMUTargetRunnerTargetFactory = cast ( QEMUTargetRunnerTargetFactory , LinuxTarget ) ,
267
+ ** args ) -> None :
219
268
220
- self . connection_settings = {
269
+ default_connection_settings = {
221
270
'host' : '127.0.0.1' ,
222
271
'port' : 8022 ,
223
272
'username' : 'root' ,
224
273
'password' : 'root' ,
225
274
'strict_host_check' : False ,
226
275
}
227
- self .connection_settings = {** self .connection_settings , ** (connection_settings or {})}
228
276
229
- qemu_args = {
277
+ self .connection_settings : SshConnectionSettings = cast (SshConnectionSettings , {
278
+ ** default_connection_settings ,
279
+ ** (connection_settings or {})
280
+ })
281
+
282
+ qemu_default_args = {
230
283
'arch' : 'aarch64' ,
231
284
'cpu_type' : 'cortex-a72' ,
232
285
'mem_size' : 512 ,
@@ -235,7 +288,7 @@ def __init__(self,
235
288
'cmdline' : 'console=ttyAMA0' ,
236
289
'enable_kvm' : True ,
237
290
}
238
- qemu_args = {** qemu_args , ** qemu_settings }
291
+ qemu_args : QEMUTargetRunnerSettings = cast ( QEMUTargetRunnerSettings , {** qemu_default_args , ** qemu_settings })
239
292
240
293
qemu_executable = f'qemu-system-{ qemu_args ["arch" ]} '
241
294
qemu_path = which (qemu_executable )
0 commit comments