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 # Use that RunnerConfig to create the ZephyrBinaryRunner instance
# and call its run(). # and call its run().
runner = runner_cls.create(runner_cfg_from_args(args, build_dir), args)
try: try:
runner = runner_cls.create(runner_cfg_from_args(args, build_dir), args)
runner.run(command_name) runner.run(command_name)
except ValueError as ve: except ValueError as ve:
log.err(str(ve), fatal=True) log.err(str(ve), fatal=True)

View file

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

View file

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

View file

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

View file

@ -199,6 +199,16 @@ class RunnerCaps:
self.commands, self.flash_addr) 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: class RunnerConfig:
'''Runner execution-time configuration. '''Runner execution-time configuration.
@ -260,7 +270,9 @@ class _DTFlashAction(argparse.Action):
class ZephyrBinaryRunner(abc.ABC): class ZephyrBinaryRunner(abc.ABC):
'''Abstract superclass for binary runners (flashers, debuggers). '''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 With some exceptions, boards supported by Zephyr must provide
generic means to be flashed (have a Zephyr firmware binary 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() Runner-specific options are added through the do_add_parser()
hook.''' hook.'''
# Common options that depend on runner capabilities. # Common options that depend on runner capabilities. If a
if cls.capabilities().flash_addr: # 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, parser.add_argument('--dt-flash', default='n', choices=_YN_CHOICES,
action=_DTFlashAction, action=_DTFlashAction,
help='''If 'yes', use configuration generated help='''If 'yes', use configuration generated
by device tree (DT) to compute flash by device tree (DT) to compute flash
addresses.''') addresses.''')
else:
parser.add_argument('--dt-flash', help=argparse.SUPPRESS)
# Runner-specific options. # Runner-specific options.
cls.do_add_parser(parser) cls.do_add_parser(parser)
@ -406,13 +425,22 @@ class ZephyrBinaryRunner(abc.ABC):
'''Hook for adding runner-specific options.''' '''Hook for adding runner-specific options.'''
@classmethod @classmethod
@abc.abstractmethod
def create(cls, cfg, args): def create(cls, cfg, args):
'''Create an instance from command-line arguments. '''Create an instance from command-line arguments.
- ``cfg``: RunnerConfig instance (pass to superclass __init__) - ``cfg``: RunnerConfig instance (pass to superclass __init__)
- ``args``: runner-specific argument namespace parsed from - ``args``: runner-specific argument namespace parsed from
execution environment, as specified by ``add_parser()``.''' 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 @classmethod
def get_flash_address(cls, args, build_conf, default=0x0): 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)') help='Number of retries (default 5)')
@classmethod @classmethod
def create(cls, cfg, args): def do_create(cls, cfg, args):
return DediProgBinaryRunner(cfg, return DediProgBinaryRunner(cfg,
spi_image=args.spi_image, spi_image=args.spi_image,
vcc=args.vcc, vcc=args.vcc,

View file

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

View file

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

View file

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

View file

@ -53,7 +53,7 @@ class IntelS1000BinaryRunner(ZephyrBinaryRunner):
help='xt-gdb port, defaults to 20000') help='xt-gdb port, defaults to 20000')
@classmethod @classmethod
def create(cls, cfg, args): def do_create(cls, cfg, args):
return IntelS1000BinaryRunner( return IntelS1000BinaryRunner(
cfg, args.xt_ocd_dir, cfg, args.xt_ocd_dir,
args.ocd_topology, args.ocd_jtag_instr, args.gdb_flash_file, 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) parser.set_defaults(reset_after_load=False)
@classmethod @classmethod
def create(cls, cfg, args): def do_create(cls, cfg, args):
build_conf = BuildConfiguration(cfg.build_dir) build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf) flash_addr = cls.get_flash_address(args, build_conf)
return JLinkBinaryRunner(cfg, args.device, return JLinkBinaryRunner(cfg, args.device,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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