diff --git a/scripts/west_commands/build_helpers.py b/scripts/west_commands/build_helpers.py index 2571bc4a991..ca116f1bdcf 100644 --- a/scripts/west_commands/build_helpers.py +++ b/scripts/west_commands/build_helpers.py @@ -24,9 +24,9 @@ DEFAULT_CMAKE_GENERATOR = 'Ninja' '''Name of the default CMake generator.''' FIND_BUILD_DIR_DESCRIPTION = '''\ -If not given, the default build directory ({}/ unless the -build.dir-fmt configuration variable is set) and the current directory are -checked, in that order. If one is a Zephyr build directory, it is used. +If not given, the default build directory is {}/ unless the +build.dir-fmt configuration variable is set. The current directory is +checked after that. If either is a Zephyr build directory, it is used. '''.format(DEFAULT_BUILD_DIR) def _resolve_build_dir(fmt, guess, cwd, **kwargs): diff --git a/scripts/west_commands/debug.py b/scripts/west_commands/debug.py index afb738eabdb..469dc3d0b54 100644 --- a/scripts/west_commands/debug.py +++ b/scripts/west_commands/debug.py @@ -1,9 +1,10 @@ # Copyright (c) 2018 Open Source Foundries Limited. # Copyright 2019 Foundries.io +# Copyright (c) 2020 Nordic Semiconductor ASA # # SPDX-License-Identifier: Apache-2.0 -'''west "debug" and "debugserver" commands.''' +'''west "debug", "debugserver", and "attach" commands.''' from textwrap import dedent @@ -20,17 +21,18 @@ class Debug(WestCommand): # Keep this in sync with the string in west-commands.yml. 'flash and interactively debug a Zephyr application', dedent(''' - Connect to the board, program the flash, and start a - debugging session.\n\n''') + + Connect to the board, flash the program, and start a + debugging session. Use "west attach" instead to attach + a debugger without reflashing.\n\n''') + desc_common('debug'), accepts_unknown_args=True) + self.runner_key = 'debug-runner' # in runners.yaml 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): - do_run_common(self, my_args, runner_args, - 'ZEPHYR_BOARD_DEBUG_RUNNER') + do_run_common(self, my_args, runner_args) class DebugServer(WestCommand): @@ -49,13 +51,13 @@ class DebugServer(WestCommand): Zephyr image.\n\n''') + desc_common('debugserver'), accepts_unknown_args=True) + self.runner_key = 'debug-runner' # in runners.yaml 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): - do_run_common(self, my_args, runner_args, - 'ZEPHYR_BOARD_DEBUG_RUNNER') + do_run_common(self, my_args, runner_args) class Attach(WestCommand): @@ -65,15 +67,13 @@ class Attach(WestCommand): 'attach', # Keep this in sync with the string in west-commands.yml. 'interactively debug a board', - dedent(''' - Like 'debug', this connects to the board and starts a debugging - session, but it doesn't reflash the program on the board.\n\n''') + + "Like \"west debug\", but doesn't reflash the program.\n\n" + desc_common('attach'), accepts_unknown_args=True) + self.runner_key = 'debug-runner' # in runners.yaml 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): - do_run_common(self, my_args, runner_args, - 'ZEPHYR_BOARD_DEBUG_RUNNER') + do_run_common(self, my_args, runner_args) diff --git a/scripts/west_commands/flash.py b/scripts/west_commands/flash.py index 26523fe9f58..d7585817221 100644 --- a/scripts/west_commands/flash.py +++ b/scripts/west_commands/flash.py @@ -1,12 +1,11 @@ # Copyright (c) 2018 Open Source Foundries Limited. # Copyright 2019 Foundries.io +# Copyright (c) 2020 Nordic Semiconductor ASA # # SPDX-License-Identifier: Apache-2.0 '''west "flash" command''' -from textwrap import dedent - from west.commands import WestCommand from run_common import desc_common, add_parser_common, do_run_common @@ -19,14 +18,13 @@ class Flash(WestCommand): 'flash', # Keep this in sync with the string in west-commands.yml. 'flash and run a binary on a board', - dedent(''' - Connects to the board and reprograms it with a new binary\n\n''') + + 'Permanently reprogram a board with a new binary.\n' + desc_common('flash'), accepts_unknown_args=True) + self.runner_key = 'flash-runner' # in runners.yaml 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): - do_run_common(self, my_args, runner_args, - 'ZEPHYR_BOARD_FLASH_RUNNER') + do_run_common(self, my_args, runner_args) diff --git a/scripts/west_commands/run_common.py b/scripts/west_commands/run_common.py index c9f22e44c0a..5c50cee0579 100644 --- a/scripts/west_commands/run_common.py +++ b/scripts/west_commands/run_common.py @@ -13,17 +13,16 @@ import tempfile import textwrap import traceback -from west import cmake from west import log -from west import util from build_helpers import find_build_dir, is_zephyr_build, \ FIND_BUILD_DIR_DESCRIPTION from west.commands import CommandError from west.configuration import config +import yaml from runners import get_runner_cls, ZephyrBinaryRunner, MissingProgram - -from zephyr_ext_common import cached_runner_config +from runners.core import RunnerConfig +import zcmake # Context-sensitive help indentation. # Don't change this, or output from argparse won't match up. @@ -70,12 +69,17 @@ class WestLogHandler(logging.Handler): else: log.dbg(fmt, level=log.VERBOSE_EXTREME) -def add_parser_common(parser_adder, command): - parser = parser_adder.add_parser( - command.name, - formatter_class=argparse.RawDescriptionHelpFormatter, - help=command.help, - description=command.description) +def command_verb(command): + return "flash" if command.name == "flash" else "debug" + +def add_parser_common(command, parser_adder=None, parser=None): + if parser_adder is not None: + 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 # flags @@ -85,88 +89,130 @@ def add_parser_common(parser_adder, command): 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', - 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: {}. + group = parser.add_argument_group(title='General options') - If this is a relative path, it is assumed relative to - the build directory. An absolute path can also be - given instead.'''.format(cmake.DEFAULT_CACHE)) + group.add_argument('-d', '--build-dir', metavar='DIR', + help='zephyr build directory') + group.add_argument('-c', '--cmake-cache', metavar='FILE', + help='override the default CMake cache file') group.add_argument('-r', '--runner', - help='''If given, overrides any cached {} - runner.'''.format(command.name)) + help=f'overrides the default {command.name} runner') group.add_argument('--skip-rebuild', action='store_true', - help='''If given, do not rebuild the application - before running {} commands.'''.format(command.name)) - - group = parser.add_argument_group( - title='Configuration overrides', - 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.''')) - - # Important: - # - # 1. The destination variables of these options must match - # 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') + help='do not rebuild the application first') + + group = parser.add_argument_group(title='Common runner options') + + group.add_argument('--board-dir', metavar='DIR', + help='zephyr board directory') + # FIXME: we should just have a single --file argument. + group.add_argument('--elf-file', metavar='FILE', help='path to zephyr.elf') + group.add_argument('--hex-file', metavar='FILE', help='path to zephyr.hex') + group.add_argument('--bin-file', metavar='FILE', help='path to zephyr.bin') + # FIXME: these are runner-specific. Remove them from this location. + group.add_argument('--gdb', help='path to GDB, if applicable') + group.add_argument('--openocd', help='path to OpenOCD, if applicable') group.add_argument( - '--openocd-search', - help='Path to add to OpenOCD search path, if applicable') + '--openocd-search', metavar='DIR', + help='path to add to OpenOCD search path, if applicable') return parser - def desc_common(command_name): - return textwrap.dedent('''\ - Any options not recognized by this command are passed to the - back-end {command} runner (run "west {command} --context" - for help on available runner-specific options). + return textwrap.dedent(f'''\ + Only common "west {command_name}" options are listed here. + To get help on available runner-specific options, run: + + 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 - same name as one recognized by this command, you can - end argument parsing with a '--', like so: + if args.context: + dump_context(command, args, unknown_args) + return - west {command} --{command}-arg=value -- --runner-arg=value2 - '''.format(**{'command': command_name})) + command_name = 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): - '''Override a RunnerConfig's contents with command-line values.''' - for var in cfg.__slots__: - if var in namespace: - val = getattr(namespace, var) - if val is not None: - setattr(cfg, var, val) + # Get a concrete ZephyrBinaryRunner subclass to use based on + # runners.yaml and command line arguments. + runner_cls = use_runner_cls(command, board, args, runner_config) + runner_name = runner_cls.name() + # 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. if args.build_dir: return args.build_dir @@ -189,282 +235,161 @@ def _build_dir(args, die_if_none=True): else: return None -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) +def load_cmake_cache(build_dir, args): + cache_file = path.join(build_dir, args.cmake_cache or zcmake.DEFAULT_CACHE) + try: + return zcmake.CMakeCache(cache_file) + except FileNotFoundError: + log.die(f'no CMake cache found (expected one at {cache_file})') -def do_run_common(command, args, runner_args, cached_runner_var): - if args.context: - _dump_context(command, args, runner_args, cached_runner_var) - return +def rebuild(command, build_dir, args): + _banner(f'west {command.name}: rebuilding') + try: + 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 - build_dir = _build_dir(args) +def runners_yaml_path(cache): + 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: - _banner('west {}: rebuilding'.format(command_name)) - try: - 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. +def load_runners_yaml(path, args): + # Load runners.yaml using its location in the CMake cache, + # allowing a command line override for the cache file location. - cache_file = path.join(build_dir, args.cmake_cache or cmake.DEFAULT_CACHE) try: - cache = cmake.CMakeCache(cache_file) + with open(path, 'r') as f: + config = yaml.safe_load(f.read()) except FileNotFoundError: - log.die('no CMake cache found (expected one at {})'.format(cache_file)) - board = cache['CACHED_BOARD'] - available = cache.get_list('ZEPHYR_RUNNERS') - if not available: - log.wrn('No cached runners are available in', cache_file) - runner = args.runner or cache.get(cached_runner_var) + log.die(f'runners.yaml file not found: {path}') + + if not config.get('runners'): + log.wrn(f'no pre-configured runners in {path}; ' + "this probably won't work") + + 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: - log.die('No', command_name, 'runner available for board', board, - '({} is not in the cache).'.format(cached_runner_var), - "Check your board's documentation for instructions.") + log.die(f'no {command.name} runner available for board {board}. ' + "Check the 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: - log.wrn('Runner {} is not configured for use with {}, ' - 'this may not work'.format(runner, board)) + log.wrn(f'runner {runner} is not configured for use with {board}, ' + 'this may not work') runner_cls = get_runner_cls(runner) - if command_name not in runner_cls.capabilities().commands: - log.die('Runner {} does not support command {}'.format( - runner, command_name)) + if command.name not in runner_cls.capabilities().commands: + log.die(f'runner {runner} does not support command {command.name}') - # Runner creation, phase 2. - # - # 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 + return runner_cls - logger = logging.getLogger('runners') - logger.setLevel(LOG_LEVEL) - logger.addHandler(WestLogHandler()) - cfg = cached_runner_config(build_dir, cache) - _override_config_from_namespace(cfg, args) - - # 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 runner_cfg_from_args(args, build_dir): + return RunnerConfig(build_dir, args.board_dir, args.elf_file, + args.hex_file, args.bin_file, + gdb=args.gdb, openocd=args.openocd, + openocd_search=args.openocd_search) +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): - build_dir = _build_dir(args, die_if_none=False) - - # Try to figure out the CMake cache file based on the build - # directory or an explicit argument. - 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) +def dump_context(command, args, unknown_args): + build_dir = get_build_dir(args, die_if_none=False) + if build_dir is None: + log.wrn('no --build-dir given or found; output will be limited') + runner_config = None 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. - 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. + # Re-build unless asked not to, to make sure the output is up to date. if build_dir 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 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 + rebuild(command, build_dir, args) 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), - 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) + try: + cls = get_runner_cls(args.runner) + except ValueError: + log.die(f'invalid runner name {args.runner}; choices: ' + + ', '.join(cls.name() for cls in + ZephyrBinaryRunner.get_runners())) + else: + cls = None + 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): - 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), - colorize=True) - 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_context_no_config(command, cls): + if not cls: + 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), 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): - runner = args.runner - cls = get_runner_cls(runner) - - if cache is None: - _dump_runner_opt_help(runner, cls) - _dump_runner_caps(cls, '') + if runner_config is None: return - available = runner in cache.get_list('ZEPHYR_RUNNERS') - cfg = cached_runner_config(build_dir, cache) - - log.inf('Build directory:', colorize=True) - log.inf(INDENT + build_dir) - 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.') + if cls.name() in runner_config['runners']: + dump_runner_args(cls.name(), runner_config, indent) + else: + log.wrn(f'support for runner {cls.name()} is not configured ' + f'in this build directory') +def dump_runner_caps(cls, indent=''): + # Print RunnerCaps for the given runner class. -def _dump_runner_caps(cls, base_indent): - log.inf('{}Capabilities:'.format(base_indent), colorize=True) - log.inf('{}{}'.format(base_indent + INDENT, cls.capabilities())) + log.inf(f'{indent}{cls.name()} capabilities:', colorize=True) + log.inf(f'{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) cls.add_parser(dummy_parser) formatter = dummy_parser._get_formatter() @@ -481,31 +406,53 @@ def _dump_runner_opt_help(runner, cls): formatter.add_arguments(actions) formatter.end_section() # Get the runner help, with the "REMOVE ME" string gone - runner_help = '\n'.join(formatter.format_help().splitlines()[1:]) - - log.inf('{} options:'.format(runner), colorize=True) - log.inf(runner_help) + runner_help = f'\n{indent}'.join(formatter.format_help().splitlines()[1:]) + + log.inf(f'{indent}{cls.name()} options:', colorize=True) + 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('{}Cached common runner configuration:'.format(initial_indent), + log.inf(f'zephyr runners which support "west {command.name}":', colorize=True) - 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 + dump_wrapped_lines(', '.join(all_cls.keys()), INDENT) + log.inf() + dump_wrapped_lines('Note: not all may work with this board and build ' + 'directory. Available runners are listed below.', + INDENT) - log.inf('{}Cached runner-specific options:'.format(initial_indent), + log.inf(f'available runners in runners.yaml:', colorize=True) - for arg in runner_args: - log.inf('{}{}'.format(subsequent_indent, arg)) - + dump_wrapped_lines(', '.join(available), INDENT) + 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): - runner_ident = cmake.make_c_identifier(runner) - args_var = 'ZEPHYR_RUNNER_ARGS_{}'.format(runner_ident) - return cache.get_list(args_var) + if len(available) > 1: + log.inf() + log.inf('Note: use -r RUNNER to limit information to one runner.') + +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)