runners: enforce RunnerCaps via create() indirection

Require all implementations to provide a do_create(), a new
ZephyrBinaryRunner abstract class method, and make create() itself
concrete.

This allows us to enforce common conventions related to individual
runner capabilities as each runner provides to the core via
RunnerCaps.

For now, just enforce that:

- common options related to capabilities are always added, so runners
  can't reuse them for different ends

- common options provided for runners which don't support them emit
  sensible error messages that should be easy to diagnose and support

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Martí Bolívar 2020-06-23 13:35:52 -07:00 committed by Carles Cufí
commit f8e8e9229d
21 changed files with 52 additions and 24 deletions

View file

@ -210,8 +210,8 @@ def do_run_common(command, user_args, user_runner_args):
#
# Use that RunnerConfig to create the ZephyrBinaryRunner instance
# and call its run().
runner = runner_cls.create(runner_cfg_from_args(args, build_dir), args)
try:
runner = runner_cls.create(runner_cfg_from_args(args, build_dir), args)
runner.run(command_name)
except ValueError as ve:
log.err(str(ve), fatal=True)

View file

@ -27,7 +27,7 @@ class BlackMagicProbeRunner(ZephyrBinaryRunner):
return RunnerCaps(commands={'flash', 'debug', 'attach'})
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return BlackMagicProbeRunner(cfg, args.gdb_serial)
@classmethod

View file

@ -41,7 +41,7 @@ class BossacBinaryRunner(ZephyrBinaryRunner):
help='serial port to use, default is /dev/ttyACM0')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return BossacBinaryRunner(cfg, bossac=args.bossac,
port=args.bossac_port, offset=args.offset)

View file

@ -87,7 +87,7 @@ class CANopenBinaryRunner(ZephyrBinaryRunner):
parser.set_defaults(confirm=True)
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return CANopenBinaryRunner(cfg, int(args.node_id),
can_context=args.can_context,
program_number=int(args.program_number),

View file

@ -199,6 +199,16 @@ class RunnerCaps:
self.commands, self.flash_addr)
def _missing_cap(cls, option):
# Helper function that's called when an option was given on the
# command line that corresponds to a missing capability.
#
# 'cls' is a ZephyrBinaryRunner subclass; 'option' is an option
# that can't be supported due to missing capability.
raise ValueError(f"{cls.name()} doesn't support {option} option")
class RunnerConfig:
'''Runner execution-time configuration.
@ -260,7 +270,9 @@ class _DTFlashAction(argparse.Action):
class ZephyrBinaryRunner(abc.ABC):
'''Abstract superclass for binary runners (flashers, debuggers).
**Note**: these APIs are still evolving, and will change!
**Note**: this class's API has changed relatively rarely since it
as added, but it is not considered a stable Zephyr API, and may change
without notice.
With some exceptions, boards supported by Zephyr must provide
generic means to be flashed (have a Zephyr firmware binary
@ -389,13 +401,20 @@ class ZephyrBinaryRunner(abc.ABC):
Runner-specific options are added through the do_add_parser()
hook.'''
# Common options that depend on runner capabilities.
if cls.capabilities().flash_addr:
# Common options that depend on runner capabilities. If a
# capability is not supported, the option string or strings
# are added anyway, to prevent an individual runner class from
# using them to mean something else.
caps = cls.capabilities()
if caps.flash_addr:
parser.add_argument('--dt-flash', default='n', choices=_YN_CHOICES,
action=_DTFlashAction,
help='''If 'yes', use configuration generated
by device tree (DT) to compute flash
addresses.''')
else:
parser.add_argument('--dt-flash', help=argparse.SUPPRESS)
# Runner-specific options.
cls.do_add_parser(parser)
@ -406,13 +425,22 @@ class ZephyrBinaryRunner(abc.ABC):
'''Hook for adding runner-specific options.'''
@classmethod
@abc.abstractmethod
def create(cls, cfg, args):
'''Create an instance from command-line arguments.
- ``cfg``: RunnerConfig instance (pass to superclass __init__)
- ``args``: runner-specific argument namespace parsed from
execution environment, as specified by ``add_parser()``.'''
caps = cls.capabilities()
if args.dt_flash and not caps.flash_addr:
_missing_cap(cls, '--dt-flash')
return cls.do_create(cfg, args)
@classmethod
@abc.abstractmethod
def do_create(cls, cfg, args):
'''Hook for instance creation from command line arguments.'''
@classmethod
def get_flash_address(cls, args, build_conf, default=0x0):

View file

@ -40,7 +40,7 @@ class DediProgBinaryRunner(ZephyrBinaryRunner):
help='Number of retries (default 5)')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return DediProgBinaryRunner(cfg,
spi_image=args.spi_image,
vcc=args.vcc,

View file

@ -74,7 +74,7 @@ class DfuUtilBinaryRunner(ZephyrBinaryRunner):
help='dfu-util executable; defaults to "dfu-util"')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
if args.img is None:
args.img = cfg.bin_file

View file

@ -63,7 +63,7 @@ class Esp32BinaryRunner(ZephyrBinaryRunner):
help='Partition table to flash')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
if args.esp_tool:
espidf = args.esp_tool
else:

View file

@ -29,7 +29,7 @@ class HiFive1BinaryRunner(ZephyrBinaryRunner):
pass
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
if cfg.gdb is None:
raise ValueError('--gdb not provided at command line')

View file

@ -53,7 +53,7 @@ class IntelS1000BinaryRunner(ZephyrBinaryRunner):
help='xt-gdb port, defaults to 20000')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return IntelS1000BinaryRunner(
cfg, args.xt_ocd_dir,
args.ocd_topology, args.ocd_jtag_instr, args.gdb_flash_file,

View file

@ -90,7 +90,7 @@ class JLinkBinaryRunner(ZephyrBinaryRunner):
parser.set_defaults(reset_after_load=False)
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf)
return JLinkBinaryRunner(cfg, args.device,

View file

@ -58,7 +58,7 @@ class MdbBinaryRunner(ZephyrBinaryRunner):
targets are connected''')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return MdbBinaryRunner(
cfg,
cores=args.cores,

View file

@ -43,7 +43,7 @@ class MiscFlasher(ZephyrBinaryRunner):
directory''')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return MiscFlasher(cfg, args.cmd, args.args)
def do_run(self, *args, **kwargs):

View file

@ -41,7 +41,7 @@ class Nios2BinaryRunner(ZephyrBinaryRunner):
help='if given, GDB uses -tui')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return Nios2BinaryRunner(cfg,
quartus_py=args.quartus_flash,
cpu_sof=args.cpu_sof,

View file

@ -53,7 +53,7 @@ class NrfJprogBinaryRunner(ZephyrBinaryRunner):
e.g. "--recover"''')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return NrfJprogBinaryRunner(cfg, args.nrf_family, args.softreset,
args.snr, erase=args.erase,
tool_opt=args.tool_opt)

View file

@ -45,7 +45,7 @@ class NsimBinaryRunner(ZephyrBinaryRunner):
help='nsim props file, defaults to nsim.props')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
if cfg.gdb is None:
raise ValueError('--gdb not provided at command line')

View file

@ -98,7 +98,7 @@ class OpenOcdBinaryRunner(ZephyrBinaryRunner):
help='openocd gdb port, defaults to 3333')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return OpenOcdBinaryRunner(
cfg,
pre_init=args.cmd_pre_init,

View file

@ -95,7 +95,7 @@ class PyOcdBinaryRunner(ZephyrBinaryRunner):
e.g. \'--script=user.py\' ''')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf)

View file

@ -24,7 +24,7 @@ class QemuBinaryRunner(ZephyrBinaryRunner):
pass # Nothing to do.
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return QemuBinaryRunner(cfg)
def do_run(self, command, **kwargs):

View file

@ -79,7 +79,7 @@ class Stm32flashBinaryRunner(ZephyrBinaryRunner):
help='verify writes, default False')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
return Stm32flashBinaryRunner(cfg, device=args.device, action=args.action,
baud=args.baud_rate, force_binary=args.force_binary,
start_addr=args.start_addr, exec_addr=args.execution_addr,

View file

@ -26,7 +26,7 @@ class XtensaBinaryRunner(ZephyrBinaryRunner):
help='path to XTensa tools')
@classmethod
def create(cls, cfg, args):
def do_create(cls, cfg, args):
# Override any GDB with the one provided by the XTensa tools.
cfg.gdb = path.join(args.xcc_tools, 'bin', 'xt-gdb')
return XtensaBinaryRunner(cfg)