# Copyright (c) 2018 Open Source Foundries Limited. # # SPDX-License-Identifier: Apache-2.0 '''Common code used by commands which execute runners. ''' import argparse from os import getcwd, path from subprocess import CalledProcessError from textwrap import dedent from .. import cmake from .. import log from ..runner import get_runner_cls from ..runner.core import RunnerConfig from . import CommandContextError def add_parser_common(parser_adder, command): parser = parser_adder.add_parser( command.name, formatter_class=argparse.RawDescriptionHelpFormatter, description=command.description) group = parser.add_argument_group(title='General Options') group.add_argument('-d', '--build-dir', help='''Build directory to obtain runner information from; default is the current working directory.''') group.add_argument('-c', '--cmake-cache', default=cmake.DEFAULT_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 the build directory. An absolute path can also be given instead.'''.format(cmake.DEFAULT_CACHE)) group.add_argument('-r', '--runner', help='''If given, overrides any cached {} runner.'''.format(command.name)) 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=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. group.add_argument('--board-dir', help='Zephyr board directory') group.add_argument('--kernel-elf', help='Path to kernel binary in .elf format') group.add_argument('--kernel-hex', help='Path to kernel binary in .hex format') group.add_argument('--kernel-bin', help='Path to kernel binary in .bin format') 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') return parser def desc_common(command_name): return dedent('''\ Any options not recognized by this command are passed to the back-end {command} runner. 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: west {command} --{command}-arg=value -- --runner-arg=value2 '''.format(**{'command': command_name})) def cached_runner_config(build_dir, cache): '''Parse the RunnerConfig from a build directory and CMake Cache.''' board_dir = cache['ZEPHYR_RUNNER_CONFIG_BOARD_DIR'] kernel_elf = cache['ZEPHYR_RUNNER_CONFIG_KERNEL_ELF'] kernel_hex = cache['ZEPHYR_RUNNER_CONFIG_KERNEL_HEX'] kernel_bin = cache['ZEPHYR_RUNNER_CONFIG_KERNEL_BIN'] gdb = cache.get('ZEPHYR_RUNNER_CONFIG_GDB') openocd = cache.get('ZEPHYR_RUNNER_CONFIG_OPENOCD') openocd_search = cache.get('ZEPHYR_RUNNER_CONFIG_OPENOCD_SEARCH') return RunnerConfig(build_dir, board_dir, kernel_elf, kernel_hex, kernel_bin, gdb=gdb, openocd=openocd, openocd_search=openocd_search) 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) def do_run_common(command, args, runner_args, cached_runner_var): command_name = command.name build_dir = args.build_dir or getcwd() if not args.skip_rebuild: 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. cache_file = path.join(build_dir, args.cmake_cache) cache = cmake.CMakeCache(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) if runner is None: raise CommandContextError(dedent(""" No {} runner available for {}. Please either specify one manually, or check your board's documentation for alternative instructions.""".format(command_name, board))) log.inf('Using runner:', runner) if runner not in available: log.wrn('Runner {} is not configured for use with {}, ' 'this may not work'.format(runner, board)) 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)) # Runner creation, phase 2. # # At this point, the common options above are already parsed in # 'args', and unrecognized arguments are in 'runner_args'. # # - Pull the RunnerConfig out of the cache # - Override cached values with applicable command-line options 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: raise CommandContextError('Runner', runner, 'received unknown arguments', unknown) runner = runner_cls.create(cfg, parsed_args) runner.run(command_name)