scripts: west: add flash, debug, debugserver commands

When run without any arguments, the commands work the same way that
their CMake equivalents do. For example, if using the Ninja CMake
generator, these are equivalent:

    west flash        <----> ninja flash
    west debug        <----> ninja debug
    west debugserver  <----> ninja debugserver

Like CMake's build tool mode, you can also run them from any directory
in the system by passing the path to the build directory using
--build-dir (-d):

    west flash -d build/my-board

The commands will run the CMake-generated build system, so they keep
dependencies up to date and users don't have to manually compile
binaries between running CMake and using this tool.

The commands also support important use cases that CMake can't:

1) Any arguments not handled by 'west flash' et al. are passed to the
   underlying runner. For example, if the runner supports --gdb-port,
   the default can be overridden like so:

   west debugserver --gdb-port=1234

   Command processing by the 'west' command can also be halted using
   '--'; anything after that point (even if it's an option recognized
   by the west command) will be passed to the runner. Example:

   west debug -- --this-option-goes-to-the-debug-runner=foo

2) Any runner supported by the board can be selected at runtime using
   the -r (--runner) option. For example, if the board's flash runner
   defaults to nrfjprog but jlink is supported as well, it can be
   selected with:

   west flash -r jlink

3) The runner configuration can be persisted elsewhere, edited
   offline, and selected at runtime, using --cmake-cache (-c):

   west flash -c /home/me/some/other/CMakeCache.txt

Signed-off-by: Marti Bolivar <marti@opensourcefoundries.com>
This commit is contained in:
Marti Bolivar 2018-05-09 18:16:30 -04:00 committed by Anas Nashif
commit b611e5b1ea
4 changed files with 203 additions and 1 deletions

View file

@ -0,0 +1,126 @@
# 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, chdir
from subprocess import CalledProcessError
from textwrap import dedent
from .. import cmake
from .. import log
from ..runner import get_runner_cls
from . import CommandContextError
def add_parser_common(parser_adder, command):
parser = parser_adder.add_parser(
command.name,
formatter_class=argparse.RawDescriptionHelpFormatter,
description=command.description)
parser.add_argument('-d', '--build-dir',
help='''Build directory to obtain runner information
from; default is the current working directory.''')
parser.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))
parser.add_argument('-r', '--runner',
help='''If given, overrides any cached {}
runner.'''.format(command.name))
parser.add_argument('--skip-rebuild', action='store_true',
help='''If given, do not rebuild the application
before running {} commands.'''.format(command.name))
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 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))
# Temporary hack: we need to ensure we're running from the build
# directory for now. Otherwise, the BuildConfiguration objects
# that get created by the runners look for .config in the wrong
# places.
chdir(build_dir)
# TODO: build this by joining with build_dir once the above chdir
# goes away.
cache_file = 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))
cached_common_args = cache.get_list('ZEPHYR_RUNNER_ARGS_COMMON')
cached_runner_args = cache.get_list(
'ZEPHYR_RUNNER_ARGS_{}'.format(cmake.make_c_identifier(runner)))
# Construct the final command line arguments, create a
# runner-specific parser to handle them, and run the command.
assert isinstance(runner_args, list), runner_args
final_runner_args = (cached_common_args + cached_runner_args +
runner_args + [command_name])
# Having the runners themselves be the place where their argument
# parsing is handled is hackish; it's an artifact of the time
# before the runner package was part of west.
#
# TODO: refactor runner argument parsing higher up into west.
parser = argparse.ArgumentParser(prog=runner)
runner_cls.add_parser(parser)
parsed_args = parser.parse_args(args=final_runner_args)
parsed_args.verbose = args.verbose
runner = runner_cls.create_from_args(parsed_args)
runner.run(command_name)