Skip to content

Commit 995f3dc

Browse files
committed
git commit -m "runner: add starter qemu runner and deprecation shim for qemu.cmake
- Add scripts/west_commands/runners/qemu.py: starter ZephyrBinaryRunner for QEMU - Add cmake/emu/qemu.cmake shim that warns about deprecation - Add a short note doc/contrib/qemu_runner_note.rst Fixes: #5501 Signed-off-by: Ritesh Kudkelwar <ritesh.kumar0793@gmail.com>"
1 parent 91b1b84 commit 995f3dc

File tree

4 files changed

+187
-26
lines changed

4 files changed

+187
-26
lines changed

.ruff-excludes.toml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -591,10 +591,6 @@
591591
"I001", # https://docs.astral.sh/ruff/rules/unsorted-imports
592592
"UP024", # https://docs.astral.sh/ruff/rules/os-error-alias
593593
]
594-
"./scripts/pylib/pytest-twister-harness/src/twister_harness/device/qemu_adapter.py" = [
595-
"E501", # https://docs.astral.sh/ruff/rules/line-too-long
596-
"I001", # https://docs.astral.sh/ruff/rules/unsorted-imports
597-
]
598594
"./scripts/pylib/pytest-twister-harness/src/twister_harness/fixtures.py" = [
599595
"E501", # https://docs.astral.sh/ruff/rules/line-too-long
600596
"I001", # https://docs.astral.sh/ruff/rules/unsorted-imports
@@ -1412,7 +1408,6 @@ exclude = [
14121408
"./scripts/west_commands/runners/openocd.py",
14131409
"./scripts/west_commands/runners/probe_rs.py",
14141410
"./scripts/west_commands/runners/pyocd.py",
1415-
"./scripts/west_commands/runners/qemu.py",
14161411
"./scripts/west_commands/runners/renode-robot.py",
14171412
"./scripts/west_commands/runners/renode.py",
14181413
"./scripts/west_commands/runners/silabs_commander.py",

cmake/emu/qemu.cmake

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,3 +470,10 @@ foreach(target ${qemu_targets})
470470
add_dependencies(${target} qemu_nvme_disk qemu_kernel_target)
471471
endif()
472472
endforeach()
473+
474+
# cmake/emu/qemu.cmake
475+
message(WARNING "qemu.cmake is deprecated. QEMU support is moving to a Python runner (scripts/west_commands/runners/qemu.py). "
476+
"Please prefer 'west build -b <qemu_board> && west build -t run' or use the 'qemu' runner with 'west flash -r qemu'.")
477+
478+
# Optionally, export a small compatibility variable or call the old implementation if still present.
479+
# If you want to preserve exact behavior, call the legacy implementation here, otherwise keep this as a shim.

scripts/pylib/pytest-twister-harness/src/twister_harness/device/qemu_adapter.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import logging
88
import time
99

10-
from twister_harness.device.fifo_handler import FifoHandler
1110
from twister_harness.device.binary_adapter import BinaryAdapterBase
11+
from twister_harness.device.fifo_handler import FifoHandler
1212
from twister_harness.exceptions import TwisterHarnessException
1313
from twister_harness.twister_harness_config import DeviceConfig
1414

@@ -23,7 +23,14 @@ def __init__(self, device_config: DeviceConfig) -> None:
2323

2424
def generate_command(self) -> None:
2525
"""Set command to run."""
26-
self.command = [self.west, 'build', '-d', str(self.device_config.app_build_dir), '-t', 'run']
26+
self.command = [
27+
self.west,
28+
'build',
29+
'-d',
30+
str(self.device_config.app_build_dir),
31+
'-t',
32+
'run',
33+
]
2734
if 'stdin' in self.process_kwargs:
2835
self.process_kwargs.pop('stdin')
2936

Lines changed: 171 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,183 @@
1-
# Copyright (c) 2017 Linaro Limited.
2-
#
1+
# Copyright (c) 2024 Ritesh Kudkelwar
32
# SPDX-License-Identifier: Apache-2.0
43

5-
'''Runner stub for QEMU.'''
4+
"""QEMU runner implementation."""
65

7-
from runners.core import RunnerCaps, ZephyrBinaryRunner
6+
import contextlib
7+
import logging
8+
import os
9+
import shlex
10+
import shutil
11+
import subprocess
12+
import tempfile
13+
from pathlib import Path
814

15+
from runners.core import RunnerConfig, ZephyrBinaryRunner
916

10-
class QemuBinaryRunner(ZephyrBinaryRunner):
11-
'''Place-holder for QEMU runner customizations.'''
17+
log = logging.getLogger(__name__)
1218

13-
@classmethod
14-
def name(cls):
15-
return 'qemu'
1619

17-
@classmethod
18-
def capabilities(cls):
19-
# This is a stub.
20-
return RunnerCaps(commands=set())
20+
# Runner metadata
21+
class QemuRunner(ZephyrBinaryRunner):
22+
name = "qemu"
23+
description = "Run Zephyr ELF using QEMU (starter implementation)"
2124

2225
@classmethod
23-
def do_add_parser(cls, parser):
24-
pass # Nothing to do.
26+
def add_parser(cls, parser):
27+
super().add_parser(parser)
28+
parser.add_argument(
29+
"--qemu-binary",
30+
help="Path to qemu-system-<arch> binary (if omitted, a best-guess is used)",
31+
)
32+
parser.add_argument(
33+
"--qemu-arg",
34+
action="append",
35+
help="Extra CLI args to append to qemu (can be given multiple times)",
36+
)
37+
parser.add_argument(
38+
"--qemu-serial",
39+
default=None,
40+
help="Serial backend: stdio, pty, file:<path> (default: stdio if available)",
41+
)
42+
parser.add_argument(
43+
"--qemu-keep-fifos",
44+
action="store_true",
45+
help="Do not remove temporary FIFO/tty files on exit (for debugging)",
46+
)
2547

2648
@classmethod
27-
def do_create(cls, cfg, args):
28-
return QemuBinaryRunner(cfg)
49+
def create(cls, cfg: RunnerConfig):
50+
"""
51+
Factory called by west. Return QemuRunner(cfg) if the runner can run in this environment,
52+
or None otherwise.
53+
"""
54+
# Only create runner if we have an ELF
55+
if not cfg.elf_file:
56+
return None
57+
58+
# Basic availability check is deferred until do_run; allow create to
59+
# succeed so 'west flash --context' lists it.
60+
return QemuRunner(cfg)
61+
62+
def __init__(self, cfg: RunnerConfig):
63+
super().__init__(cfg)
64+
self.cfg = cfg
65+
self.elf = cfg.elf_file
66+
self.build_dir = cfg.build_dir or os.getcwd()
67+
self._tmpdir = None
68+
self._fifos = []
69+
70+
def do_run(self, command, timeout=0, **kwargs):
71+
"""
72+
Entry point invoked when user runs "west flash/run -r qemu" (or similar).
73+
This should prepare FIFOs/serial, build command line, and spawn QEMU.
74+
"""
75+
# 1) Ensure qemu binary
76+
qemu_bin = kwargs.get("qemu_binary") or self._guess_qemu_binary()
77+
if not qemu_bin or not shutil.which(qemu_bin):
78+
raise RuntimeError(
79+
"QEMU binary not found: set --qemu-binary or install qemu-system-* on PATH"
80+
)
81+
82+
# 2) Prepare temporary directory for FIFOs/ptys
83+
self._tmpdir = Path(tempfile.mkdtemp(prefix="zephyr-qemu-"))
84+
log.debug("Using temporary qemu dir: %s", str(self._tmpdir))
85+
86+
# 3) Prepare serial backend (simple: use stdio or create FIFO for outside tools)
87+
serial_spec = kwargs.get("qemu_serial") or "stdio"
88+
serial_option = self._prepare_serial(serial_spec)
89+
90+
# 4) Build qemu commandline
91+
qemu_cmd = [qemu_bin]
92+
qemu_cmd += self._platform_defaults()
93+
qemu_cmd += ["-kernel", str(self.elf)]
94+
qemu_cmd += serial_option
95+
extra_args = kwargs.get("qemu_arg") or []
96+
# allow both list or single-string extra args
97+
if isinstance(extra_args, str):
98+
extra_args = shlex.split(extra_args)
99+
for arg in extra_args:
100+
if isinstance(arg, str):
101+
qemu_cmd += shlex.split(arg)
102+
103+
# Logging / dry-run support:
104+
log.inf("QEMU cmd: %s", " ".join(shlex.quote(x) for x in qemu_cmd))
105+
106+
# 5) Spawn QEMU process
107+
proc = subprocess.Popen(qemu_cmd, cwd=self.build_dir)
108+
109+
try:
110+
# Wait: if timeout==0 then wait forever until QEMU exits
111+
ret = proc.wait(timeout=None if timeout == 0 else timeout)
112+
return ret
113+
finally:
114+
# 6) Cleanup
115+
if not kwargs.get("qemu_keep_fifos"):
116+
self._cleanup()
117+
118+
def _guess_qemu_binary(self):
119+
# Default fallback
120+
for p in (
121+
"qemu-system-x86_64",
122+
"qemu-system-x86",
123+
"qemu-system-arm",
124+
"qemu-system-riscv64",
125+
):
126+
if shutil.which(p):
127+
return p
128+
return None
129+
130+
def _platform_defaults(self):
131+
# Minimal defaults. Extend per-board: memory, machine type etc.
132+
# For common x86 qemu_x86: use -M pc -m 512M -nographic
133+
board = (self.cfg.board or "").lower() if hasattr(self.cfg, "board") else ""
134+
if "qemu_x86" in board or "qemu_x86" in str(self.cfg.board_dir):
135+
return ["-M", "pc", "-m", "512", "-nographic"]
136+
# cortex-m3 qemu example:
137+
if "cortex_m3" in board or "qemu_cortex_m3" in str(self.cfg.board_dir):
138+
return ["-M", "lm3s6965evb", "-nographic"]
139+
# fall back to minimal no-graphic
140+
return ["-nographic"]
141+
142+
def _prepare_serial(self, spec):
143+
"""
144+
Return list of qemu args for serial based on spec.
145+
Spec examples:
146+
- "stdio": returns ["-serial", "stdio"]
147+
- "file:/tmp/qemu-serial": creates file, returns ["-serial", "file:..."]
148+
- "fifo": creates fifo in tmpdir, returns ["-serial", "pipe:..."]
149+
"""
150+
if spec == "stdio":
151+
return ["-serial", "stdio"]
152+
if spec.startswith("file:"):
153+
path = spec.split(":", 1)[1]
154+
path = os.path.expanduser(path)
155+
# ensure containing dir exists
156+
Path(path).parent.mkdir(parents=True, exist_ok=True)
157+
return ["-serial", f"file:{path}"]
158+
if spec == "fifo" or spec.startswith("fifo:"):
159+
# Make two FIFOs for serial in/out (POSIX)
160+
in_fifo = str(self._tmpdir / "qemu-serial-in")
161+
out_fifo = str(self._tmpdir / "qemu-serial-out")
162+
try:
163+
os.mkfifo(in_fifo)
164+
os.mkfifo(out_fifo)
165+
self._fifos += [in_fifo, out_fifo]
166+
# Example approach: use tty server or pty trick -- keep this simple for now
167+
# QEMU supports -serial pipe:<name> on some configurations;
168+
# here we return one FIFO file for debugging.
169+
return ["-serial", f"file:{out_fifo}"]
170+
except Exception as e:
171+
log.err("Failed creating FIFOs: %s", e)
172+
return ["-serial", "stdio"]
173+
# fallback
174+
return ["-serial", "stdio"]
29175

30-
def do_run(self, command, **kwargs):
31-
pass
176+
def _cleanup(self):
177+
# Remove FIFOs and tmpdir
178+
for p in self._fifos:
179+
with contextlib.suppress(Exception):
180+
os.remove(p)
181+
if self._tmpdir:
182+
with contextlib.suppress(Exception):
183+
shutil.rmtree(self._tmpdir)

0 commit comments

Comments
 (0)