Browse Source

scripts: west_commands: fix argument handling with runners.yaml

Refactor the code to support the new runners.yaml file created by the
build system.

Compared to fishing around in the CMake cache, this makes it trivial
to put all the command line arguments to a runner-based command on
equal footing, regardless of if they're defined in the runners package
proper or defined in run_common.py.

This allows board.cmake files to do things like this:

   board_set_runner_args(foo
     --bin-file=${PROJECT_BINARY_DIR}/my-signed.bin)

While at it, make some other cleanups:

- Stop using the obsolete and deprecated west.cmake module while we're
  here in favor of the zcmake.py module which was added to Zephyr a long
  time ago. Yikes. I had forgotten this was still here.

- Stop using west.util's wrap function in favor of raw use of
  textwrap. The west function splits on hyphens, which is breaking
  runner names like "em-starterkit".

- Clean up the --context output a bit

Fixes: #22563
Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
pull/21050/head
Martí Bolívar 5 years ago committed by Johan Hedberg
parent
commit
eb95bed552
  1. 6
      scripts/west_commands/build_helpers.py
  2. 30
      scripts/west_commands/debug.py
  3. 12
      scripts/west_commands/flash.py
  4. 631
      scripts/west_commands/run_common.py

6
scripts/west_commands/build_helpers.py

@ -24,9 +24,9 @@ DEFAULT_CMAKE_GENERATOR = 'Ninja'
'''Name of the default CMake generator.''' '''Name of the default CMake generator.'''
FIND_BUILD_DIR_DESCRIPTION = '''\ FIND_BUILD_DIR_DESCRIPTION = '''\
If not given, the default build directory ({}/ unless the If not given, the default build directory is {}/ unless the
build.dir-fmt configuration variable is set) and the current directory are build.dir-fmt configuration variable is set. The current directory is
checked, in that order. If one is a Zephyr build directory, it is used. checked after that. If either is a Zephyr build directory, it is used.
'''.format(DEFAULT_BUILD_DIR) '''.format(DEFAULT_BUILD_DIR)
def _resolve_build_dir(fmt, guess, cwd, **kwargs): def _resolve_build_dir(fmt, guess, cwd, **kwargs):

30
scripts/west_commands/debug.py

@ -1,9 +1,10 @@
# Copyright (c) 2018 Open Source Foundries Limited. # Copyright (c) 2018 Open Source Foundries Limited.
# Copyright 2019 Foundries.io # Copyright 2019 Foundries.io
# Copyright (c) 2020 Nordic Semiconductor ASA
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
'''west "debug" and "debugserver" commands.''' '''west "debug", "debugserver", and "attach" commands.'''
from textwrap import dedent from textwrap import dedent
@ -20,17 +21,18 @@ class Debug(WestCommand):
# Keep this in sync with the string in west-commands.yml. # Keep this in sync with the string in west-commands.yml.
'flash and interactively debug a Zephyr application', 'flash and interactively debug a Zephyr application',
dedent(''' dedent('''
Connect to the board, program the flash, and start a Connect to the board, flash the program, and start a
debugging session.\n\n''') + debugging session. Use "west attach" instead to attach
a debugger without reflashing.\n\n''') +
desc_common('debug'), desc_common('debug'),
accepts_unknown_args=True) accepts_unknown_args=True)
self.runner_key = 'debug-runner' # in runners.yaml
def do_add_parser(self, parser_adder): def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self) return add_parser_common(self, parser_adder)
def do_run(self, my_args, runner_args): def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args, do_run_common(self, my_args, runner_args)
'ZEPHYR_BOARD_DEBUG_RUNNER')
class DebugServer(WestCommand): class DebugServer(WestCommand):
@ -49,13 +51,13 @@ class DebugServer(WestCommand):
Zephyr image.\n\n''') + Zephyr image.\n\n''') +
desc_common('debugserver'), desc_common('debugserver'),
accepts_unknown_args=True) accepts_unknown_args=True)
self.runner_key = 'debug-runner' # in runners.yaml
def do_add_parser(self, parser_adder): def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self) return add_parser_common(self, parser_adder)
def do_run(self, my_args, runner_args): def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args, do_run_common(self, my_args, runner_args)
'ZEPHYR_BOARD_DEBUG_RUNNER')
class Attach(WestCommand): class Attach(WestCommand):
@ -65,15 +67,13 @@ class Attach(WestCommand):
'attach', 'attach',
# Keep this in sync with the string in west-commands.yml. # Keep this in sync with the string in west-commands.yml.
'interactively debug a board', 'interactively debug a board',
dedent(''' "Like \"west debug\", but doesn't reflash the program.\n\n" +
Like 'debug', this connects to the board and starts a debugging
session, but it doesn't reflash the program on the board.\n\n''') +
desc_common('attach'), desc_common('attach'),
accepts_unknown_args=True) accepts_unknown_args=True)
self.runner_key = 'debug-runner' # in runners.yaml
def do_add_parser(self, parser_adder): def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self) return add_parser_common(self, parser_adder)
def do_run(self, my_args, runner_args): def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args, do_run_common(self, my_args, runner_args)
'ZEPHYR_BOARD_DEBUG_RUNNER')

12
scripts/west_commands/flash.py

@ -1,12 +1,11 @@
# Copyright (c) 2018 Open Source Foundries Limited. # Copyright (c) 2018 Open Source Foundries Limited.
# Copyright 2019 Foundries.io # Copyright 2019 Foundries.io
# Copyright (c) 2020 Nordic Semiconductor ASA
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
'''west "flash" command''' '''west "flash" command'''
from textwrap import dedent
from west.commands import WestCommand from west.commands import WestCommand
from run_common import desc_common, add_parser_common, do_run_common from run_common import desc_common, add_parser_common, do_run_common
@ -19,14 +18,13 @@ class Flash(WestCommand):
'flash', 'flash',
# Keep this in sync with the string in west-commands.yml. # Keep this in sync with the string in west-commands.yml.
'flash and run a binary on a board', 'flash and run a binary on a board',
dedent(''' 'Permanently reprogram a board with a new binary.\n' +
Connects to the board and reprograms it with a new binary\n\n''') +
desc_common('flash'), desc_common('flash'),
accepts_unknown_args=True) accepts_unknown_args=True)
self.runner_key = 'flash-runner' # in runners.yaml
def do_add_parser(self, parser_adder): def do_add_parser(self, parser_adder):
return add_parser_common(parser_adder, self) return add_parser_common(self, parser_adder)
def do_run(self, my_args, runner_args): def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args, do_run_common(self, my_args, runner_args)
'ZEPHYR_BOARD_FLASH_RUNNER')

631
scripts/west_commands/run_common.py

@ -13,17 +13,16 @@ import tempfile
import textwrap import textwrap
import traceback import traceback
from west import cmake
from west import log from west import log
from west import util
from build_helpers import find_build_dir, is_zephyr_build, \ from build_helpers import find_build_dir, is_zephyr_build, \
FIND_BUILD_DIR_DESCRIPTION FIND_BUILD_DIR_DESCRIPTION
from west.commands import CommandError from west.commands import CommandError
from west.configuration import config from west.configuration import config
import yaml
from runners import get_runner_cls, ZephyrBinaryRunner, MissingProgram from runners import get_runner_cls, ZephyrBinaryRunner, MissingProgram
from runners.core import RunnerConfig
from zephyr_ext_common import cached_runner_config import zcmake
# Context-sensitive help indentation. # Context-sensitive help indentation.
# Don't change this, or output from argparse won't match up. # Don't change this, or output from argparse won't match up.
@ -70,12 +69,17 @@ class WestLogHandler(logging.Handler):
else: else:
log.dbg(fmt, level=log.VERBOSE_EXTREME) log.dbg(fmt, level=log.VERBOSE_EXTREME)
def add_parser_common(parser_adder, command): def command_verb(command):
parser = parser_adder.add_parser( return "flash" if command.name == "flash" else "debug"
command.name,
formatter_class=argparse.RawDescriptionHelpFormatter, def add_parser_common(command, parser_adder=None, parser=None):
help=command.help, if parser_adder is not None:
description=command.description) parser = parser_adder.add_parser(
command.name,
formatter_class=argparse.RawDescriptionHelpFormatter,
help=command.help,
description=command.description,
epilog=FIND_BUILD_DIR_DESCRIPTION)
# Remember to update scripts/west-completion.bash if you add or remove # Remember to update scripts/west-completion.bash if you add or remove
# flags # flags
@ -85,88 +89,130 @@ def add_parser_common(parser_adder, command):
help; this may be combined with --runner to restrict help; this may be combined with --runner to restrict
output to a given runner.''') output to a given runner.''')
group = parser.add_argument_group(title='General Options') group = parser.add_argument_group(title='General options')
group.add_argument('-d', '--build-dir',
help='Build directory to obtain runner information ' +
'from. ' + FIND_BUILD_DIR_DESCRIPTION)
group.add_argument('-c', '--cmake-cache',
help='''Path to CMake cache file containing runner
configuration (this is generated by the Zephyr
build system when compiling binaries);
default: {}.
If this is a relative path, it is assumed relative to group.add_argument('-d', '--build-dir', metavar='DIR',
the build directory. An absolute path can also be help='zephyr build directory')
given instead.'''.format(cmake.DEFAULT_CACHE)) group.add_argument('-c', '--cmake-cache', metavar='FILE',
help='override the default CMake cache file')
group.add_argument('-r', '--runner', group.add_argument('-r', '--runner',
help='''If given, overrides any cached {} help=f'overrides the default {command.name} runner')
runner.'''.format(command.name))
group.add_argument('--skip-rebuild', action='store_true', group.add_argument('--skip-rebuild', action='store_true',
help='''If given, do not rebuild the application help='do not rebuild the application first')
before running {} commands.'''.format(command.name))
group = parser.add_argument_group(title='Common runner options')
group = parser.add_argument_group(
title='Configuration overrides', group.add_argument('--board-dir', metavar='DIR',
description=textwrap.dedent('''\ help='zephyr board directory')
These values usually come from the Zephyr build system itself # FIXME: we should just have a single --file argument.
as stored in the CMake cache; providing these options group.add_argument('--elf-file', metavar='FILE', help='path to zephyr.elf')
overrides those settings.''')) group.add_argument('--hex-file', metavar='FILE', help='path to zephyr.hex')
group.add_argument('--bin-file', metavar='FILE', help='path to zephyr.bin')
# Important: # FIXME: these are runner-specific. Remove them from this location.
# group.add_argument('--gdb', help='path to GDB, if applicable')
# 1. The destination variables of these options must match group.add_argument('--openocd', help='path to OpenOCD, if applicable')
# the RunnerConfig slots.
# 2. The default values for all of these must be None.
#
# This is how we detect if the user provided them or not when
# overriding values from the cached configuration.
command_verb = "flash" if command.name == "flash" else "debug"
group.add_argument('--board-dir',
help='Zephyr board directory')
group.add_argument('--elf-file',
help='Path to elf file to {0}'.format(command_verb))
group.add_argument('--hex-file',
help='Path to hex file to {0}'.format(command_verb))
group.add_argument('--bin-file',
help='Path to binary file to {0}'.format(command_verb))
group.add_argument('--gdb',
help='Path to GDB, if applicable')
group.add_argument('--openocd',
help='Path to OpenOCD, if applicable')
group.add_argument( group.add_argument(
'--openocd-search', '--openocd-search', metavar='DIR',
help='Path to add to OpenOCD search path, if applicable') help='path to add to OpenOCD search path, if applicable')
return parser return parser
def desc_common(command_name): def desc_common(command_name):
return textwrap.dedent('''\ return textwrap.dedent(f'''\
Any options not recognized by this command are passed to the Only common "west {command_name}" options are listed here.
back-end {command} runner (run "west {command} --context" To get help on available runner-specific options, run:
for help on available runner-specific options).
west {command_name} --context -d BUILD_DIR
''')
def do_run_common(command, args, unknown_args):
# This is the main routine for all the "west flash", "west debug",
# etc. commands.
If you need to pass an option to a runner which has the if args.context:
same name as one recognized by this command, you can dump_context(command, args, unknown_args)
end argument parsing with a '--', like so: return
west {command} --{command}-arg=value -- --runner-arg=value2 command_name = command.name
'''.format(**{'command': command_name})) build_dir = get_build_dir(args)
cache = load_cmake_cache(build_dir, args)
board = cache['CACHED_BOARD']
if not args.skip_rebuild:
rebuild(command, build_dir, args)
# Load runners.yaml.
runners_yaml = runners_yaml_path(cache)
runner_config = load_runners_yaml(runners_yaml, args)
def _override_config_from_namespace(cfg, namespace): # Get a concrete ZephyrBinaryRunner subclass to use based on
'''Override a RunnerConfig's contents with command-line values.''' # runners.yaml and command line arguments.
for var in cfg.__slots__: runner_cls = use_runner_cls(command, board, args, runner_config)
if var in namespace: runner_name = runner_cls.name()
val = getattr(namespace, var)
if val is not None:
setattr(cfg, var, val)
# Set up runner logging to delegate to west.log commands.
logger = logging.getLogger('runners')
logger.setLevel(LOG_LEVEL)
logger.addHandler(WestLogHandler())
def _build_dir(args, die_if_none=True): # If the user passed -- to force the parent argument parser to stop
# parsing, it will show up here, and needs to be filtered out.
runner_args = [arg for arg in unknown_args if arg != '--']
# Arguments are provided in this order to allow the specific to
# override the general:
#
# - common runners.yaml arguments
# - runner-specific runners.yaml arguments
# - command line arguments
final_argv = (runner_config['args']['common'] +
runner_config['args'][runner_name] +
runner_args)
# At this point, 'args' contains parsed arguments which are both:
#
# 1. provided on the command line
# 2. handled by add_parser_common()
#
# This doesn't include runner specific arguments on the command line or
# anything from runners.yaml.
#
# We therefore have to re-parse now that we know everything,
# including the final runner.
parser = argparse.ArgumentParser(prog=runner_name)
add_parser_common(command, parser=parser)
runner_cls.add_parser(parser)
final_args, unknown = parser.parse_known_args(args=final_argv)
if unknown:
log.die(f'runner {runner_name} received unknown arguments: {unknown}')
# Create the RunnerConfig from the values assigned to common
# arguments. This is a hacky way to go about this; probably
# ZephyrBinaryRunner should define what it needs to make this
# happen by itself. That would be a larger refactoring of the
# runners package than there's time for right now, though.
#
# Use that RunnerConfig to create the ZephyrBinaryRunner instance
# and call its run().
runner = runner_cls.create(runner_cfg_from_args(final_args,
build_dir),
final_args)
try:
runner.run(command_name)
except ValueError as ve:
log.err(str(ve), fatal=True)
dump_traceback()
raise CommandError(1)
except MissingProgram as e:
log.die('required program', e.filename,
'not found; install it or add its location to PATH')
except RuntimeError as re:
if not args.verbose:
log.die(re)
else:
log.err('verbose mode enabled, dumping stack:', fatal=True)
raise
def get_build_dir(args, die_if_none=True):
# Get the build directory for the given argument list and environment. # Get the build directory for the given argument list and environment.
if args.build_dir: if args.build_dir:
return args.build_dir return args.build_dir
@ -189,282 +235,161 @@ def _build_dir(args, die_if_none=True):
else: else:
return None return None
def dump_traceback(): def load_cmake_cache(build_dir, args):
# Save the current exception to a file and return its path. cache_file = path.join(build_dir, args.cmake_cache or zcmake.DEFAULT_CACHE)
fd, name = tempfile.mkstemp(prefix='west-exc-', suffix='.txt') try:
close(fd) # traceback has no use for the fd return zcmake.CMakeCache(cache_file)
with open(name, 'w') as f: except FileNotFoundError:
traceback.print_exc(file=f) log.die(f'no CMake cache found (expected one at {cache_file})')
log.inf("An exception trace has been saved in", name)
def do_run_common(command, args, runner_args, cached_runner_var): def rebuild(command, build_dir, args):
if args.context: _banner(f'west {command.name}: rebuilding')
_dump_context(command, args, runner_args, cached_runner_var) try:
return zcmake.run_build(build_dir)
except CalledProcessError:
if args.build_dir:
log.die(f're-build in {args.build_dir} failed')
else:
log.die(f're-build in {build_dir} failed (no --build-dir given)')
command_name = command.name def runners_yaml_path(cache):
build_dir = _build_dir(args) try:
return cache['ZEPHYR_RUNNERS_YAML']
except KeyError:
board = cache.get('CACHED_BOARD')
log.die(f'either a pristine build is needed, or board {board} '
"doesn't support west flash/debug "
'(no ZEPHYR_RUNNERS_YAML in CMake cache)')
if not args.skip_rebuild: def load_runners_yaml(path, args):
_banner('west {}: rebuilding'.format(command_name)) # Load runners.yaml using its location in the CMake cache,
try: # allowing a command line override for the cache file location.
cmake.run_build(build_dir)
except CalledProcessError:
if args.build_dir:
log.die('cannot run {}, build in {} failed'.format(
command_name, args.build_dir))
else:
log.die('cannot run {}; no --build-dir given and build in '
'current directory {} failed'.format(command_name,
build_dir))
# Runner creation, phase 1.
#
# Get the default runner name from the cache, allowing a command
# line override. Get the ZephyrBinaryRunner class by name, and
# make sure it supports the command.
cache_file = path.join(build_dir, args.cmake_cache or cmake.DEFAULT_CACHE)
try: try:
cache = cmake.CMakeCache(cache_file) with open(path, 'r') as f:
config = yaml.safe_load(f.read())
except FileNotFoundError: except FileNotFoundError:
log.die('no CMake cache found (expected one at {})'.format(cache_file)) log.die(f'runners.yaml file not found: {path}')
board = cache['CACHED_BOARD']
available = cache.get_list('ZEPHYR_RUNNERS') if not config.get('runners'):
if not available: log.wrn(f'no pre-configured runners in {path}; '
log.wrn('No cached runners are available in', cache_file) "this probably won't work")
runner = args.runner or cache.get(cached_runner_var)
return config
def use_runner_cls(command, board, args, runner_config):
# Get the ZephyrBinaryRunner class from its name, and make sure it
# supports the command. Print a message about the choice, and
# return the class.
runner = args.runner or runner_config.get(command.runner_key)
if runner is None: if runner is None:
log.die('No', command_name, 'runner available for board', board, log.die(f'no {command.name} runner available for board {board}. '
'({} is not in the cache).'.format(cached_runner_var), "Check the board's documentation for instructions.")
"Check your board's documentation for instructions.")
_banner(f'west {command.name}: using runner {runner}')
_banner('west {}: using runner {}'.format(command_name, runner)) available = runner_config.get('runners', [])
if runner not in available: if runner not in available:
log.wrn('Runner {} is not configured for use with {}, ' log.wrn(f'runner {runner} is not configured for use with {board}, '
'this may not work'.format(runner, board)) 'this may not work')
runner_cls = get_runner_cls(runner) runner_cls = get_runner_cls(runner)
if command_name not in runner_cls.capabilities().commands: if command.name not in runner_cls.capabilities().commands:
log.die('Runner {} does not support command {}'.format( log.die(f'runner {runner} does not support command {command.name}')
runner, command_name))
# Runner creation, phase 2. return runner_cls
#
# At this point, the common options above are already parsed in
# 'args', and unrecognized arguments are in 'runner_args'.
#
# - Set up runner logging to delegate to west.
# - Pull the RunnerConfig out of the cache
# - Override cached values with applicable command-line options
logger = logging.getLogger('runners') def runner_cfg_from_args(args, build_dir):
logger.setLevel(LOG_LEVEL) return RunnerConfig(build_dir, args.board_dir, args.elf_file,
logger.addHandler(WestLogHandler()) args.hex_file, args.bin_file,
cfg = cached_runner_config(build_dir, cache) gdb=args.gdb, openocd=args.openocd,
_override_config_from_namespace(cfg, args) openocd_search=args.openocd_search)
# Runner creation, phase 3.
#
# - Pull out cached runner arguments, and append command-line
# values (which should override the cache)
# - Construct a runner-specific argument parser to handle cached
# values plus overrides given in runner_args
# - Parse arguments and create runner instance from final
# RunnerConfig and parsed arguments.
cached_runner_args = cache.get_list(
'ZEPHYR_RUNNER_ARGS_{}'.format(cmake.make_c_identifier(runner)))
assert isinstance(runner_args, list), runner_args
# If the user passed -- to force the parent argument parser to stop
# parsing, it will show up here, and needs to be filtered out.
runner_args = [arg for arg in runner_args if arg != '--']
final_runner_args = cached_runner_args + runner_args
parser = argparse.ArgumentParser(prog=runner)
runner_cls.add_parser(parser)
parsed_args, unknown = parser.parse_known_args(args=final_runner_args)
if unknown:
log.die('Runner', runner, 'received unknown arguments:', unknown)
runner = runner_cls.create(cfg, parsed_args)
try:
runner.run(command_name)
except ValueError as ve:
log.err(str(ve), fatal=True)
dump_traceback()
raise CommandError(1)
except MissingProgram as e:
log.die('required program', e.filename,
'not found; install it or add its location to PATH')
except RuntimeError as re:
if not args.verbose:
log.die(re)
else:
log.err('verbose mode enabled, dumping stack:', fatal=True)
raise
def dump_traceback():
# Save the current exception to a file and return its path.
fd, name = tempfile.mkstemp(prefix='west-exc-', suffix='.txt')
close(fd) # traceback has no use for the fd
with open(name, 'w') as f:
traceback.print_exc(file=f)
log.inf("An exception trace has been saved in", name)
# #
# Context-specific help # west {command} --context
# #
def _dump_context(command, args, runner_args, cached_runner_var): def dump_context(command, args, unknown_args):
build_dir = _build_dir(args, die_if_none=False) build_dir = get_build_dir(args, die_if_none=False)
if build_dir is None:
# Try to figure out the CMake cache file based on the build log.wrn('no --build-dir given or found; output will be limited')
# directory or an explicit argument. runner_config = None
if build_dir is not None:
cache_file = path.abspath(
path.join(build_dir, args.cmake_cache or cmake.DEFAULT_CACHE))
elif args.cmake_cache:
cache_file = path.abspath(args.cmake_cache)
else: else:
cache_file = None cache = load_cmake_cache(build_dir, args)
board = cache['CACHED_BOARD']
runners_yaml = runners_yaml_path(cache)
runner_config = load_runners_yaml(runners_yaml, args)
# Load the cache itself, if possible. # Re-build unless asked not to, to make sure the output is up to date.
if cache_file is None:
log.wrn('No build directory (--build-dir) or CMake cache '
'(--cmake-cache) given or found; output will be limited')
cache = None
else:
try:
cache = cmake.CMakeCache(cache_file)
except Exception:
log.die('Cannot load cache {}.'.format(cache_file))
# If we have a build directory, try to ensure build artifacts are
# up to date. If that doesn't work, still try to print information
# on a best-effort basis.
if build_dir and not args.skip_rebuild: if build_dir and not args.skip_rebuild:
try: rebuild(command, build_dir, args)
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 one
used was {}.'''.format(build_dir))
log.die('\n'.join(textwrap.wrap(msg, initial_indent='',
subsequent_indent=INDENT,
break_on_hyphens=False)))
if cache is None:
_dump_no_context_info(command, args)
if not args.runner:
return
if args.runner: if args.runner:
# Just information on one runner was requested. try:
_dump_one_runner_info(cache, args, build_dir, INDENT) cls = get_runner_cls(args.runner)
return except ValueError:
log.die(f'invalid runner name {args.runner}; choices: ' +
board = cache['CACHED_BOARD'] ', '.join(cls.name() for cls in
ZephyrBinaryRunner.get_runners()))
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if else:
command.name in cls.capabilities().commands} cls = None
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),
colorize=True)
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.)',
colorize=True)
if cache is None:
log.wrn('Missing or invalid CMake cache; there is no context.',
'Use --build-dir to specify the build directory.')
return
log.inf('Build directory:', colorize=True)
log.inf(INDENT + build_dir)
log.inf('Board:', colorize=True)
log.inf(INDENT + board)
log.inf('CMake cache:', colorize=True)
log.inf(INDENT + 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, colorize=True)
return
log.inf('Available {} runners:'.format(command.name), colorize=True)
log.inf(INDENT + ', '.join(available))
log.inf('Additional options for available', command.name, 'runners:',
colorize=True)
for runner in available:
_dump_runner_opt_help(runner, all_cls[runner])
log.inf('Default {} runner:'.format(command.name), colorize=True)
log.inf(INDENT + default_runner)
_dump_runner_config(cfg, '', INDENT)
log.inf('Runner-specific information:', colorize=True)
for runner in available:
log.inf('{}{}:'.format(INDENT, runner), colorize=True)
_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.)',
colorize=True)
if runner_config is None:
dump_context_no_config(command, cls)
else:
log.inf(f'build configuration:', colorize=True)
log.inf(f'{INDENT}build directory: {build_dir}')
log.inf(f'{INDENT}board: {board}')
log.inf(f'{INDENT}runners.yaml: {runners_yaml}')
if cls:
dump_runner_context(command, cls, runner_config)
else:
dump_all_runner_context(command, runner_config, board, build_dir)
def _dump_no_context_info(command, args): def dump_context_no_config(command, cls):
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if if not cls:
command.name in cls.capabilities().commands} all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners()
log.inf('All Zephyr runners which support {}:'.format(command.name), if command.name in cls.capabilities().commands}
colorize=True) 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.',
colorize=True) colorize=True)
dump_wrapped_lines(', '.join(all_cls.keys()), INDENT)
log.inf()
log.inf('Note: use -r RUNNER to limit information to one runner.')
else:
# This does the right thing with runner_config=None.
dump_runner_context(command, cls, None)
def dump_runner_context(command, cls, runner_config, indent=''):
dump_runner_caps(cls, indent)
dump_runner_option_help(cls, indent)
def _dump_one_runner_info(cache, args, build_dir, indent): if runner_config is None:
runner = args.runner
cls = get_runner_cls(runner)
if cache is None:
_dump_runner_opt_help(runner, cls)
_dump_runner_caps(cls, '')
return return
available = runner in cache.get_list('ZEPHYR_RUNNERS') if cls.name() in runner_config['runners']:
cfg = cached_runner_config(build_dir, cache) dump_runner_args(cls.name(), runner_config, indent)
else:
log.inf('Build directory:', colorize=True) log.wrn(f'support for runner {cls.name()} is not configured '
log.inf(INDENT + build_dir) f'in this build directory')
log.inf('Board:', colorize=True)
log.inf(INDENT + cache['CACHED_BOARD'])
log.inf('CMake cache:', colorize=True)
log.inf(INDENT + cache.cache_file)
log.inf(runner, 'is available:', 'yes' if available else 'no',
colorize=True)
_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, indent=''):
# Print RunnerCaps for the given runner class.
def _dump_runner_caps(cls, base_indent): log.inf(f'{indent}{cls.name()} capabilities:', colorize=True)
log.inf('{}Capabilities:'.format(base_indent), colorize=True) log.inf(f'{indent}{INDENT}{cls.capabilities()}')
log.inf('{}{}'.format(base_indent + INDENT, cls.capabilities()))
def dump_runner_option_help(cls, indent=''):
# Print help text for class-specific command line options for the
# given runner class.
def _dump_runner_opt_help(runner, cls):
# Construct and print the usage text
dummy_parser = argparse.ArgumentParser(prog='', add_help=False) dummy_parser = argparse.ArgumentParser(prog='', add_help=False)
cls.add_parser(dummy_parser) cls.add_parser(dummy_parser)
formatter = dummy_parser._get_formatter() formatter = dummy_parser._get_formatter()
@ -481,31 +406,53 @@ def _dump_runner_opt_help(runner, cls):
formatter.add_arguments(actions) formatter.add_arguments(actions)
formatter.end_section() formatter.end_section()
# Get the runner help, with the "REMOVE ME" string gone # Get the runner help, with the "REMOVE ME" string gone
runner_help = '\n'.join(formatter.format_help().splitlines()[1:]) runner_help = f'\n{indent}'.join(formatter.format_help().splitlines()[1:])
log.inf('{} options:'.format(runner), colorize=True) log.inf(f'{indent}{cls.name()} options:', colorize=True)
log.inf(runner_help) log.inf(indent + runner_help)
def dump_runner_args(group, runner_config, indent=''):
msg = f'{indent}{group} arguments from runners.yaml:'
args = runner_config['args'][group]
if args:
log.inf(msg, colorize=True)
for arg in args:
log.inf(f'{indent}{INDENT}{arg}')
else:
log.inf(f'{msg} (none)', colorize=True)
def dump_all_runner_context(command, runner_config, board, build_dir):
all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if
command.name in cls.capabilities().commands}
available = runner_config['runners']
available_cls = {r: all_cls[r] for r in available if r in all_cls}
default_runner = runner_config[command.runner_key]
def _dump_runner_config(cfg, initial_indent, subsequent_indent): log.inf(f'zephyr runners which support "west {command.name}":',
log.inf('{}Cached common runner configuration:'.format(initial_indent),
colorize=True) colorize=True)
for var in cfg.__slots__: dump_wrapped_lines(', '.join(all_cls.keys()), INDENT)
log.inf('{}--{}={}'.format(subsequent_indent, var, getattr(cfg, var))) log.inf()
dump_wrapped_lines('Note: not all may work with this board and build '
'directory. Available runners are listed below.',
def _dump_runner_cached_opts(cache, runner, initial_indent, subsequent_indent): INDENT)
runner_args = _get_runner_args(cache, runner)
if not runner_args:
return
log.inf('{}Cached runner-specific options:'.format(initial_indent), log.inf(f'available runners in runners.yaml:',
colorize=True) colorize=True)
for arg in runner_args: dump_wrapped_lines(', '.join(available), INDENT)
log.inf('{}{}'.format(subsequent_indent, arg)) log.inf(f'default runner in runners.yaml:', colorize=True)
log.inf(INDENT + default_runner)
dump_runner_args('common', runner_config)
log.inf('runner-specific context:', colorize=True)
for cls in available_cls.values():
dump_runner_context(command, cls, runner_config, INDENT)
def _get_runner_args(cache, runner): if len(available) > 1:
runner_ident = cmake.make_c_identifier(runner) log.inf()
args_var = 'ZEPHYR_RUNNER_ARGS_{}'.format(runner_ident) log.inf('Note: use -r RUNNER to limit information to one runner.')
return cache.get_list(args_var)
def dump_wrapped_lines(text, indent):
for line in textwrap.wrap(text, initial_indent=indent,
subsequent_indent=indent,
break_on_hyphens=False,
break_long_words=False):
log.inf(line)

Loading…
Cancel
Save