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:
parent
4a354e891d
commit
b611e5b1ea
4 changed files with 203 additions and 1 deletions
49
scripts/meta/west/cmd/debug.py
Normal file
49
scripts/meta/west/cmd/debug.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright (c) 2018 Open Source Foundries Limited.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'''west "debug" and "debugserver" commands.'''
|
||||||
|
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
from .run_common import desc_common, add_parser_common, do_run_common
|
||||||
|
from . import WestCommand
|
||||||
|
|
||||||
|
|
||||||
|
class Debug(WestCommand):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Debug, self).__init__(
|
||||||
|
'debug',
|
||||||
|
'Connect to the board and start a debugging session.\n\n' +
|
||||||
|
desc_common('debug'),
|
||||||
|
accepts_unknown_args=True)
|
||||||
|
|
||||||
|
def do_add_parser(self, parser_adder):
|
||||||
|
return add_parser_common(parser_adder, self)
|
||||||
|
|
||||||
|
def do_run(self, my_args, runner_args):
|
||||||
|
do_run_common(self, my_args, runner_args,
|
||||||
|
'ZEPHYR_BOARD_DEBUG_RUNNER')
|
||||||
|
|
||||||
|
|
||||||
|
class DebugServer(WestCommand):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DebugServer, self).__init__(
|
||||||
|
'debugserver',
|
||||||
|
dedent('''
|
||||||
|
Connect to the board and accept debug networking connections.
|
||||||
|
|
||||||
|
The debug server binds to a known port, and allows client software
|
||||||
|
started elsewhere to connect to it and debug the running
|
||||||
|
Zephyr image.\n\n''') +
|
||||||
|
desc_common('debugserver'),
|
||||||
|
accepts_unknown_args=True)
|
||||||
|
|
||||||
|
def do_add_parser(self, parser_adder):
|
||||||
|
return add_parser_common(parser_adder, self)
|
||||||
|
|
||||||
|
def do_run(self, my_args, runner_args):
|
||||||
|
do_run_common(self, my_args, runner_args,
|
||||||
|
'ZEPHYR_BOARD_DEBUG_RUNNER')
|
25
scripts/meta/west/cmd/flash.py
Normal file
25
scripts/meta/west/cmd/flash.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright (c) 2018 Open Source Foundries Limited.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'''west "flash" command'''
|
||||||
|
|
||||||
|
from .run_common import desc_common, add_parser_common, do_run_common
|
||||||
|
from . import WestCommand
|
||||||
|
|
||||||
|
|
||||||
|
class Flash(WestCommand):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(Flash, self).__init__(
|
||||||
|
'flash',
|
||||||
|
'Flash and run a binary onto a board.\n\n' +
|
||||||
|
desc_common('flash'),
|
||||||
|
accepts_unknown_args=True)
|
||||||
|
|
||||||
|
def do_add_parser(self, parser_adder):
|
||||||
|
return add_parser_common(parser_adder, self)
|
||||||
|
|
||||||
|
def do_run(self, my_args, runner_args):
|
||||||
|
do_run_common(self, my_args, runner_args,
|
||||||
|
'ZEPHYR_BOARD_FLASH_RUNNER')
|
126
scripts/meta/west/cmd/run_common.py
Normal file
126
scripts/meta/west/cmd/run_common.py
Normal 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)
|
|
@ -14,10 +14,12 @@ from subprocess import CalledProcessError
|
||||||
|
|
||||||
from . import log
|
from . import log
|
||||||
from .cmd import CommandContextError
|
from .cmd import CommandContextError
|
||||||
|
from .cmd.flash import Flash
|
||||||
|
from .cmd.debug import Debug, DebugServer
|
||||||
from .util import quote_sh_list
|
from .util import quote_sh_list
|
||||||
|
|
||||||
|
|
||||||
COMMANDS = ()
|
COMMANDS = (Flash(), Debug(), DebugServer())
|
||||||
'''Supported top-level commands.'''
|
'''Supported top-level commands.'''
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue