8
8
import argparse
9
9
from os import getcwd , path
10
10
from subprocess import CalledProcessError
11
- from textwrap import dedent
11
+ import textwrap
12
12
13
13
from .. import cmake
14
14
from .. import log
15
- from ..runner import get_runner_cls
15
+ from .. import util
16
+ from ..runner import get_runner_cls , ZephyrBinaryRunner
16
17
from ..runner .core import RunnerConfig
17
18
from . import CommandContextError
18
19
20
+ # Context-sensitive help indentation.
21
+ # Don't change this, or output from argparse won't match up.
22
+ INDENT = ' ' * 2
23
+
19
24
20
25
def add_parser_common (parser_adder , command ):
21
26
parser = parser_adder .add_parser (
22
27
command .name ,
23
28
formatter_class = argparse .RawDescriptionHelpFormatter ,
24
29
description = command .description )
25
30
31
+ parser .add_argument ('-H' , '--context' , action = 'store_true' ,
32
+ help = '''Rebuild application and print context-sensitive
33
+ help; this may be combined with --runner to restrict
34
+ output to a given runner.''' )
35
+
26
36
group = parser .add_argument_group (title = 'General Options' )
27
37
28
38
group .add_argument ('-d' , '--build-dir' ,
@@ -46,7 +56,7 @@ def add_parser_common(parser_adder, command):
46
56
47
57
group = parser .add_argument_group (
48
58
title = 'Configuration overrides' ,
49
- description = dedent ('''\
59
+ description = textwrap . dedent ('''\
50
60
These values usually come from the Zephyr build system itself
51
61
as stored in the CMake cache; providing these options
52
62
overrides those settings.''' ))
@@ -79,9 +89,10 @@ def add_parser_common(parser_adder, command):
79
89
80
90
81
91
def desc_common (command_name ):
82
- return dedent ('''\
92
+ return textwrap . dedent ('''\
83
93
Any options not recognized by this command are passed to the
84
- back-end {command} runner.
94
+ back-end {command} runner (run "west {command} --context"
95
+ for help on available runner-specific options).
85
96
86
97
If you need to pass an option to a runner which has the
87
98
same name as one recognized by this command, you can
@@ -117,6 +128,10 @@ def _override_config_from_namespace(cfg, namespace):
117
128
118
129
119
130
def do_run_common (command , args , runner_args , cached_runner_var ):
131
+ if args .context :
132
+ _dump_context (command , args , runner_args , cached_runner_var )
133
+ return
134
+
120
135
command_name = command .name
121
136
build_dir = args .build_dir or getcwd ()
122
137
@@ -147,7 +162,7 @@ def do_run_common(command, args, runner_args, cached_runner_var):
147
162
runner = args .runner or cache .get (cached_runner_var )
148
163
149
164
if runner is None :
150
- raise CommandContextError (dedent ("""
165
+ raise CommandContextError (textwrap . dedent ("""
151
166
No {} runner available for {}. Please either specify one
152
167
manually, or check your board's documentation for
153
168
alternative instructions.""" .format (command_name , board )))
@@ -196,3 +211,199 @@ def do_run_common(command, args, runner_args, cached_runner_var):
196
211
'received unknown arguments' , unknown )
197
212
runner = runner_cls .create (cfg , parsed_args )
198
213
runner .run (command_name )
214
+
215
+
216
+ #
217
+ # Context-specific help
218
+ #
219
+
220
+ def _dump_context (command , args , runner_args , cached_runner_var ):
221
+ build_dir = args .build_dir or getcwd ()
222
+
223
+ # If the cache is a file, try to ensure build artifacts are up to
224
+ # date. If that doesn't work, still try to print information on a
225
+ # best-effort basis.
226
+ cache_file = path .abspath (path .join (build_dir , args .cmake_cache ))
227
+ cache = None
228
+
229
+ if path .isfile (cache_file ):
230
+ have_cache_file = True
231
+ else :
232
+ have_cache_file = False
233
+ if args .build_dir :
234
+ msg = textwrap .dedent ('''\
235
+ CMake cache {}: no such file or directory, --build-dir {}
236
+ is invalid''' .format (cache_file , args .build_dir ))
237
+ log .die ('\n ' .join (textwrap .wrap (msg , initial_indent = '' ,
238
+ subsequent_indent = INDENT ,
239
+ break_on_hyphens = False )))
240
+ else :
241
+ msg = textwrap .dedent ('''\
242
+ No cache file {} found; is this a build directory?
243
+ (Use --build-dir to set one if not, otherwise, output will be
244
+ limited.)''' .format (cache_file ))
245
+ log .wrn ('\n ' .join (textwrap .wrap (msg , initial_indent = '' ,
246
+ subsequent_indent = INDENT ,
247
+ break_on_hyphens = False )))
248
+
249
+ if have_cache_file and not args .skip_rebuild :
250
+ try :
251
+ cmake .run_build (build_dir )
252
+ except CalledProcessError :
253
+ msg = 'Failed re-building application; cannot load context. '
254
+ if args .build_dir :
255
+ msg += 'Is {} the right --build-dir?' .format (args .build_dir )
256
+ else :
257
+ msg += textwrap .dedent ('''\
258
+ Use --build-dir (-d) to specify a build directory; the default
259
+ is the current directory, {}.''' .format (build_dir ))
260
+ log .die ('\n ' .join (textwrap .wrap (msg , initial_indent = '' ,
261
+ subsequent_indent = INDENT ,
262
+ break_on_hyphens = False )))
263
+
264
+ if have_cache_file :
265
+ try :
266
+ cache = cmake .CMakeCache (cache_file )
267
+ except Exception :
268
+ log .die ('Cannot load cache {}.' .format (cache_file ))
269
+
270
+ if cache is None :
271
+ _dump_no_context_info (command , args )
272
+ if not args .runner :
273
+ return
274
+
275
+ if args .runner :
276
+ # Just information on one runner was requested.
277
+ _dump_one_runner_info (cache , args , build_dir , INDENT )
278
+ return
279
+
280
+ board = cache ['CACHED_BOARD' ]
281
+
282
+ all_cls = {cls .name (): cls for cls in ZephyrBinaryRunner .get_runners () if
283
+ command .name in cls .capabilities ().commands }
284
+ available = [r for r in cache .get_list ('ZEPHYR_RUNNERS' ) if r in all_cls ]
285
+ available_cls = {r : all_cls [r ] for r in available if r in all_cls }
286
+
287
+ default_runner = cache .get (cached_runner_var )
288
+ cfg = cached_runner_config (build_dir , cache )
289
+
290
+ log .inf ('All Zephyr runners which support {}:' .format (command .name ))
291
+ for line in util .wrap (', ' .join (all_cls .keys ()), INDENT ):
292
+ log .inf (line )
293
+ log .inf ('(Not all may work with this build, see available runners below.)' )
294
+
295
+ if cache is None :
296
+ log .warn ('Missing or invalid CMake cache {}; there is no context.' ,
297
+ 'Use --build-dir to specify the build directory.' )
298
+ return
299
+
300
+ log .inf ('Build directory:' , build_dir )
301
+ log .inf ('Board:' , board )
302
+ log .inf ('CMake cache:' , cache_file )
303
+
304
+ if not available :
305
+ # Bail with a message if no runners are available.
306
+ msg = ('No runners available for {}. '
307
+ 'Consult the documentation for instructions on how to run '
308
+ 'binaries on this target.' ).format (board )
309
+ for line in util .wrap (msg , '' ):
310
+ log .inf (line )
311
+ return
312
+
313
+ log .inf ('Available {} runners:' .format (command .name ), ', ' .join (available ))
314
+ log .inf ('Additional options for available' , command .name , 'runners:' )
315
+ for runner in available :
316
+ _dump_runner_opt_help (runner , all_cls [runner ])
317
+ log .inf ('Default {} runner: {}' .format (command .name , default_runner ))
318
+ _dump_runner_config (cfg , '' , INDENT )
319
+ log .inf ('Runner-specific information:' )
320
+ for runner in available :
321
+ log .inf ('{}{}:' .format (INDENT , runner ))
322
+ _dump_runner_cached_opts (cache , runner , INDENT * 2 , INDENT * 3 )
323
+ _dump_runner_caps (available_cls [runner ], INDENT * 2 )
324
+
325
+ if len (available ) > 1 :
326
+ log .inf ('(Add -r RUNNER to just print information about one runner.)' )
327
+
328
+
329
+ def _dump_no_context_info (command , args ):
330
+ all_cls = {cls .name (): cls for cls in ZephyrBinaryRunner .get_runners () if
331
+ command .name in cls .capabilities ().commands }
332
+ log .inf ('All Zephyr runners which support {}:' .format (command .name ))
333
+ for line in util .wrap (', ' .join (all_cls .keys ()), INDENT ):
334
+ log .inf (line )
335
+ if not args .runner :
336
+ log .inf ('Add -r RUNNER to print more information about any runner.' )
337
+
338
+
339
+ def _dump_one_runner_info (cache , args , build_dir , indent ):
340
+ runner = args .runner
341
+ cls = get_runner_cls (runner )
342
+
343
+ if cache is None :
344
+ _dump_runner_opt_help (runner , cls )
345
+ _dump_runner_caps (cls , '' )
346
+ return
347
+
348
+ available = runner in cache .get_list ('ZEPHYR_RUNNERS' )
349
+ cfg = cached_runner_config (build_dir , cache )
350
+
351
+ log .inf ('Build directory:' , build_dir )
352
+ log .inf ('Board:' , cache ['CACHED_BOARD' ])
353
+ log .inf ('CMake cache:' , cache .cache_file )
354
+ log .inf (runner , 'is available:' , 'yes' if available else 'no' )
355
+ _dump_runner_opt_help (runner , cls )
356
+ _dump_runner_config (cfg , '' , indent )
357
+ if available :
358
+ _dump_runner_cached_opts (cache , runner , '' , indent )
359
+ _dump_runner_caps (cls , '' )
360
+ if not available :
361
+ log .wrn ('Runner' , runner , 'is not configured in this build.' )
362
+
363
+
364
+ def _dump_runner_caps (cls , base_indent ):
365
+ log .inf ('{}Capabilities:' .format (base_indent ))
366
+ log .inf ('{}{}' .format (base_indent + INDENT , cls .capabilities ()))
367
+
368
+
369
+ def _dump_runner_opt_help (runner , cls ):
370
+ # Construct and print the usage text
371
+ dummy_parser = argparse .ArgumentParser (prog = '' , add_help = False )
372
+ cls .add_parser (dummy_parser )
373
+ formatter = dummy_parser ._get_formatter ()
374
+ for group in dummy_parser ._action_groups :
375
+ # Break the abstraction to filter out the 'flash', 'debug', etc.
376
+ # TODO: come up with something cleaner (may require changes
377
+ # in the runner core).
378
+ actions = group ._group_actions
379
+ if len (actions ) == 1 and actions [0 ].dest == 'command' :
380
+ # This is the lone positional argument. Skip it.
381
+ continue
382
+ formatter .start_section ('{} option help' .format (runner ))
383
+ formatter .add_text (group .description )
384
+ formatter .add_arguments (actions )
385
+ formatter .end_section ()
386
+ log .inf (formatter .format_help ())
387
+
388
+
389
+ def _dump_runner_config (cfg , initial_indent , subsequent_indent ):
390
+ log .inf ('{}Cached common runner configuration:' .format (initial_indent ))
391
+ for var in cfg .__slots__ :
392
+ log .inf ('{}--{}={}' .format (subsequent_indent , var , getattr (cfg , var )))
393
+
394
+
395
+ def _dump_runner_cached_opts (cache , runner , initial_indent , subsequent_indent ):
396
+ runner_args = _get_runner_args (cache , runner )
397
+ if not runner_args :
398
+ return
399
+
400
+ log .inf ('{}Cached runner-specific options:' .format (
401
+ initial_indent ))
402
+ for arg in runner_args :
403
+ log .inf ('{}{}' .format (subsequent_indent , arg ))
404
+
405
+
406
+ def _get_runner_args (cache , runner ):
407
+ runner_ident = cmake .make_c_identifier (runner )
408
+ args_var = 'ZEPHYR_RUNNER_ARGS_{}' .format (runner_ident )
409
+ return cache .get_list (args_var )
0 commit comments