From b50519e3c9b8ae3eabb52a3edbd7f82a6ef1df5f Mon Sep 17 00:00:00 2001 From: Carles Cufi Date: Tue, 28 Oct 2025 13:43:05 +0100 Subject: [PATCH 1/3] [nrf fromtree] scripts: runners: Generalize the --dry-run option Move the argparse.add_argument() call to the abstract base class and augment the RunnerCaps class with a dry_run parameter. Signed-off-by: Carles Cufi (cherry picked from commit 48e04817463f827609e694fb1472dc6af326d252) --- scripts/west_commands/runners/core.py | 7 +++++++ scripts/west_commands/runners/nrf_common.py | 4 ++-- scripts/west_commands/runners/nrfutil.py | 6 +----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/west_commands/runners/core.py b/scripts/west_commands/runners/core.py index 611848f671cd..0efd4c3fb163 100644 --- a/scripts/west_commands/runners/core.py +++ b/scripts/west_commands/runners/core.py @@ -318,6 +318,7 @@ class RunnerCaps: hide_load_files: bool = False rtt: bool = False # This capability exists separately from the rtt command # to allow other commands to use the rtt address + dry_run: bool = False def __post_init__(self): if self.mult_dev_ids and not self.dev_id: @@ -639,6 +640,10 @@ def add_parser(cls, parser): else: parser.add_argument('--rtt-address', help=argparse.SUPPRESS) + parser.add_argument('--dry-run', action='store_true', + help=('''Print all the commands without actually + executing them''' if caps.dry_run else argparse.SUPPRESS)) + # Runner-specific options. cls.do_add_parser(parser) @@ -685,6 +690,8 @@ def create(cls, cfg: RunnerConfig, _missing_cap(cls, '--file-type') if args.rtt_address and not caps.rtt: _missing_cap(cls, '--rtt-address') + if args.dry_run and not caps.dry_run: + _missing_cap(cls, '--dry-run') ret = cls.do_create(cfg, args) if args.erase: diff --git a/scripts/west_commands/runners/nrf_common.py b/scripts/west_commands/runners/nrf_common.py index 75ad66aed99f..5cdca1e6b3f6 100644 --- a/scripts/west_commands/runners/nrf_common.py +++ b/scripts/west_commands/runners/nrf_common.py @@ -75,10 +75,10 @@ def __init__(self, cfg, family, softreset, pinreset, dev_id, erase=False, self.tool_opt += opts @classmethod - def _capabilities(cls, mult_dev_ids=False): + def _capabilities(cls, mult_dev_ids=False, dry_run=False): return RunnerCaps(commands={'flash'}, dev_id=True, mult_dev_ids=mult_dev_ids, erase=True, reset=True, - tool_opt=True) + tool_opt=True, dry_run=dry_run) @classmethod def _dev_id_help(cls) -> str: diff --git a/scripts/west_commands/runners/nrfutil.py b/scripts/west_commands/runners/nrfutil.py index 9deb32e06a02..f5be3ac8eb01 100644 --- a/scripts/west_commands/runners/nrfutil.py +++ b/scripts/west_commands/runners/nrfutil.py @@ -37,7 +37,7 @@ def name(cls): @classmethod def capabilities(cls): - return NrfBinaryRunner._capabilities(mult_dev_ids=True) + return NrfBinaryRunner._capabilities(mult_dev_ids=True, dry_run=True) @classmethod def dev_id_help(cls) -> str: @@ -65,10 +65,6 @@ def do_add_parser(cls, parser): parser.add_argument('--ext-mem-config-file', required=False, dest='ext_mem_config_file', help='path to an JSON file with external memory configuration') - parser.add_argument('--dry-run', required=False, - action='store_true', - help='''Generate all the commands without actually - executing them''') def _exec(self, args, force=False): jout_all = [] From 452ca0610dd88524917d46af4800dccca83e7463 Mon Sep 17 00:00:00 2001 From: Carles Cufi Date: Tue, 28 Oct 2025 19:35:31 +0100 Subject: [PATCH 2/3] [nrf fromtree] scripts: runners: Enable reusing the core dry run logic In order to avoid duplication of logic in runners, allow subclasses of ZephyrBinaryRunner to set self.dry_run so that they can then reuse the logic in core.py to log instead of execute. Signed-off-by: Carles Cufi (cherry picked from commit ffe2028dcf6aa2ec5f58c4dc6ba084f1932529c9) --- scripts/west_commands/runners/core.py | 13 ++++++++----- scripts/west_commands/runners/nrfutil.py | 12 +++--------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/scripts/west_commands/runners/core.py b/scripts/west_commands/runners/core.py index 0efd4c3fb163..a47f76f1e9e0 100644 --- a/scripts/west_commands/runners/core.py +++ b/scripts/west_commands/runners/core.py @@ -493,6 +493,9 @@ def __init__(self, cfg: RunnerConfig): self.logger = logging.getLogger(f'runners.{self.name()}') '''logging.Logger for this instance.''' + self.dry_run = _DRY_RUN + '''log commands instead of executing them. Can be set by subclasses''' + @staticmethod def get_runners() -> list[type['ZephyrBinaryRunner']]: '''Get a list of all currently defined runner classes.''' @@ -867,7 +870,7 @@ def run_client(self, client, **kwargs): def _log_cmd(self, cmd: list[str]): escaped = ' '.join(shlex.quote(s) for s in cmd) - if not _DRY_RUN: + if not self.dry_run: self.logger.debug(escaped) else: self.logger.info(escaped) @@ -880,7 +883,7 @@ def call(self, cmd: list[str], **kwargs) -> int: using subprocess directly, to keep accurate debug logs. ''' self._log_cmd(cmd) - if _DRY_RUN: + if self.dry_run: return 0 return subprocess.call(cmd, **kwargs) @@ -892,7 +895,7 @@ def check_call(self, cmd: list[str], **kwargs): using subprocess directly, to keep accurate debug logs. ''' self._log_cmd(cmd) - if _DRY_RUN: + if self.dry_run: return subprocess.check_call(cmd, **kwargs) @@ -904,7 +907,7 @@ def check_output(self, cmd: list[str], **kwargs) -> bytes: using subprocess directly, to keep accurate debug logs. ''' self._log_cmd(cmd) - if _DRY_RUN: + if self.dry_run: return b'' return subprocess.check_output(cmd, **kwargs) @@ -925,7 +928,7 @@ def popen_ignore_int(self, cmd: list[str], **kwargs) -> subprocess.Popen: preexec = os.setsid # type: ignore self._log_cmd(cmd) - if _DRY_RUN: + if self.dry_run: return _DebugDummyPopen() # type: ignore return subprocess.Popen(cmd, creationflags=cflags, preexec_fn=preexec, **kwargs) diff --git a/scripts/west_commands/runners/nrfutil.py b/scripts/west_commands/runners/nrfutil.py index f5be3ac8eb01..5770cf86fae4 100644 --- a/scripts/west_commands/runners/nrfutil.py +++ b/scripts/west_commands/runners/nrfutil.py @@ -5,12 +5,10 @@ '''Runner for flashing with nrfutil.''' import json -import shlex import subprocess import sys from pathlib import Path -from runners.core import _DRY_RUN from runners.nrf_common import NrfBinaryRunner @@ -70,14 +68,10 @@ def _exec(self, args, force=False): jout_all = [] cmd = ['nrfutil', '--json', 'device'] + args + self._log_cmd(cmd) - escaped = ' '.join(shlex.quote(s) for s in cmd) - if _DRY_RUN or (self.dry_run): - self.logger.info(escaped) - if not force: - return {} - else: - self.logger.debug(escaped) + if self.dry_run and not force: + return {} with subprocess.Popen(cmd, stdout=subprocess.PIPE) as p: for line in iter(p.stdout.readline, b''): From d10fdabeee5e2ec1e8cf28023e51bcb9eebfdd46 Mon Sep 17 00:00:00 2001 From: Carles Cufi Date: Fri, 21 Nov 2025 10:29:04 +0100 Subject: [PATCH 3/3] [nrf fromtree] scripts: west: nrfutil: Use build dir to store generated json Until now the code was using the path to the .hex file to select the directory where the generated JSON file required for nrfutil would be generated. But this has a problem: if the .hex file in a sysbuild project is located in the tree as a precompiled binary, the runner would place a file in there and make the tree dirty. Instead use the build_dir folder which always corresponds (in both sysbuild and regular builds) to the current target build directory. Signed-off-by: Carles Cufi (cherry picked from commit d6f5aa925832800204d3e0fe0015a924de5323f6) --- scripts/west_commands/runners/nrfutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/west_commands/runners/nrfutil.py b/scripts/west_commands/runners/nrfutil.py index 5770cf86fae4..051a686f5a1a 100644 --- a/scripts/west_commands/runners/nrfutil.py +++ b/scripts/west_commands/runners/nrfutil.py @@ -152,7 +152,7 @@ def _append_batch(self, op, json_file): def _exec_batch(self): # Use x-append-batch to get the JSON from nrfutil itself - json_file = Path(self.hex_).parent / 'generated_nrfutil_batch.json' + json_file = Path(self.cfg.build_dir) / 'zephyr' / 'generated_nrfutil_batch.json' json_file.unlink(missing_ok=True) for op in self._ops: self._append_batch(op, json_file)