1
0
mirror of https://git.yoctoproject.org/meta-arm synced 2026-05-07 04:58:57 +00:00

scripts/runfvp: check available terminal types

Improve usability by detecting which terminal types are available on the
system.

Extend the terminal abstraction to support checking whether a terminal
can be executed, and add basic validation for all terminal types. Update
the documentation to reflect the new behavior.

Signed-off-by: Gyorgy Szing <gyorgy.szing@arm.com>
Signed-off-by: Jon Mason <jon.mason@arm.com>
This commit is contained in:
Gyorgy Szing
2026-04-23 17:37:19 +02:00
committed by Jon Mason
parent 9d1070b435
commit 2b0fbab119
3 changed files with 96 additions and 12 deletions
+5
View File
@@ -28,6 +28,11 @@ Note that currently meta-arm's `scripts` directory isn't in `PATH`, so a full pa
`runfvp` will automatically start terminals connected to each of the serial ports that the machine specifies. This can be controlled by using the `--terminals` option, for example `--terminals=none` will mean no terminals are started, and `--terminals=tmux` will start the terminals in [`tmux`][tmux] sessions. Alternatively, passing `--console` will connect the serial port directly to the current session, without needing to open further windows.
The tool attempts to automatically select a suitable terminal type. To see which terminal type is selected by default in your environment, run `runfvp --help`.
`runfvp` determines availability by checking for required executables in your PATH as well as environment variables specific to each terminal type. If any of these checks fail, the corresponding terminal type is disabled.
The --help output also lists all currently available terminal types.
The default terminal can also be configured by writing a [INI-style][INI] configuration file to `~/.config/runfvp.conf`:
```
+84 -11
View File
@@ -3,9 +3,14 @@ import collections
import pathlib
import os
import logging
import configparser
from typing import List, Optional
logger = logging.getLogger("Terminal")
def get_config_dir() -> pathlib.Path:
value = os.environ.get("XDG_CONFIG_HOME")
if value and os.path.isabs(value):
@@ -13,47 +18,115 @@ def get_config_dir() -> pathlib.Path:
else:
return pathlib.Path.home() / ".config"
def check_executable(*cmd) -> bool:
import subprocess
try:
result = subprocess.run(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
exitcode = result.returncode
except FileNotFoundError:
exitcode = 127
return exitcode == 0
def tmux_is_ready(*, silent: bool = False) -> bool:
log_print = (lambda *_args, **_kwargs: None) if silent else logger.error
if not check_executable("tmux", "-V"):
log_print("--terminal tmux requires tmux to be available and runnable, but startup failed.")
return False
return True
def is_display_available(log_print, terminal_name: str) -> bool:
if "DISPLAY" not in os.environ and "WAYLAND_DISPLAY" not in os.environ:
log_print(f"--terminal {terminal_name} requires a graphical display"
" but nor DISPLAY nor WAYLAND_DISPLAY is set.")
return False
return True
def gterm_is_ready(*, silent: bool = False) -> bool:
log_print = (lambda *_args, **_kwargs: None) if silent else logger.error
if not is_display_available(log_print, "gnome-terminal"):
return False
if not check_executable("gnome-terminal", "--version"):
log_print("--terminal gnome-terminal requires gnome-terminal to be available and runnable, but startup failed.")
return False
return True
def xterm_is_ready(*, silent: bool = False) -> bool:
log_print = (lambda *_args, **_kwargs: None) if silent else logger.error
if not is_display_available(log_print, "xterm"):
return False
if not check_executable("xterm", "-version"):
log_print("--terminal xterm requires xterm to be available and runnable, but startup failed.")
return False
return True
class Terminals:
Terminal = collections.namedtuple("Terminal", ["priority", "name", "command"])
Terminal = collections.namedtuple("Terminal", ["priority", "name", "command", "is_ready"])
def __init__(self):
self.terminals = []
def add_terminal(self, priority, name, command):
self.terminals.append(Terminals.Terminal(priority, name, command))
def always_ready(self, *, silent: bool = False) -> bool:
return True
def add_terminal(self, priority, name, command, is_ready=None):
if is_ready is None:
is_ready = self.always_ready
self.terminals.append(Terminals.Terminal(priority, name, command, is_ready))
# Keep this list sorted by priority
self.terminals.sort(reverse=True, key=lambda t: t.priority)
self.name_map = {t.name: t for t in self.terminals}
def configured_terminal(self) -> Optional[str]:
import configparser
config = configparser.ConfigParser()
config.read(get_config_dir() / "runfvp.conf")
return config.get("RunFVP", "Terminal", fallback=None)
def preferred_terminal(self) -> str:
import shlex
preferred = self.configured_terminal()
if preferred:
return preferred
for t in self.terminals:
if t.command and shutil.which(shlex.split(t.command)[0]):
if t.command and t.is_ready(silent=True):
return t.name
return self.terminals[-1].name
def all_terminals(self) -> List[str]:
return self.name_map.keys()
def available_terminals(self) -> List[str]:
return [t for t in self.name_map if self.name_map[t].is_ready(silent=True)]
def __getitem__(self, name: str):
return self.name_map[name]
terminals = Terminals()
# TODO: option to switch between telnet and netcat
connect_command = "telnet localhost %port"
terminals.add_terminal(2, "tmux", f"tmux new-window -n \"{{name}}\" \"{connect_command}\"")
terminals.add_terminal(2, "gnome-terminal", f"gnome-terminal --window --title \"{{name}} - %title\" --command \"{connect_command}\"")
terminals.add_terminal(1, "xterm", f"xterm -title \"{{name}} - %title\" -e {connect_command}")
terminals.add_terminal(2, "tmux", f'tmux new-window -n "{{name}}" "{connect_command}"', tmux_is_ready)
terminals.add_terminal(2, "gnome-terminal", f'gnome-terminal --window --title "{{name}} - %title" --command "{connect_command}"', gterm_is_ready)
terminals.add_terminal(1, "xterm", f'xterm -title "{{name}} - %title" -e {connect_command}', xterm_is_ready)
terminals.add_terminal(0, "none", None)
+7 -1
View File
@@ -23,7 +23,8 @@ def parse_args(arguments):
parser = argparse.ArgumentParser(description="Run images in a FVP")
parser.add_argument("config", nargs="?", help="Machine name or path to .fvpconf file")
group = parser.add_mutually_exclusive_group()
group.add_argument("-t", "--terminals", choices=terminals.all_terminals(), default=terminals.preferred_terminal(), help="Automatically start terminals (default: %(default)s)")
available_terminals=",".join(terminals.available_terminals())
group.add_argument("-t", "--terminals", choices=terminals.all_terminals(), default=terminals.preferred_terminal(), help=f"Automatically start terminals (default: %(default)s). Available terminals are ({available_terminals})")
group.add_argument("-c", "--console", action="store_true", help="Attach the first uart to stdin/stdout")
parser.add_argument("--verbose", action="store_true", help="Output verbose logging")
parser.usage = f"{parser.format_usage().strip()} -- [ arguments passed to FVP ]"
@@ -52,6 +53,11 @@ def parse_args(arguments):
def start_fvp(args, fvpconf, extra_args):
fvp = runner.FVPRunner(logger)
try:
if args.terminals:
if not terminal.terminals[args.terminals].is_ready():
return 1
fvp.start(fvpconf, extra_args, args.terminals)
if args.console: