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
+ from devlib .platform import Platform
27
+ if TYPE_CHECKING :
28
+ from _typeshed import StrPath , BytesPath
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,63 @@ 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 ):
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
+ # TODO - look into which params can be made NotRequired and Optional
197
+ # TODO - use pydantic for dynamic type checking
198
+ class SshConnectionSettings (TypedDict ):
199
+ username : str
200
+ password : str
201
+ keyfile : Optional [Union [LiteralString , StrPath , BytesPath ]]
202
+ host : str
203
+ port : int
204
+ timeout : float
205
+ platform : 'Platform'
206
+ sudo_cmd : str
207
+ strict_host_check : bool
208
+ use_scp : bool
209
+ poll_transfers : bool
210
+ start_transfer_poll_delay : int
211
+ total_transfer_timeout : int
212
+ transfer_poll_period : int
213
+
214
+
215
+ class QEMUTargetRunnerTargetFactory (Protocol ):
216
+ """
217
+ Protocol for Lambda function for creating :class:`Target` based object.
218
+ """
219
+ def __call__ (self , * , connect : bool , conn_cls , connection_settings : SshConnectionSettings ) -> Target :
220
+ ...
167
221
168
222
169
223
class QEMUTargetRunner (SubprocessTargetRunner ):
@@ -177,7 +231,7 @@ class QEMUTargetRunner(SubprocessTargetRunner):
177
231
178
232
* ``arch``: Architecture type. Defaults to ``aarch64``.
179
233
180
- * ``cpu_types ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
234
+ * ``cpu_type ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
181
235
default. This parameter is valid for Arm architectures only.
182
236
183
237
* ``initrd_image``: This points to the location of initrd image (e.g.,
@@ -197,36 +251,37 @@ class QEMUTargetRunner(SubprocessTargetRunner):
197
251
* ``enable_kvm``: Specifies if KVM will be used as accelerator in QEMU or not.
198
252
Enabled by default if host architecture matches with target's for improving
199
253
QEMU performance.
200
- :type qemu_settings: Dict
201
254
202
255
:param connection_settings: the dictionary to store connection settings
203
256
of ``Target.connection_settings``, defaults to None.
204
- :type connection_settings: Dict or None
205
257
206
258
:param make_target: Lambda function for creating :class:`Target` based object.
207
- :type make_target: func or None
208
259
209
260
:Variable positional arguments: Forwarded to :class:`TargetRunner`.
210
261
211
262
:raises FileNotFoundError: if QEMU executable, kernel or initrd image cannot be found.
212
263
"""
213
264
214
265
def __init__ (self ,
215
- qemu_settings ,
216
- connection_settings = None ,
217
- make_target = LinuxTarget ,
218
- ** args ) :
266
+ qemu_settings : QEMUTargetUserSettings ,
267
+ connection_settings : Optional [ SshUserConnectionSettings ] = None ,
268
+ make_target : QEMUTargetRunnerTargetFactory = cast ( QEMUTargetRunnerTargetFactory , LinuxTarget ) ,
269
+ ** kwargs ) -> None :
219
270
220
- self . connection_settings = {
271
+ default_connection_settings = {
221
272
'host' : '127.0.0.1' ,
222
273
'port' : 8022 ,
223
274
'username' : 'root' ,
224
275
'password' : 'root' ,
225
276
'strict_host_check' : False ,
226
277
}
227
- self .connection_settings = {** self .connection_settings , ** (connection_settings or {})}
278
+ # TODO - use pydantic for dynamic type checking. that can avoid casting and ensure runtime type compatibility
279
+ self .connection_settings : SshConnectionSettings = cast (SshConnectionSettings , {
280
+ ** default_connection_settings ,
281
+ ** (connection_settings or {})
282
+ })
228
283
229
- qemu_args = {
284
+ qemu_default_args = {
230
285
'arch' : 'aarch64' ,
231
286
'cpu_type' : 'cortex-a72' ,
232
287
'mem_size' : 512 ,
@@ -235,7 +290,8 @@ def __init__(self,
235
290
'cmdline' : 'console=ttyAMA0' ,
236
291
'enable_kvm' : True ,
237
292
}
238
- qemu_args = {** qemu_args , ** qemu_settings }
293
+ # TODO - same as above, use pydantic.
294
+ qemu_args : QEMUTargetRunnerSettings = cast (QEMUTargetRunnerSettings , {** qemu_default_args , ** qemu_settings })
239
295
240
296
qemu_executable = f'qemu-system-{ qemu_args ["arch" ]} '
241
297
qemu_path = which (qemu_executable )
@@ -281,4 +337,4 @@ def __init__(self,
281
337
282
338
super ().__init__ (runner_cmd = qemu_cmd ,
283
339
target = target ,
284
- ** args )
340
+ ** kwargs )
0 commit comments