west: runners: add file argument

adds --file argument to west flash/debug as described in #52262.

Signed-off-by: Gerhard Jörges <joerges@metratec.com>
This commit is contained in:
Gerhard Jörges 2022-11-16 15:05:29 +01:00 committed by Marti Bolivar
commit 87a751c266
6 changed files with 142 additions and 36 deletions

View file

@ -802,6 +802,8 @@ __comp_west_runner_cmd()
"
local file_opts="
--file -f
--file-type -t
--elf-file
--hex-file
--bin-file

View file

@ -261,6 +261,8 @@ _west_sign() {
typeset -a -g _west_runner_opts=(
'(-H --context)'{-H,--context}'[print runner-specific options]'
'--board-dir[board directory]:board dir:_directories'
'(-f --file)'{-f,--file}'[path to binary]:path to binary:_files'
'(-t --file-type)'{-t,--file-type}'[type of binary]:type of binary:(hex bin elf)'
'--elf-file[path to zephyr.elf]:path to zephyr.elf:_files'
'--hex-file[path to zephyr.hex]:path to zephyr.hex:_files'
'--bin-file[path to zephyr.bin]:path to zephyr.bin:_files'

View file

@ -20,6 +20,7 @@ from build_helpers import find_build_dir, is_zephyr_build, load_domains, \
FIND_BUILD_DIR_DESCRIPTION
from west.commands import CommandError
from west.configuration import config
from runners.core import FileType
import yaml
from zephyr_ext_common import ZEPHYR_SCRIPTS
@ -132,11 +133,6 @@ def add_parser_common(command, parser_adder=None, parser=None):
# Options used to override RunnerConfig values in runners.yaml.
# TODO: is this actually useful?
group.add_argument('--board-dir', metavar='DIR', help='board directory')
# FIXME: we should just have a single --file argument. The variation
# between runners is confusing people.
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 and should be moved to where --context
# can find them instead.
group.add_argument('--gdb', help='path to GDB')
@ -385,11 +381,40 @@ def get_runner_config(build_dir, yaml_path, runners_yaml, args=None):
def config(attr, default=None):
return getattr(args, attr, None) or yaml_config.get(attr, default)
def filetype(attr):
ftype = str(getattr(args, attr, None)).lower()
if ftype == "hex":
return FileType.HEX
elif ftype == "bin":
return FileType.BIN
elif ftype == "elf":
return FileType.ELF
elif getattr(args, attr, None) is not None:
err = 'unknown --file-type ({}). Please use hex, bin or elf'
raise ValueError(err.format(ftype))
# file-type not provided, try to get from filename
file = getattr(args, "file", None)
if file is not None:
ext = Path(file).suffix
if ext == ".hex":
return FileType.HEX
if ext == ".bin":
return FileType.BIN
if ext == ".elf":
return FileType.ELF
# we couldn't get the file-type, set to
# OTHER and let the runner deal with it
return FileType.OTHER
return RunnerConfig(build_dir,
yaml_config['board_dir'],
output_file('elf'),
output_file('hex'),
output_file('bin'),
config('file'),
filetype('file_type'),
config('gdb'),
config('openocd'),
config('openocd_search', []))

View file

@ -22,6 +22,8 @@ import shutil
import signal
import subprocess
import re
from functools import partial
from enum import Enum
from typing import Dict, List, NamedTuple, NoReturn, Optional, Set, Type, \
Union
@ -237,19 +239,22 @@ class RunnerCaps:
dev_id: bool = False,
flash_addr: bool = False,
erase: bool = False,
tool_opt: bool = False):
tool_opt: bool = False,
file: bool = False):
self.commands = commands
self.dev_id = dev_id
self.flash_addr = bool(flash_addr)
self.erase = bool(erase)
self.tool_opt = bool(tool_opt)
self.file = bool(file)
def __str__(self):
return (f'RunnerCaps(commands={self.commands}, '
f'dev_id={self.dev_id}, '
f'flash_addr={self.flash_addr}, '
f'erase={self.erase}, '
f'tool_opt={self.tool_opt}'
f'tool_opt={self.tool_opt}, '
f'file={self.file}'
')')
@ -261,6 +266,13 @@ def _missing_cap(cls: Type['ZephyrBinaryRunner'], option: str) -> NoReturn:
raise ValueError(f"{cls.name()} doesn't support {option} option")
class FileType(Enum):
OTHER = 0
HEX = 1
BIN = 2
ELF = 3
class RunnerConfig(NamedTuple):
'''Runner execution-time configuration.
@ -268,13 +280,15 @@ class RunnerConfig(NamedTuple):
can register specific configuration options using their
do_add_parser() hooks.
'''
build_dir: str # application build directory
board_dir: str # board definition directory
elf_file: Optional[str] # zephyr.elf path, or None
hex_file: Optional[str] # zephyr.hex path, or None
bin_file: Optional[str] # zephyr.bin path, or None
gdb: Optional[str] = None # path to a usable gdb
openocd: Optional[str] = None # path to a usable openocd
build_dir: str # application build directory
board_dir: str # board definition directory
elf_file: Optional[str] # zephyr.elf path, or None
hex_file: Optional[str] # zephyr.hex path, or None
bin_file: Optional[str] # zephyr.bin path, or None
file: Optional[str] # binary file path (provided by the user), or None
file_type: Optional[FileType] = FileType.OTHER # binary file type
gdb: Optional[str] = None # path to a usable gdb
openocd: Optional[str] = None # path to a usable openocd
openocd_search: List[str] = [] # add these paths to the openocd search path
@ -298,12 +312,14 @@ class _ToggleAction(argparse.Action):
class DeprecatedAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
_logger.warning(f'Argument {self.option_strings[0]} is deprecated, '
f'use {self._replacement} instead.')
_logger.warning(f'Argument {self.option_strings[0]} is deprecated' +
(f' for your runner {self._cls.name()}' if self._cls is not None else '') +
f', use {self._replacement} instead.')
setattr(namespace, self.dest, values)
def depr_action(*args, replacement=None, **kwargs):
def depr_action(*args, cls=None, replacement=None, **kwargs):
action = DeprecatedAction(*args, **kwargs)
setattr(action, '_cls', cls)
setattr(action, '_replacement', replacement)
return action
@ -465,6 +481,30 @@ class ZephyrBinaryRunner(abc.ABC):
else:
parser.add_argument('--dt-flash', help=argparse.SUPPRESS)
if caps.file:
parser.add_argument('-f', '--file',
dest='file',
help="path to binary file")
parser.add_argument('-t', '--file-type',
dest='file_type',
help="type of binary file")
else:
parser.add_argument('-f', '--file', help=argparse.SUPPRESS)
parser.add_argument('-t', '--file-type', help=argparse.SUPPRESS)
parser.add_argument('--elf-file',
metavar='FILE',
action=(partial(depr_action, cls=cls, replacement='-f/--file') if caps.file else None),
help='path to zephyr.elf' if not caps.file else 'Deprecated, use -f/--file instead.')
parser.add_argument('--hex-file',
metavar='FILE',
action=(partial(depr_action, cls=cls, replacement='-f/--file') if caps.file else None),
help='path to zephyr.hex' if not caps.file else 'Deprecated, use -f/--file instead.')
parser.add_argument('--bin-file',
metavar='FILE',
action=(partial(depr_action, cls=cls, replacement='-f/--file') if caps.file else None),
help='path to zephyr.bin' if not caps.file else 'Deprecated, use -f/--file instead.')
parser.add_argument('--erase', '--no-erase', nargs=0,
action=_ToggleAction,
help=("mass erase flash before loading, or don't"
@ -500,6 +540,12 @@ class ZephyrBinaryRunner(abc.ABC):
_missing_cap(cls, '--erase')
if args.tool_opt and not caps.tool_opt:
_missing_cap(cls, '--tool-opt')
if args.file and not caps.file:
_missing_cap(cls, '--file')
if args.file_type and not args.file:
raise ValueError("--file-type requires --file")
if args.file_type and not caps.file:
_missing_cap(cls, '--file-type')
ret = cls.do_create(cfg, args)
if args.erase:

View file

@ -13,7 +13,7 @@ import subprocess
import sys
import tempfile
from runners.core import ZephyrBinaryRunner, RunnerCaps
from runners.core import ZephyrBinaryRunner, RunnerCaps, FileType
try:
import pylink
@ -43,6 +43,8 @@ class JLinkBinaryRunner(ZephyrBinaryRunner):
gdb_port=DEFAULT_JLINK_GDB_PORT,
tui=False, tool_opt=[]):
super().__init__(cfg)
self.file = cfg.file
self.file_type = cfg.file_type
self.hex_name = cfg.hex_file
self.bin_name = cfg.bin_file
self.elf_name = cfg.elf_file
@ -73,7 +75,7 @@ class JLinkBinaryRunner(ZephyrBinaryRunner):
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
dev_id=True, flash_addr=True, erase=True,
tool_opt=True)
tool_opt=True, file=True)
@classmethod
def dev_id_help(cls) -> str:
@ -246,11 +248,17 @@ class JLinkBinaryRunner(ZephyrBinaryRunner):
else:
if self.gdb_cmd is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_name is None:
if self.file is not None:
if self.file_type != FileType.ELF:
raise ValueError('Cannot debug; elf file required')
elf_name = self.file
elif self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
else:
elf_name = self.elf_name
client_cmd = (self.gdb_cmd +
self.tui_arg +
[self.elf_name] +
[elf_name] +
['-ex', 'target remote {}:{}'.format(self.gdb_host, self.gdb_port)])
if command == 'debug':
client_cmd += ['-ex', 'monitor halt',
@ -276,20 +284,42 @@ class JLinkBinaryRunner(ZephyrBinaryRunner):
if self.erase:
lines.append('erase') # Erase all flash sectors
# Get the build artifact to flash, preferring .hex over .bin
if self.hex_name is not None and os.path.isfile(self.hex_name):
flash_file = self.hex_name
flash_cmd = f'loadfile "{self.hex_name}"'
elif self.bin_name is not None and os.path.isfile(self.bin_name):
if self.dt_flash:
flash_addr = self.flash_address_from_build_conf(self.build_conf)
# Get the build artifact to flash
if self.file is not None:
# use file provided by the user
if not os.path.isfile(self.file):
err = 'Cannot flash; file ({}) not found'
raise ValueError(err.format(self.file))
flash_file = self.file
if self.file_type == FileType.HEX:
flash_cmd = f'loadfile "{self.file}"'
elif self.file_type == FileType.BIN:
if self.dt_flash:
flash_addr = self.flash_address_from_build_conf(self.build_conf)
else:
flash_addr = 0
flash_cmd = f'loadfile "{self.file}" 0x{flash_addr:x}'
else:
flash_addr = 0
flash_file = self.bin_name
flash_cmd = f'loadfile "{self.bin_name}" 0x{flash_addr:x}'
err = 'Cannot flash; jlink runner only supports hex and bin files'
raise ValueError(err)
else:
err = 'Cannot flash; no hex ({}) or bin ({}) files found.'
raise ValueError(err.format(self.hex_name, self.bin_name))
# use hex or bin file provided by the buildsystem, preferring .hex over .bin
if self.hex_name is not None and os.path.isfile(self.hex_name):
flash_file = self.hex_name
flash_cmd = f'loadfile "{self.hex_name}"'
elif self.bin_name is not None and os.path.isfile(self.bin_name):
if self.dt_flash:
flash_addr = self.flash_address_from_build_conf(self.build_conf)
else:
flash_addr = 0
flash_file = self.bin_name
flash_cmd = f'loadfile "{self.bin_name}" 0x{flash_addr:x}'
else:
err = 'Cannot flash; no hex ({}) or bin ({}) files found.'
raise ValueError(err.format(self.hex_name, self.bin_name))
# Flash the selected build artifact
lines.append(flash_cmd)

View file

@ -6,7 +6,7 @@
import pytest
from runners.core import RunnerConfig
from runners.core import RunnerConfig, FileType
RC_BUILD_DIR = '/test/build-dir'
RC_BOARD_DIR = '/test/zephyr/boards/test-arch/test-board'
@ -22,5 +22,6 @@ RC_OPENOCD_SEARCH = ['/test/openocd/search']
def runner_config():
'''Fixture which provides a runners.core.RunnerConfig.'''
return RunnerConfig(RC_BUILD_DIR, RC_BOARD_DIR, RC_KERNEL_ELF,
RC_KERNEL_HEX, RC_KERNEL_BIN, gdb=RC_GDB,
openocd=RC_OPENOCD, openocd_search=RC_OPENOCD_SEARCH)
RC_KERNEL_HEX, RC_KERNEL_BIN, None, FileType.OTHER,
gdb=RC_GDB, openocd=RC_OPENOCD,
openocd_search=RC_OPENOCD_SEARCH)