-
Notifications
You must be signed in to change notification settings - Fork 8.3k
runners: add and improve QEMU runner (Python-based replacement for qemu.cmake) #99170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ritesh006
wants to merge
1
commit into
zephyrproject-rtos:main
Choose a base branch
from
ritesh006:feat/qemu-runner-starter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+201
−40
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,31 +1,183 @@ | ||
| # Copyright (c) 2017 Linaro Limited. | ||
| # | ||
| # Copyright (c) 2024 Ritesh Kudkelwar | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| '''Runner stub for QEMU.''' | ||
| """QEMU runner implementation.""" | ||
|
|
||
| from runners.core import RunnerCaps, ZephyrBinaryRunner | ||
| import contextlib | ||
| import logging | ||
| import os | ||
| import shlex | ||
| import shutil | ||
| import subprocess | ||
| import tempfile | ||
| from pathlib import Path | ||
|
|
||
| from runners.core import RunnerConfig, ZephyrBinaryRunner | ||
|
|
||
| class QemuBinaryRunner(ZephyrBinaryRunner): | ||
| '''Place-holder for QEMU runner customizations.''' | ||
| log = logging.getLogger(__name__) | ||
|
|
||
| @classmethod | ||
| def name(cls): | ||
| return 'qemu' | ||
|
|
||
| @classmethod | ||
| def capabilities(cls): | ||
| # This is a stub. | ||
| return RunnerCaps(commands=set()) | ||
| # Runner metadata | ||
| class QemuRunner(ZephyrBinaryRunner): | ||
| name = "qemu" | ||
| description = "Run Zephyr ELF using QEMU (starter implementation)" | ||
|
|
||
| @classmethod | ||
| def do_add_parser(cls, parser): | ||
| pass # Nothing to do. | ||
| def add_parser(cls, parser): | ||
| super().add_parser(parser) | ||
| parser.add_argument( | ||
| "--qemu-binary", | ||
| help="Path to qemu-system-<arch> binary (if omitted, a best-guess is used)", | ||
| ) | ||
| parser.add_argument( | ||
| "--qemu-arg", | ||
| action="append", | ||
| help="Extra CLI args to append to qemu (can be given multiple times)", | ||
| ) | ||
| parser.add_argument( | ||
| "--qemu-serial", | ||
| default=None, | ||
| help="Serial backend: stdio, pty, file:<path> (default: stdio if available)", | ||
| ) | ||
| parser.add_argument( | ||
| "--qemu-keep-fifos", | ||
| action="store_true", | ||
| help="Do not remove temporary FIFO/tty files on exit (for debugging)", | ||
| ) | ||
|
|
||
| @classmethod | ||
| def do_create(cls, cfg, args): | ||
| return QemuBinaryRunner(cfg) | ||
| def create(cls, cfg: RunnerConfig): | ||
| """ | ||
| Factory called by west. Return QemuRunner(cfg) if the runner can run in this environment, | ||
| or None otherwise. | ||
| """ | ||
| # Only create runner if we have an ELF | ||
| if not cfg.elf_file: | ||
| return None | ||
|
|
||
| # Basic availability check is deferred until do_run; allow create to | ||
| # succeed so 'west flash --context' lists it. | ||
| return QemuRunner(cfg) | ||
|
|
||
| def __init__(self, cfg: RunnerConfig): | ||
| super().__init__(cfg) | ||
| self.cfg = cfg | ||
| self.elf = cfg.elf_file | ||
| self.build_dir = cfg.build_dir or os.getcwd() | ||
| self._tmpdir = None | ||
| self._fifos = [] | ||
|
|
||
| def do_run(self, command, timeout=0, **kwargs): | ||
| """ | ||
| Entry point invoked when user runs "west flash/run -r qemu" (or similar). | ||
| This should prepare FIFOs/serial, build command line, and spawn QEMU. | ||
| """ | ||
| # 1) Ensure qemu binary | ||
| qemu_bin = kwargs.get("qemu_binary") or self._guess_qemu_binary() | ||
| if not qemu_bin or not shutil.which(qemu_bin): | ||
| raise RuntimeError( | ||
| "QEMU binary not found: set --qemu-binary or install qemu-system-* on PATH" | ||
| ) | ||
|
|
||
| # 2) Prepare temporary directory for FIFOs/ptys | ||
| self._tmpdir = Path(tempfile.mkdtemp(prefix="zephyr-qemu-")) | ||
| log.debug("Using temporary qemu dir: %s", str(self._tmpdir)) | ||
|
|
||
| # 3) Prepare serial backend (simple: use stdio or create FIFO for outside tools) | ||
| serial_spec = kwargs.get("qemu_serial") or "stdio" | ||
| serial_option = self._prepare_serial(serial_spec) | ||
|
|
||
| # 4) Build qemu commandline | ||
| qemu_cmd = [qemu_bin] | ||
| qemu_cmd += self._platform_defaults() | ||
| qemu_cmd += ["-kernel", str(self.elf)] | ||
| qemu_cmd += serial_option | ||
| extra_args = kwargs.get("qemu_arg") or [] | ||
| # allow both list or single-string extra args | ||
| if isinstance(extra_args, str): | ||
| extra_args = shlex.split(extra_args) | ||
| for arg in extra_args: | ||
| if isinstance(arg, str): | ||
| qemu_cmd += shlex.split(arg) | ||
|
|
||
| # Logging / dry-run support: | ||
| log.inf("QEMU cmd: %s", " ".join(shlex.quote(x) for x in qemu_cmd)) | ||
|
|
||
| # 5) Spawn QEMU process | ||
| proc = subprocess.Popen(qemu_cmd, cwd=self.build_dir) | ||
|
|
||
| try: | ||
| # Wait: if timeout==0 then wait forever until QEMU exits | ||
| ret = proc.wait(timeout=None if timeout == 0 else timeout) | ||
| return ret | ||
| finally: | ||
| # 6) Cleanup | ||
| if not kwargs.get("qemu_keep_fifos"): | ||
| self._cleanup() | ||
|
|
||
| def _guess_qemu_binary(self): | ||
| # Default fallback | ||
| for p in ( | ||
| "qemu-system-x86_64", | ||
| "qemu-system-x86", | ||
| "qemu-system-arm", | ||
| "qemu-system-riscv64", | ||
| ): | ||
| if shutil.which(p): | ||
| return p | ||
| return None | ||
|
|
||
| def _platform_defaults(self): | ||
| # Minimal defaults. Extend per-board: memory, machine type etc. | ||
| # For common x86 qemu_x86: use -M pc -m 512M -nographic | ||
| board = (self.cfg.board or "").lower() if hasattr(self.cfg, "board") else "" | ||
| if "qemu_x86" in board or "qemu_x86" in str(self.cfg.board_dir): | ||
| return ["-M", "pc", "-m", "512", "-nographic"] | ||
| # cortex-m3 qemu example: | ||
| if "cortex_m3" in board or "qemu_cortex_m3" in str(self.cfg.board_dir): | ||
| return ["-M", "lm3s6965evb", "-nographic"] | ||
| # fall back to minimal no-graphic | ||
| return ["-nographic"] | ||
|
|
||
| def _prepare_serial(self, spec): | ||
| """ | ||
| Return list of qemu args for serial based on spec. | ||
| Spec examples: | ||
| - "stdio": returns ["-serial", "stdio"] | ||
| - "file:/tmp/qemu-serial": creates file, returns ["-serial", "file:..."] | ||
| - "fifo": creates fifo in tmpdir, returns ["-serial", "pipe:..."] | ||
| """ | ||
| if spec == "stdio": | ||
| return ["-serial", "stdio"] | ||
| if spec.startswith("file:"): | ||
| path = spec.split(":", 1)[1] | ||
| path = os.path.expanduser(path) | ||
| # ensure containing dir exists | ||
| Path(path).parent.mkdir(parents=True, exist_ok=True) | ||
| return ["-serial", f"file:{path}"] | ||
| if spec == "fifo" or spec.startswith("fifo:"): | ||
| # Make two FIFOs for serial in/out (POSIX) | ||
| in_fifo = str(self._tmpdir / "qemu-serial-in") | ||
| out_fifo = str(self._tmpdir / "qemu-serial-out") | ||
| try: | ||
| os.mkfifo(in_fifo) | ||
| os.mkfifo(out_fifo) | ||
| self._fifos += [in_fifo, out_fifo] | ||
| # Example approach: use tty server or pty trick -- keep this simple for now | ||
| # QEMU supports -serial pipe:<name> on some configurations; | ||
| # here we return one FIFO file for debugging. | ||
| return ["-serial", f"file:{out_fifo}"] | ||
| except Exception as e: | ||
| log.err("Failed creating FIFOs: %s", e) | ||
| return ["-serial", "stdio"] | ||
| # fallback | ||
| return ["-serial", "stdio"] | ||
|
|
||
| def do_run(self, command, **kwargs): | ||
| pass | ||
| def _cleanup(self): | ||
| # Remove FIFOs and tmpdir | ||
| for p in self._fifos: | ||
| with contextlib.suppress(Exception): | ||
| os.remove(p) | ||
| if self._tmpdir: | ||
| with contextlib.suppress(Exception): | ||
| shutil.rmtree(self._tmpdir) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these changes are not valid with this alignment