88import argparse
99from os import getcwd , path
1010from subprocess import CalledProcessError
11- from textwrap import dedent
11+ import textwrap
1212
1313from .. import cmake
1414from .. import log
15- from ..runner import get_runner_cls
15+ from .. import util
16+ from ..runner import get_runner_cls , ZephyrBinaryRunner
1617from ..runner .core import RunnerConfig
1718from . import CommandContextError
1819
20+ # Context-sensitive help indentation.
21+ # Don't change this, or output from argparse won't match up.
22+ INDENT = ' ' * 2
23+
1924
2025def add_parser_common (parser_adder , command ):
2126 parser = parser_adder .add_parser (
2227 command .name ,
2328 formatter_class = argparse .RawDescriptionHelpFormatter ,
2429 description = command .description )
2530
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+
2636 group = parser .add_argument_group (title = 'General Options' )
2737
2838 group .add_argument ('-d' , '--build-dir' ,
@@ -46,7 +56,7 @@ def add_parser_common(parser_adder, command):
4656
4757 group = parser .add_argument_group (
4858 title = 'Configuration overrides' ,
49- description = dedent ('''\
59+ description = textwrap . dedent ('''\
5060 These values usually come from the Zephyr build system itself
5161 as stored in the CMake cache; providing these options
5262 overrides those settings.''' ))
@@ -79,9 +89,10 @@ def add_parser_common(parser_adder, command):
7989
8090
8191def desc_common (command_name ):
82- return dedent ('''\
92+ return textwrap . dedent ('''\
8393 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).
8596
8697 If you need to pass an option to a runner which has the
8798 same name as one recognized by this command, you can
@@ -117,6 +128,10 @@ def _override_config_from_namespace(cfg, namespace):
117128
118129
119130def 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+
120135 command_name = command .name
121136 build_dir = args .build_dir or getcwd ()
122137
@@ -147,7 +162,7 @@ def do_run_common(command, args, runner_args, cached_runner_var):
147162 runner = args .runner or cache .get (cached_runner_var )
148163
149164 if runner is None :
150- raise CommandContextError (dedent ("""
165+ raise CommandContextError (textwrap . dedent ("""
151166 No {} runner available for {}. Please either specify one
152167 manually, or check your board's documentation for
153168 alternative instructions.""" .format (command_name , board )))
@@ -196,3 +211,199 @@ def do_run_common(command, args, runner_args, cached_runner_var):
196211 'received unknown arguments' , unknown )
197212 runner = runner_cls .create (cfg , parsed_args )
198213 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