Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 1 | # Copyright (c) 2018 Open Source Foundries Limited. |
| 2 | # |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | |
| 5 | '''Common code used by commands which execute runners. |
| 6 | ''' |
| 7 | |
| 8 | import argparse |
Marti Bolivar | ddce583 | 2019-06-02 21:33:41 -0600 | [diff] [blame] | 9 | import logging |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 10 | from os import close, getcwd, path, fspath |
Martí Bolívar | a526877 | 2020-03-17 16:26:51 -0700 | [diff] [blame] | 11 | from pathlib import Path |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 12 | from subprocess import CalledProcessError |
Martí Bolívar | a526877 | 2020-03-17 16:26:51 -0700 | [diff] [blame] | 13 | import sys |
Marti Bolivar | f08935f | 2019-06-03 01:05:26 -0600 | [diff] [blame] | 14 | import tempfile |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 15 | import textwrap |
Marti Bolivar | f08935f | 2019-06-03 01:05:26 -0600 | [diff] [blame] | 16 | import traceback |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 17 | |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 18 | from west import log |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 19 | from build_helpers import find_build_dir, is_zephyr_build, \ |
| 20 | FIND_BUILD_DIR_DESCRIPTION |
Marti Bolivar | f08935f | 2019-06-03 01:05:26 -0600 | [diff] [blame] | 21 | from west.commands import CommandError |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 22 | from west.configuration import config |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 23 | import yaml |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 24 | |
Martí Bolívar | b3c6470 | 2021-02-23 13:40:13 -0800 | [diff] [blame] | 25 | from zephyr_ext_common import ZEPHYR_SCRIPTS |
| 26 | |
| 27 | # Runners depend on edtlib. Make sure the copy in the tree is |
| 28 | # available to them before trying to import any. |
Jordan Yates | 8e4107f | 2022-04-30 21:13:52 +1000 | [diff] [blame] | 29 | sys.path.insert(0, str(ZEPHYR_SCRIPTS / 'dts' / 'python-devicetree' / 'src')) |
Martí Bolívar | b3c6470 | 2021-02-23 13:40:13 -0800 | [diff] [blame] | 30 | |
Marti Bolivar | c07267a | 2019-05-25 15:41:36 -0600 | [diff] [blame] | 31 | from runners import get_runner_cls, ZephyrBinaryRunner, MissingProgram |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 32 | from runners.core import RunnerConfig |
| 33 | import zcmake |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 34 | |
| 35 | # Context-sensitive help indentation. |
| 36 | # Don't change this, or output from argparse won't match up. |
| 37 | INDENT = ' ' * 2 |
| 38 | |
Marti Bolivar | ddce583 | 2019-06-02 21:33:41 -0600 | [diff] [blame] | 39 | if log.VERBOSE >= log.VERBOSE_NORMAL: |
| 40 | # Using level 1 allows sub-DEBUG levels of verbosity. The |
| 41 | # west.log module decides whether or not to actually print the |
| 42 | # message. |
| 43 | # |
| 44 | # https://docs.python.org/3.7/library/logging.html#logging-levels. |
| 45 | LOG_LEVEL = 1 |
| 46 | else: |
| 47 | LOG_LEVEL = logging.INFO |
| 48 | |
Marti Bolivar | ec8dbf3 | 2019-06-02 22:46:02 -0600 | [diff] [blame] | 49 | def _banner(msg): |
| 50 | log.inf('-- ' + msg, colorize=True) |
Marti Bolivar | ddce583 | 2019-06-02 21:33:41 -0600 | [diff] [blame] | 51 | |
| 52 | class WestLogFormatter(logging.Formatter): |
| 53 | |
| 54 | def __init__(self): |
Marti Bolivar | ec8dbf3 | 2019-06-02 22:46:02 -0600 | [diff] [blame] | 55 | super().__init__(fmt='%(name)s: %(message)s') |
Marti Bolivar | ddce583 | 2019-06-02 21:33:41 -0600 | [diff] [blame] | 56 | |
| 57 | class WestLogHandler(logging.Handler): |
| 58 | |
| 59 | def __init__(self, *args, **kwargs): |
| 60 | super().__init__(*args, **kwargs) |
| 61 | self.setFormatter(WestLogFormatter()) |
| 62 | self.setLevel(LOG_LEVEL) |
| 63 | |
| 64 | def emit(self, record): |
| 65 | fmt = self.format(record) |
| 66 | lvl = record.levelno |
| 67 | if lvl > logging.CRITICAL: |
| 68 | log.die(fmt) |
| 69 | elif lvl >= logging.ERROR: |
| 70 | log.err(fmt) |
| 71 | elif lvl >= logging.WARNING: |
| 72 | log.wrn(fmt) |
| 73 | elif lvl >= logging.INFO: |
Marti Bolivar | ec8dbf3 | 2019-06-02 22:46:02 -0600 | [diff] [blame] | 74 | _banner(fmt) |
Marti Bolivar | ddce583 | 2019-06-02 21:33:41 -0600 | [diff] [blame] | 75 | elif lvl >= logging.DEBUG: |
| 76 | log.dbg(fmt) |
| 77 | else: |
| 78 | log.dbg(fmt, level=log.VERBOSE_EXTREME) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 79 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 80 | def command_verb(command): |
| 81 | return "flash" if command.name == "flash" else "debug" |
| 82 | |
| 83 | def add_parser_common(command, parser_adder=None, parser=None): |
| 84 | if parser_adder is not None: |
| 85 | parser = parser_adder.add_parser( |
| 86 | command.name, |
| 87 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 88 | help=command.help, |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 89 | description=command.description) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 90 | |
Martí Bolívar | 0d5e6c1 | 2020-12-09 15:53:00 -0800 | [diff] [blame] | 91 | # Remember to update west-completion.bash if you add or remove |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 92 | # flags |
| 93 | |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 94 | group = parser.add_argument_group('general options', |
| 95 | FIND_BUILD_DIR_DESCRIPTION) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 96 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 97 | group.add_argument('-d', '--build-dir', metavar='DIR', |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 98 | help='application build directory') |
| 99 | # still supported for backwards compatibility, but questionably |
| 100 | # useful now that we do everything with runners.yaml |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 101 | group.add_argument('-c', '--cmake-cache', metavar='FILE', |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 102 | help=argparse.SUPPRESS) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 103 | group.add_argument('-r', '--runner', |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 104 | help='override default runner from --build-dir') |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 105 | group.add_argument('--skip-rebuild', action='store_true', |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 106 | help='do not refresh cmake dependencies first') |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 107 | |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 108 | group = parser.add_argument_group( |
Martí Bolívar | 4ac9607 | 2020-11-02 08:30:14 -0800 | [diff] [blame] | 109 | 'runner configuration', |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 110 | textwrap.dedent(f'''\ |
Martí Bolívar | 4ac9607 | 2020-11-02 08:30:14 -0800 | [diff] [blame] | 111 | =================================================================== |
| 112 | IMPORTANT: |
| 113 | Individual runners support additional options not printed here. |
| 114 | =================================================================== |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 115 | |
Martí Bolívar | 4ac9607 | 2020-11-02 08:30:14 -0800 | [diff] [blame] | 116 | Run "west {command.name} --context" for runner-specific options. |
| 117 | |
| 118 | If a build directory is found, --context also prints per-runner |
| 119 | settings found in that build directory's runners.yaml file. |
| 120 | |
| 121 | Use "west {command.name} --context -r RUNNER" to limit output to a |
| 122 | specific RUNNER. |
| 123 | |
| 124 | Some runner settings also can be overridden with options like |
| 125 | --hex-file. However, this depends on the runner: not all runners |
| 126 | respect --elf-file / --hex-file / --bin-file, nor use gdb or openocd, |
| 127 | etc.''')) |
| 128 | group.add_argument('-H', '--context', action='store_true', |
| 129 | help='print runner- and build-specific help') |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 130 | # Options used to override RunnerConfig values in runners.yaml. |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 131 | # TODO: is this actually useful? |
| 132 | group.add_argument('--board-dir', metavar='DIR', help='board directory') |
| 133 | # FIXME: we should just have a single --file argument. The variation |
| 134 | # between runners is confusing people. |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 135 | group.add_argument('--elf-file', metavar='FILE', help='path to zephyr.elf') |
| 136 | group.add_argument('--hex-file', metavar='FILE', help='path to zephyr.hex') |
| 137 | group.add_argument('--bin-file', metavar='FILE', help='path to zephyr.bin') |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 138 | # FIXME: these are runner-specific and should be moved to where --context |
| 139 | # can find them instead. |
| 140 | group.add_argument('--gdb', help='path to GDB') |
| 141 | group.add_argument('--openocd', help='path to openocd') |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 142 | group.add_argument( |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 143 | '--openocd-search', metavar='DIR', |
Martí Bolívar | 6e4c2b9 | 2020-06-25 12:58:58 -0700 | [diff] [blame] | 144 | help='path to add to openocd search path, if applicable') |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 145 | |
| 146 | return parser |
| 147 | |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 148 | def do_run_common(command, user_args, user_runner_args): |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 149 | # This is the main routine for all the "west flash", "west debug", |
| 150 | # etc. commands. |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 151 | |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 152 | if user_args.context: |
| 153 | dump_context(command, user_args, user_runner_args) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 154 | return |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 155 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 156 | command_name = command.name |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 157 | build_dir = get_build_dir(user_args) |
| 158 | cache = load_cmake_cache(build_dir, user_args) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 159 | board = cache['CACHED_BOARD'] |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 160 | if not user_args.skip_rebuild: |
| 161 | rebuild(command, build_dir, user_args) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 162 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 163 | # Load runners.yaml. |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 164 | yaml_path = runners_yaml_path(build_dir, board) |
| 165 | runners_yaml = load_runners_yaml(yaml_path) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 166 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 167 | # Get a concrete ZephyrBinaryRunner subclass to use based on |
| 168 | # runners.yaml and command line arguments. |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 169 | runner_cls = use_runner_cls(command, board, user_args, runners_yaml, |
Martí Bolívar | a526877 | 2020-03-17 16:26:51 -0700 | [diff] [blame] | 170 | cache) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 171 | runner_name = runner_cls.name() |
| 172 | |
| 173 | # Set up runner logging to delegate to west.log commands. |
| 174 | logger = logging.getLogger('runners') |
| 175 | logger.setLevel(LOG_LEVEL) |
| 176 | logger.addHandler(WestLogHandler()) |
| 177 | |
| 178 | # If the user passed -- to force the parent argument parser to stop |
| 179 | # parsing, it will show up here, and needs to be filtered out. |
Martí Bolívar | 97dbda2 | 2020-02-28 11:13:37 -0800 | [diff] [blame] | 180 | runner_args = [arg for arg in user_runner_args if arg != '--'] |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 181 | |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 182 | # Arguments in this order to allow specific to override general: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 183 | # |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 184 | # - runner-specific runners.yaml arguments |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 185 | # - user-provided command line arguments |
| 186 | final_argv = runners_yaml['args'][runner_name] + runner_args |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 187 | |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 188 | # 'user_args' contains parsed arguments which are: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 189 | # |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 190 | # 1. provided on the command line, and |
| 191 | # 2. handled by add_parser_common(), and |
| 192 | # 3. *not* runner-specific |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 193 | # |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 194 | # 'final_argv' contains unparsed arguments from either: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 195 | # |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 196 | # 1. runners.yaml, or |
| 197 | # 2. the command line |
| 198 | # |
| 199 | # We next have to: |
| 200 | # |
| 201 | # - parse 'final_argv' now that we have all the command line |
| 202 | # arguments |
| 203 | # - create a RunnerConfig using 'user_args' and the result |
| 204 | # of parsing 'final_argv' |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 205 | parser = argparse.ArgumentParser(prog=runner_name) |
| 206 | add_parser_common(command, parser=parser) |
| 207 | runner_cls.add_parser(parser) |
Martí Bolívar | dc09484 | 2020-02-28 11:31:28 -0800 | [diff] [blame] | 208 | args, unknown = parser.parse_known_args(args=final_argv) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 209 | if unknown: |
| 210 | log.die(f'runner {runner_name} received unknown arguments: {unknown}') |
| 211 | |
Martí Bolívar | dc09484 | 2020-02-28 11:31:28 -0800 | [diff] [blame] | 212 | # Override args with any user_args. The latter must take |
| 213 | # precedence, or e.g. --hex-file on the command line would be |
| 214 | # ignored in favor of a board.cmake setting. |
| 215 | for a, v in vars(user_args).items(): |
| 216 | if v is not None: |
| 217 | setattr(args, a, v) |
| 218 | |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 219 | # Create the RunnerConfig from runners.yaml and any command line |
| 220 | # overrides. |
| 221 | runner_config = get_runner_config(build_dir, yaml_path, runners_yaml, args) |
| 222 | log.dbg(f'runner_config: {runner_config}', level=log.VERBOSE_VERY) |
| 223 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 224 | # Use that RunnerConfig to create the ZephyrBinaryRunner instance |
| 225 | # and call its run(). |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 226 | try: |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 227 | runner = runner_cls.create(runner_config, args) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 228 | runner.run(command_name) |
| 229 | except ValueError as ve: |
| 230 | log.err(str(ve), fatal=True) |
| 231 | dump_traceback() |
| 232 | raise CommandError(1) |
| 233 | except MissingProgram as e: |
| 234 | log.die('required program', e.filename, |
| 235 | 'not found; install it or add its location to PATH') |
| 236 | except RuntimeError as re: |
Martí Bolívar | f8cb3d4 | 2020-02-28 11:21:59 -0800 | [diff] [blame] | 237 | if not user_args.verbose: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 238 | log.die(re) |
| 239 | else: |
| 240 | log.err('verbose mode enabled, dumping stack:', fatal=True) |
| 241 | raise |
| 242 | |
| 243 | def get_build_dir(args, die_if_none=True): |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 244 | # Get the build directory for the given argument list and environment. |
| 245 | if args.build_dir: |
| 246 | return args.build_dir |
| 247 | |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 248 | guess = config.get('build', 'guess-dir', fallback='never') |
Ulf Magnusson | 4699375 | 2019-09-05 20:10:53 +0200 | [diff] [blame] | 249 | guess = guess == 'runners' |
Carles Cufi | 41f1f64 | 2019-06-17 11:47:11 +0200 | [diff] [blame] | 250 | dir = find_build_dir(None, guess) |
Carles Cufi | 49c4b1c | 2019-06-03 12:43:38 +0200 | [diff] [blame] | 251 | |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 252 | if dir and is_zephyr_build(dir): |
Carles Cufi | 49c4b1c | 2019-06-03 12:43:38 +0200 | [diff] [blame] | 253 | return dir |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 254 | elif die_if_none: |
Carles Cufi | 98980c6 | 2019-06-05 16:04:29 +0200 | [diff] [blame] | 255 | msg = '--build-dir was not given, ' |
| 256 | if dir: |
| 257 | msg = msg + 'and neither {} nor {} are zephyr build directories.' |
| 258 | else: |
| 259 | msg = msg + ('{} is not a build directory and the default build ' |
| 260 | 'directory cannot be determined. Check your ' |
| 261 | 'build.dir-fmt configuration option') |
| 262 | log.die(msg.format(getcwd(), dir)) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 263 | else: |
| 264 | return None |
| 265 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 266 | def load_cmake_cache(build_dir, args): |
| 267 | cache_file = path.join(build_dir, args.cmake_cache or zcmake.DEFAULT_CACHE) |
| 268 | try: |
| 269 | return zcmake.CMakeCache(cache_file) |
| 270 | except FileNotFoundError: |
| 271 | log.die(f'no CMake cache found (expected one at {cache_file})') |
| 272 | |
| 273 | def rebuild(command, build_dir, args): |
| 274 | _banner(f'west {command.name}: rebuilding') |
| 275 | try: |
Torsten Rasmussen | 3dd65a7 | 2021-03-17 14:09:37 +0100 | [diff] [blame] | 276 | zcmake.run_build(build_dir) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 277 | except CalledProcessError: |
| 278 | if args.build_dir: |
| 279 | log.die(f're-build in {args.build_dir} failed') |
| 280 | else: |
| 281 | log.die(f're-build in {build_dir} failed (no --build-dir given)') |
| 282 | |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 283 | def runners_yaml_path(build_dir, board): |
| 284 | ret = Path(build_dir) / 'zephyr' / 'runners.yaml' |
| 285 | if not ret.is_file(): |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 286 | log.die(f'either a pristine build is needed, or board {board} ' |
| 287 | "doesn't support west flash/debug " |
| 288 | '(no ZEPHYR_RUNNERS_YAML in CMake cache)') |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 289 | return ret |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 290 | |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 291 | def load_runners_yaml(path): |
| 292 | # Load runners.yaml and convert to Python object. |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 293 | |
| 294 | try: |
| 295 | with open(path, 'r') as f: |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 296 | content = yaml.safe_load(f.read()) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 297 | except FileNotFoundError: |
| 298 | log.die(f'runners.yaml file not found: {path}') |
| 299 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 300 | if not content.get('runners'): |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 301 | log.wrn(f'no pre-configured runners in {path}; ' |
| 302 | "this probably won't work") |
| 303 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 304 | return content |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 305 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 306 | def use_runner_cls(command, board, args, runners_yaml, cache): |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 307 | # Get the ZephyrBinaryRunner class from its name, and make sure it |
| 308 | # supports the command. Print a message about the choice, and |
| 309 | # return the class. |
| 310 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 311 | runner = args.runner or runners_yaml.get(command.runner_key) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 312 | if runner is None: |
| 313 | log.die(f'no {command.name} runner available for board {board}. ' |
| 314 | "Check the board's documentation for instructions.") |
| 315 | |
| 316 | _banner(f'west {command.name}: using runner {runner}') |
| 317 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 318 | available = runners_yaml.get('runners', []) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 319 | if runner not in available: |
Martí Bolívar | a526877 | 2020-03-17 16:26:51 -0700 | [diff] [blame] | 320 | if 'BOARD_DIR' in cache: |
| 321 | board_cmake = Path(cache['BOARD_DIR']) / 'board.cmake' |
| 322 | else: |
| 323 | board_cmake = 'board.cmake' |
| 324 | log.err(f'board {board} does not support runner {runner}', |
| 325 | fatal=True) |
| 326 | log.inf(f'To fix, configure this runner in {board_cmake} and rebuild.') |
| 327 | sys.exit(1) |
Martí Bolívar | 847ba43 | 2021-02-23 14:00:18 -0800 | [diff] [blame] | 328 | try: |
| 329 | runner_cls = get_runner_cls(runner) |
| 330 | except ValueError as e: |
| 331 | log.die(e) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 332 | if command.name not in runner_cls.capabilities().commands: |
| 333 | log.die(f'runner {runner} does not support command {command.name}') |
| 334 | |
| 335 | return runner_cls |
| 336 | |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 337 | def get_runner_config(build_dir, yaml_path, runners_yaml, args=None): |
| 338 | # Get a RunnerConfig object for the current run. yaml_config is |
| 339 | # runners.yaml's config: map, and args are the command line arguments. |
| 340 | yaml_config = runners_yaml['config'] |
| 341 | yaml_dir = yaml_path.parent |
| 342 | if args is None: |
| 343 | args = argparse.Namespace() |
| 344 | |
| 345 | def output_file(filetype): |
| 346 | |
| 347 | from_args = getattr(args, f'{filetype}_file', None) |
| 348 | if from_args is not None: |
| 349 | return from_args |
| 350 | |
| 351 | from_yaml = yaml_config.get(f'{filetype}_file') |
| 352 | if from_yaml is not None: |
| 353 | # Output paths in runners.yaml are relative to the |
| 354 | # directory containing the runners.yaml file. |
| 355 | return fspath(yaml_dir / from_yaml) |
| 356 | |
Martí Bolívar | 3204554 | 2021-02-02 08:55:16 -0800 | [diff] [blame] | 357 | return None |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 358 | |
Gerson Fernando Budke | c3db83b | 2021-08-25 22:07:54 -0300 | [diff] [blame] | 359 | def config(attr, default=None): |
| 360 | return getattr(args, attr, None) or yaml_config.get(attr, default) |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 361 | |
| 362 | return RunnerConfig(build_dir, |
| 363 | yaml_config['board_dir'], |
| 364 | output_file('elf'), |
| 365 | output_file('hex'), |
| 366 | output_file('bin'), |
| 367 | config('gdb'), |
| 368 | config('openocd'), |
Gerson Fernando Budke | c3db83b | 2021-08-25 22:07:54 -0300 | [diff] [blame] | 369 | config('openocd_search', [])) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 370 | |
Marti Bolivar | f08935f | 2019-06-03 01:05:26 -0600 | [diff] [blame] | 371 | def dump_traceback(): |
| 372 | # Save the current exception to a file and return its path. |
| 373 | fd, name = tempfile.mkstemp(prefix='west-exc-', suffix='.txt') |
| 374 | close(fd) # traceback has no use for the fd |
| 375 | with open(name, 'w') as f: |
| 376 | traceback.print_exc(file=f) |
| 377 | log.inf("An exception trace has been saved in", name) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 378 | |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 379 | # |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 380 | # west {command} --context |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 381 | # |
| 382 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 383 | def dump_context(command, args, unknown_args): |
| 384 | build_dir = get_build_dir(args, die_if_none=False) |
| 385 | if build_dir is None: |
| 386 | log.wrn('no --build-dir given or found; output will be limited') |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 387 | runners_yaml = None |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 388 | else: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 389 | cache = load_cmake_cache(build_dir, args) |
| 390 | board = cache['CACHED_BOARD'] |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 391 | yaml_path = runners_yaml_path(build_dir, board) |
| 392 | runners_yaml = load_runners_yaml(yaml_path) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 393 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 394 | # Re-build unless asked not to, to make sure the output is up to date. |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 395 | if build_dir and not args.skip_rebuild: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 396 | rebuild(command, build_dir, args) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 397 | |
| 398 | if args.runner: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 399 | try: |
| 400 | cls = get_runner_cls(args.runner) |
| 401 | except ValueError: |
| 402 | log.die(f'invalid runner name {args.runner}; choices: ' + |
| 403 | ', '.join(cls.name() for cls in |
| 404 | ZephyrBinaryRunner.get_runners())) |
| 405 | else: |
| 406 | cls = None |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 407 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 408 | if runners_yaml is None: |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 409 | dump_context_no_config(command, cls) |
| 410 | else: |
| 411 | log.inf(f'build configuration:', colorize=True) |
| 412 | log.inf(f'{INDENT}build directory: {build_dir}') |
| 413 | log.inf(f'{INDENT}board: {board}') |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 414 | log.inf(f'{INDENT}runners.yaml: {yaml_path}') |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 415 | if cls: |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 416 | dump_runner_context(command, cls, runners_yaml) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 417 | else: |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 418 | dump_all_runner_context(command, runners_yaml, board, build_dir) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 419 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 420 | def dump_context_no_config(command, cls): |
| 421 | if not cls: |
| 422 | all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() |
| 423 | if command.name in cls.capabilities().commands} |
| 424 | log.inf('all Zephyr runners which support {}:'.format(command.name), |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 425 | colorize=True) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 426 | dump_wrapped_lines(', '.join(all_cls.keys()), INDENT) |
| 427 | log.inf() |
| 428 | log.inf('Note: use -r RUNNER to limit information to one runner.') |
| 429 | else: |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 430 | # This does the right thing with a None argument. |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 431 | dump_runner_context(command, cls, None) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 432 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 433 | def dump_runner_context(command, cls, runners_yaml, indent=''): |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 434 | dump_runner_caps(cls, indent) |
| 435 | dump_runner_option_help(cls, indent) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 436 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 437 | if runners_yaml is None: |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 438 | return |
| 439 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 440 | if cls.name() in runners_yaml['runners']: |
| 441 | dump_runner_args(cls.name(), runners_yaml, indent) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 442 | else: |
| 443 | log.wrn(f'support for runner {cls.name()} is not configured ' |
| 444 | f'in this build directory') |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 445 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 446 | def dump_runner_caps(cls, indent=''): |
| 447 | # Print RunnerCaps for the given runner class. |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 448 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 449 | log.inf(f'{indent}{cls.name()} capabilities:', colorize=True) |
| 450 | log.inf(f'{indent}{INDENT}{cls.capabilities()}') |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 451 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 452 | def dump_runner_option_help(cls, indent=''): |
| 453 | # Print help text for class-specific command line options for the |
| 454 | # given runner class. |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 455 | |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 456 | dummy_parser = argparse.ArgumentParser(prog='', add_help=False) |
| 457 | cls.add_parser(dummy_parser) |
| 458 | formatter = dummy_parser._get_formatter() |
| 459 | for group in dummy_parser._action_groups: |
| 460 | # Break the abstraction to filter out the 'flash', 'debug', etc. |
| 461 | # TODO: come up with something cleaner (may require changes |
| 462 | # in the runner core). |
| 463 | actions = group._group_actions |
| 464 | if len(actions) == 1 and actions[0].dest == 'command': |
| 465 | # This is the lone positional argument. Skip it. |
| 466 | continue |
| 467 | formatter.start_section('REMOVE ME') |
| 468 | formatter.add_text(group.description) |
| 469 | formatter.add_arguments(actions) |
| 470 | formatter.end_section() |
| 471 | # Get the runner help, with the "REMOVE ME" string gone |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 472 | runner_help = f'\n{indent}'.join(formatter.format_help().splitlines()[1:]) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 473 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 474 | log.inf(f'{indent}{cls.name()} options:', colorize=True) |
| 475 | log.inf(indent + runner_help) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 476 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 477 | def dump_runner_args(group, runners_yaml, indent=''): |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 478 | msg = f'{indent}{group} arguments from runners.yaml:' |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 479 | args = runners_yaml['args'][group] |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 480 | if args: |
| 481 | log.inf(msg, colorize=True) |
| 482 | for arg in args: |
| 483 | log.inf(f'{indent}{INDENT}{arg}') |
| 484 | else: |
| 485 | log.inf(f'{msg} (none)', colorize=True) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 486 | |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 487 | def dump_all_runner_context(command, runners_yaml, board, build_dir): |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 488 | all_cls = {cls.name(): cls for cls in ZephyrBinaryRunner.get_runners() if |
| 489 | command.name in cls.capabilities().commands} |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 490 | available = runners_yaml['runners'] |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 491 | available_cls = {r: all_cls[r] for r in available if r in all_cls} |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 492 | default_runner = runners_yaml[command.runner_key] |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 493 | yaml_path = runners_yaml_path(build_dir, board) |
| 494 | runners_yaml = load_runners_yaml(yaml_path) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 495 | |
| 496 | log.inf(f'zephyr runners which support "west {command.name}":', |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 497 | colorize=True) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 498 | dump_wrapped_lines(', '.join(all_cls.keys()), INDENT) |
| 499 | log.inf() |
| 500 | dump_wrapped_lines('Note: not all may work with this board and build ' |
| 501 | 'directory. Available runners are listed below.', |
| 502 | INDENT) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 503 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 504 | log.inf(f'available runners in runners.yaml:', |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 505 | colorize=True) |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 506 | dump_wrapped_lines(', '.join(available), INDENT) |
| 507 | log.inf(f'default runner in runners.yaml:', colorize=True) |
| 508 | log.inf(INDENT + default_runner) |
Martí Bolívar | 3124c02 | 2020-08-24 15:21:15 -0700 | [diff] [blame] | 509 | log.inf('common runner configuration:', colorize=True) |
| 510 | runner_config = get_runner_config(build_dir, yaml_path, runners_yaml) |
| 511 | for field, value in zip(runner_config._fields, runner_config): |
| 512 | log.inf(f'{INDENT}- {field}: {value}') |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 513 | log.inf('runner-specific context:', colorize=True) |
| 514 | for cls in available_cls.values(): |
Martí Bolívar | e63d299 | 2020-08-26 11:10:27 -0700 | [diff] [blame] | 515 | dump_runner_context(command, cls, runners_yaml, INDENT) |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 516 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 517 | if len(available) > 1: |
| 518 | log.inf() |
| 519 | log.inf('Note: use -r RUNNER to limit information to one runner.') |
Marti Bolivar | ab82264 | 2019-01-23 08:31:06 -0700 | [diff] [blame] | 520 | |
Martí Bolívar | eb95bed | 2020-02-10 06:40:29 -0800 | [diff] [blame] | 521 | def dump_wrapped_lines(text, indent): |
| 522 | for line in textwrap.wrap(text, initial_indent=indent, |
| 523 | subsequent_indent=indent, |
| 524 | break_on_hyphens=False, |
| 525 | break_long_words=False): |
| 526 | log.inf(line) |