scripts: west: add context-sensitive runner help
Though commands like "west flash -h" now have help for generic runner configuration options, runner-specific context is not printed. In order to print this information, we would ideally want to know the currently available runners from a build directory. Otherwise, we can't print the current cached configuration, and the user will likely be overwhelmed by a giant list of options etc. available for all the runners in the package. However, we can't print that information out without re-doing the build, which is not safe to do when the user just gives '--help'. To provide more complete help without causing side effects in the default help output, add a new -H/--context option, which explicitly re-runs the build (unless --skip-rebuild was given), parses the cache, and prints context-sensitive help. This can be combined with the -r option to restrict help to a particular runner. Examples: - Print context for all available flash runners: west flash -H --build-dir build-frdm_k64f/ - Print context for just one runner: west flash -H --build-dir build-frdm_k64f/ -r jlink - Print context for all available debug runners, if current working directory is a build directory: west debug -H If no context is available because there is no CMake cache file, this command can still be used to obtain generic information about runners. It emits a warning in this case. Signed-off-by: Marti Bolivar <marti@opensourcefoundries.com>
This commit is contained in:
parent
68e5933e97
commit
38e8f06fdb
1 changed files with 217 additions and 6 deletions
|
@ -8,14 +8,19 @@
|
|||
import argparse
|
||||
from os import getcwd, path
|
||||
from subprocess import CalledProcessError
|
||||
from textwrap import dedent
|
||||
import textwrap
|
||||
|
||||
from .. import cmake
|
||||
from .. import log
|
||||
from ..runner import get_runner_cls
|
||||
from .. import util
|
||||
from ..runner import get_runner_cls, ZephyrBinaryRunner
|
||||
from ..runner.core import RunnerConfig
|
||||
from . import CommandContextError
|
||||
|
||||
# Context-sensitive help indentation.
|
||||
# Don't change this, or output from argparse won't match up.
|
||||
INDENT = ' ' * 2
|
||||
|
||||
|
||||
def add_parser_common(parser_adder, command):
|
||||
parser = parser_adder.add_parser(
|
||||
|
@ -23,6 +28,11 @@ def add_parser_common(parser_adder, command):
|
|||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description=command.description)
|
||||
|
||||
parser.add_argument('-H', '--context', action='store_true',
|
||||
help='''Rebuild application and print context-sensitive
|
||||
help; this may be combined with --runner to restrict
|
||||
output to a given runner.''')
|
||||
|
||||
group = parser.add_argument_group(title='General Options')
|
||||
|
||||
group.add_argument('-d', '--build-dir',
|
||||
|
@ -46,7 +56,7 @@ def add_parser_common(parser_adder, command):
|
|||
|
||||
group = parser.add_argument_group(
|
||||
title='Configuration overrides',
|
||||
description=dedent('''\
|
||||
description=textwrap.dedent('''\
|
||||
These values usually come from the Zephyr build system itself
|
||||
as stored in the CMake cache; providing these options
|
||||
overrides those settings.'''))
|
||||
|
@ -79,9 +89,10 @@ def add_parser_common(parser_adder, command):
|
|||
|
||||
|
||||
def desc_common(command_name):
|
||||
return dedent('''\
|
||||
return textwrap.dedent('''\
|
||||
Any options not recognized by this command are passed to the
|
||||
back-end {command} runner.
|
||||
back-end {command} runner (run "west {command} --context"
|
||||
for help on available runner-specific options).
|
||||
|
||||
If you need to pass an option to a runner which has the
|
||||
same name as one recognized by this command, you can
|
||||
|
@ -117,6 +128,10 @@ def _override_config_from_namespace(cfg, namespace):
|
|||
|
||||
|
||||
def do_run_common(command, args, runner_args, cached_runner_var):
|
||||
if args.context:
|
||||
_dump_context(command, args, runner_args, cached_runner_var)
|
||||
return
|
||||
|
||||
command_name = command.name
|
||||
build_dir = args.build_dir or getcwd()
|
||||
|
||||
|
@ -147,7 +162,7 @@ def do_run_common(command, args, runner_args, cached_runner_var):
|
|||
runner = args.runner or cache.get(cached_runner_var)
|
||||
|
||||
if runner is None:
|
||||
raise CommandContextError(dedent("""
|
||||
raise CommandContextError(textwrap.dedent("""
|
||||
No {} runner available for {}. Please either specify one
|
||||
manually, or check your board's documentation for
|
||||
alternative instructions.""".format(command_name, board)))
|
||||
|
@ -196,3 +211,199 @@ def do_run_common(command, args, runner_args, cached_runner_var):
|
|||
'received unknown arguments', unknown)
|
||||
runner = runner_cls.create(cfg, parsed_args)
|
||||
runner.run(command_name)
|
||||
|
||||
|
||||
#
|
||||
# Context-specific help
|
||||
#
|
||||
|
||||
def _dump_context(command, args, runner_args, cached_runner_var):
|
||||
build_dir = args.build_dir or getcwd()
|
||||
|
||||
# If the cache is a file, try to ensure build artifacts are up to
|
||||
# date. If that doesn't work, still try to print information on a
|
||||
# best-effort basis.
|
||||
cache_file = path.abspath(path.join(build_dir, args.cmake_cache))
|
||||
cache = None
|
||||
|
||||
if path.isfile(cache_file):
|
||||
have_cache_file = True
|
||||
else:
|
||||
have_cache_file = False
|
||||
if args.build_dir:
|
||||
msg = textwrap.dedent('''\
|
||||
CMake cache {}: no such file or directory, --build-dir {}
|
||||
is invalid'''.format(cache_file, args.build_dir))
|
||||
log.die('\n'.join(textwrap.wrap(msg, initial_indent='',
|
||||
subsequent_indent=INDENT,
|
||||
break_on_hyphens=False)))
|
||||
else:
|
||||
msg = textwrap.dedent('''\
|
||||
No cache file {} found; is this a build directory?
|
||||
(Use --build-dir to set one if not, otherwise, output will be
|
||||
limited.)'''.format(cache_file))
|
||||
log.wrn('\n'.join(textwrap.wrap(msg, initial_indent='',
|
||||
subsequent_indent=INDENT,
|
||||
break_on_hyphens=False)))
|
||||
|
||||
if have_cache_file and not args.skip_rebuild:
|
||||
try:
|
||||
cmake.run_build(build_dir)
|
||||
except CalledProcessError:
|
||||
msg = 'Failed re-building application; cannot load context. '
|
||||
if args.build_dir:
|
||||
msg += 'Is {} the right --build-dir?'.format(args.build_dir)
|
||||
else:
|
||||
msg += textwrap.dedent('''\
|
||||
Use --build-dir (-d) to specify a build directory; the default
|
||||
is the current directory, {}.'''.format(build_dir))
|
||||
log.die('\n'.join(textwrap.wrap(msg, initial_indent='',
|
||||
subsequent_indent=INDENT,
|
||||
break_on_hyphens=False)))
|
||||
|
||||
if have_cache_file:
|
||||
try:
|
||||
cache = cmake.CMakeCache(cache_file)
|
||||
except Exception:
|
||||
log.die('Cannot load cache {}.'.format(cache_file))
|
||||
|
||||
if cache is None:
|
||||
_dump_no_context_info(command, args)
|
||||
if not args.runner:
|
||||
return
|
||||
|
||||
if args.runner:
|
||||
# Just information on one runner was requested.
|
||||
_dump_one_runner_info(cache, args, build_dir, INDENT)
|
||||
return
|
||||
|
||||
board = cache['CACHED_BOARD']
|
||||
|
||||
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if
|
||||
command.name in cls.capabilities().commands}
|
||||
available = [r for r in cache.get_list('ZEPHYR_RUNNERS') if r in all_cls]
|
||||
available_cls = {r: all_cls[r] for r in available if r in all_cls}
|
||||
|
||||
default_runner = cache.get(cached_runner_var)
|
||||
cfg = cached_runner_config(build_dir, cache)
|
||||
|
||||
log.inf('All Zephyr runners which support {}:'.format(command.name))
|
||||
for line in util.wrap(', '.join(all_cls.keys()), INDENT):
|
||||
log.inf(line)
|
||||
log.inf('(Not all may work with this build, see available runners below.)')
|
||||
|
||||
if cache is None:
|
||||
log.warn('Missing or invalid CMake cache {}; there is no context.',
|
||||
'Use --build-dir to specify the build directory.')
|
||||
return
|
||||
|
||||
log.inf('Build directory:', build_dir)
|
||||
log.inf('Board:', board)
|
||||
log.inf('CMake cache:', cache_file)
|
||||
|
||||
if not available:
|
||||
# Bail with a message if no runners are available.
|
||||
msg = ('No runners available for {}. '
|
||||
'Consult the documentation for instructions on how to run '
|
||||
'binaries on this target.').format(board)
|
||||
for line in util.wrap(msg, ''):
|
||||
log.inf(line)
|
||||
return
|
||||
|
||||
log.inf('Available {} runners:'.format(command.name), ', '.join(available))
|
||||
log.inf('Additional options for available', command.name, 'runners:')
|
||||
for runner in available:
|
||||
_dump_runner_opt_help(runner, all_cls[runner])
|
||||
log.inf('Default {} runner: {}'.format(command.name, default_runner))
|
||||
_dump_runner_config(cfg, '', INDENT)
|
||||
log.inf('Runner-specific information:')
|
||||
for runner in available:
|
||||
log.inf('{}{}:'.format(INDENT, runner))
|
||||
_dump_runner_cached_opts(cache, runner, INDENT * 2, INDENT * 3)
|
||||
_dump_runner_caps(available_cls[runner], INDENT * 2)
|
||||
|
||||
if len(available) > 1:
|
||||
log.inf('(Add -r RUNNER to just print information about one runner.)')
|
||||
|
||||
|
||||
def _dump_no_context_info(command, args):
|
||||
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if
|
||||
command.name in cls.capabilities().commands}
|
||||
log.inf('All Zephyr runners which support {}:'.format(command.name))
|
||||
for line in util.wrap(', '.join(all_cls.keys()), INDENT):
|
||||
log.inf(line)
|
||||
if not args.runner:
|
||||
log.inf('Add -r RUNNER to print more information about any runner.')
|
||||
|
||||
|
||||
def _dump_one_runner_info(cache, args, build_dir, indent):
|
||||
runner = args.runner
|
||||
cls = get_runner_cls(runner)
|
||||
|
||||
if cache is None:
|
||||
_dump_runner_opt_help(runner, cls)
|
||||
_dump_runner_caps(cls, '')
|
||||
return
|
||||
|
||||
available = runner in cache.get_list('ZEPHYR_RUNNERS')
|
||||
cfg = cached_runner_config(build_dir, cache)
|
||||
|
||||
log.inf('Build directory:', build_dir)
|
||||
log.inf('Board:', cache['CACHED_BOARD'])
|
||||
log.inf('CMake cache:', cache.cache_file)
|
||||
log.inf(runner, 'is available:', 'yes' if available else 'no')
|
||||
_dump_runner_opt_help(runner, cls)
|
||||
_dump_runner_config(cfg, '', indent)
|
||||
if available:
|
||||
_dump_runner_cached_opts(cache, runner, '', indent)
|
||||
_dump_runner_caps(cls, '')
|
||||
if not available:
|
||||
log.wrn('Runner', runner, 'is not configured in this build.')
|
||||
|
||||
|
||||
def _dump_runner_caps(cls, base_indent):
|
||||
log.inf('{}Capabilities:'.format(base_indent))
|
||||
log.inf('{}{}'.format(base_indent + INDENT, cls.capabilities()))
|
||||
|
||||
|
||||
def _dump_runner_opt_help(runner, cls):
|
||||
# Construct and print the usage text
|
||||
dummy_parser = argparse.ArgumentParser(prog='', add_help=False)
|
||||
cls.add_parser(dummy_parser)
|
||||
formatter = dummy_parser._get_formatter()
|
||||
for group in dummy_parser._action_groups:
|
||||
# Break the abstraction to filter out the 'flash', 'debug', etc.
|
||||
# TODO: come up with something cleaner (may require changes
|
||||
# in the runner core).
|
||||
actions = group._group_actions
|
||||
if len(actions) == 1 and actions[0].dest == 'command':
|
||||
# This is the lone positional argument. Skip it.
|
||||
continue
|
||||
formatter.start_section('{} option help'.format(runner))
|
||||
formatter.add_text(group.description)
|
||||
formatter.add_arguments(actions)
|
||||
formatter.end_section()
|
||||
log.inf(formatter.format_help())
|
||||
|
||||
|
||||
def _dump_runner_config(cfg, initial_indent, subsequent_indent):
|
||||
log.inf('{}Cached common runner configuration:'.format(initial_indent))
|
||||
for var in cfg.__slots__:
|
||||
log.inf('{}--{}={}'.format(subsequent_indent, var, getattr(cfg, var)))
|
||||
|
||||
|
||||
def _dump_runner_cached_opts(cache, runner, initial_indent, subsequent_indent):
|
||||
runner_args = _get_runner_args(cache, runner)
|
||||
if not runner_args:
|
||||
return
|
||||
|
||||
log.inf('{}Cached runner-specific options:'.format(
|
||||
initial_indent))
|
||||
for arg in runner_args:
|
||||
log.inf('{}{}'.format(subsequent_indent, arg))
|
||||
|
||||
|
||||
def _get_runner_args(cache, runner):
|
||||
runner_ident = cmake.make_c_identifier(runner)
|
||||
args_var = 'ZEPHYR_RUNNER_ARGS_{}'.format(runner_ident)
|
||||
return cache.get_list(args_var)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue