Add tmux pane target for container commands

This commit is contained in:
mpabi
2026-05-01 23:09:31 +02:00
parent 0505b829a1
commit a8d1316717
4 changed files with 114 additions and 3 deletions
+11
View File
@@ -460,6 +460,17 @@ Dla pobranej karty `inf bss` możesz uruchomić debugowanie w obu profilach:
./rvctl debug host 4 ./rvctl debug host 4
``` ```
Jeżeli pracujesz już w tmuksie na hoście, możesz wysłać start kontenera do
konkretnego panelu:
```bash
./rvctl debug rv32i 4 pane 0
./rvctl debug rv32i 4 --pane node:0
```
`pane 0` oznacza panel `0` w bieżącym oknie tmuxa. `node:0` oznacza okno
`node`, panel `0`.
Profil `host` ma też szybkie uruchomienie bez sesji debuggera: Profil `host` ma też szybkie uruchomienie bez sesji debuggera:
```bash ```bash
+9
View File
@@ -109,9 +109,18 @@ Przydatne warianty:
./rvctl debug host bss 4 ./rvctl debug host bss 4
./rvctl debug host 4 --editor vim ./rvctl debug host 4 --editor vim
./rvctl debug host 4 --instance host-bss-t4 ./rvctl debug host 4 --instance host-bss-t4
./rvctl debug rv32i 4 --pane 0
./rvctl debug rv32i 4 pane node:0
./rvctl debug host 4 --dry-run ./rvctl debug host 4 --dry-run
``` ```
`--pane TARGET` oraz forma `pane TARGET` wysyłają wygenerowaną komendę do
istniejącego panelu tmuxa na hoście przez `tmux send-keys`. Skróty targetów:
- `0` - panel `0` w bieżącym oknie,
- `node:0` - okno `node`, panel `0`,
- `%3` albo `:node.0` - natywny target tmuxa.
## `run` ## `run`
Na razie `run` jest wdrożone dla profilu `host`. Buduje wybrany task natywnie i Na razie `run` jest wdrożone dla profilu `host`. Buduje wybrany task natywnie i
+10
View File
@@ -667,6 +667,8 @@ Uruchamia debugowanie zadania w kontenerze. Selektor może być pełny albo kró
./rvctl debug host inf bss 1 ./rvctl debug host inf bss 1
./rvctl debug rv32i 4 ./rvctl debug rv32i 4
./rvctl debug host 4 ./rvctl debug host 4
./rvctl debug rv32i 4 --pane 0
./rvctl debug rv32i 4 pane node:0
``` ```
`rv32i` uruchamia symulator Hazard3, `gdb-multiarch` i `nvim`. `host` kompiluje `rv32i` uruchamia symulator Hazard3, `gdb-multiarch` i `nvim`. `host` kompiluje
@@ -679,8 +681,14 @@ Przełączniki:
- `--task TASK` - nadpisuje zadanie z selektora albo z `workspace.json` - `--task TASK` - nadpisuje zadanie z selektora albo z `workspace.json`
- `--instance NAME` - ustawia nazwę instancji kontenera/tmuxa - `--instance NAME` - ustawia nazwę instancji kontenera/tmuxa
- `--editor nvim|vim` - wybiera edytor w kontenerze - `--editor nvim|vim` - wybiera edytor w kontenerze
- `--pane TARGET` - wysyła wygenerowaną komendę do istniejącego panelu tmuxa
na hoście
- `--dry-run` - pokazuje komendę bez uruchamiania Dockera - `--dry-run` - pokazuje komendę bez uruchamiania Dockera
`--pane 0` oznacza panel `0` w bieżącym oknie tmuxa. `pane node:0` oznacza
okno `node`, panel `0`. Natywne targety tmuxa, na przykład `%3` albo
`:node.0`, też są obsługiwane.
## `run host` ## `run host`
Buduje i uruchamia wybrane zadanie natywnie w kontenerze `host`. Buduje i uruchamia wybrane zadanie natywnie w kontenerze `host`.
@@ -688,6 +696,7 @@ Buduje i uruchamia wybrane zadanie natywnie w kontenerze `host`.
```bash ```bash
./rvctl run host inf bss 1 ./rvctl run host inf bss 1
./rvctl run host 4 ./rvctl run host 4
./rvctl run host 4 --pane node:0
``` ```
Profil `rv32i` pracuje obecnie przez `debug rv32i`, bo wymaga symulatora Profil `rv32i` pracuje obecnie przez `debug rv32i`, bo wymaga symulatora
@@ -701,6 +710,7 @@ Otwiera shell w wybranym profilu kontenera z podmontowaną kartą.
./rvctl shell rv32i inf bss ./rvctl shell rv32i inf bss
./rvctl shell host inf bss ./rvctl shell host inf bss
./rvctl shell host --dry-run ./rvctl shell host --dry-run
./rvctl shell rv32i --pane 0
``` ```
## `list-series` ## `list-series`
+84 -3
View File
@@ -3533,6 +3533,60 @@ def resolve_env_target(
return series_name, card_name, task_name return series_name, card_name, task_name
def split_pane_selector(values: list[str], explicit_pane: str | None = None) -> tuple[list[str], str | None]:
if "pane" not in values:
return values, explicit_pane
if explicit_pane:
raise SystemExit("Pass pane target either as '--pane TARGET' or as 'pane TARGET', not both.")
index = values.index("pane")
pane_values = values[index + 1 :]
if len(pane_values) != 1:
raise SystemExit("Use: pane TARGET, for example 'pane 0' or 'pane node:0'.")
return values[:index], pane_values[0]
def tmux_pane_target(raw_target: str) -> str:
target = raw_target.strip()
if not target:
raise SystemExit("Empty tmux pane target.")
if re.fullmatch(r"\d+", target):
return f":.{target}"
if re.fullmatch(r"%\d+", target):
return target
window_pane_match = re.fullmatch(r"([^:.]+):(\d+)", target)
if window_pane_match:
window_name, pane_index = window_pane_match.groups()
return f":{window_name}.{pane_index}"
return target
def send_command_to_tmux_pane(shell_command: str, raw_target: str, dry_run: bool = False) -> dict[str, str]:
target = tmux_pane_target(raw_target)
details = {
"pane": raw_target,
"tmux_target": target,
"tmux_send": f"tmux send-keys -t {shlex.quote(target)} -l {shlex.quote(shell_command)}; tmux send-keys -t {shlex.quote(target)} C-m",
}
if dry_run:
return details
resolved = subprocess.run(
["tmux", "display-message", "-p", "-t", target, "#{session_name}:#{window_name}.#{pane_index}"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=False,
)
if resolved.returncode != 0:
raise SystemExit(f"Missing tmux pane target '{raw_target}' ({target}): {resolved.stderr.strip()}")
subprocess.run(["tmux", "send-keys", "-t", target, "-l", shell_command], check=True)
subprocess.run(["tmux", "send-keys", "-t", target, "C-m"], check=True)
details["tmux_resolved"] = resolved.stdout.strip()
return details
def default_env_instance(profile: str, series_name: str, card_name: str, task_name: str, action: str) -> str: def default_env_instance(profile: str, series_name: str, card_name: str, task_name: str, action: str) -> str:
task_part = task_branch_part(task_name).lower() task_part = task_branch_part(task_name).lower()
return slug_value(f"{profile}-{series_name}-{card_name}-{task_part}-{action}", "environment instance") return slug_value(f"{profile}-{series_name}-{card_name}-{task_part}-{action}", "environment instance")
@@ -3619,8 +3673,23 @@ def env_action_command(
def print_env_plan(details: dict[str, str]) -> None: def print_env_plan(details: dict[str, str]) -> None:
for key in ["profile", "service", "selector", "task", "card_path", "tools_root", "instance", "command"]: keys = [
print(f"{key}\t{details.get(key, '')}") "profile",
"service",
"selector",
"task",
"card_path",
"tools_root",
"instance",
"pane",
"tmux_target",
"tmux_resolved",
"tmux_send",
"command",
]
for key in keys:
if key == "command" or details.get(key):
print(f"{key}\t{details.get(key, '')}")
def run_env(config: WorkspaceConfig, args: argparse.Namespace) -> None: def run_env(config: WorkspaceConfig, args: argparse.Namespace) -> None:
@@ -3650,7 +3719,8 @@ def run_env(config: WorkspaceConfig, args: argparse.Namespace) -> None:
def run_profile_action(config: WorkspaceConfig, args: argparse.Namespace, action: str) -> None: def run_profile_action(config: WorkspaceConfig, args: argparse.Namespace, action: str) -> None:
profile = normalize_env_profile(args.profile) profile = normalize_env_profile(args.profile)
series_name, card_name, task_name = resolve_env_target(config, args.selector, getattr(args, "task", None)) selector_values, pane_target = split_pane_selector(args.selector, getattr(args, "pane", None))
series_name, card_name, task_name = resolve_env_target(config, selector_values, getattr(args, "task", None))
shell_command, details = env_action_command( shell_command, details = env_action_command(
config, config,
profile, profile,
@@ -3661,6 +3731,14 @@ def run_profile_action(config: WorkspaceConfig, args: argparse.Namespace, action
instance=getattr(args, "instance", None), instance=getattr(args, "instance", None),
editor=getattr(args, "editor", None), editor=getattr(args, "editor", None),
) )
if pane_target:
pane_details = send_command_to_tmux_pane(shell_command, pane_target, dry_run=args.dry_run)
details.update(pane_details)
if args.dry_run:
print_env_plan(details)
return
print_env_plan(details)
return
if args.dry_run: if args.dry_run:
print_env_plan(details) print_env_plan(details)
return return
@@ -3975,6 +4053,7 @@ def build_parser() -> argparse.ArgumentParser:
debug_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.") debug_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.")
debug_parser.add_argument("--instance", help="Container/tmux instance name.") debug_parser.add_argument("--instance", help="Container/tmux instance name.")
debug_parser.add_argument("--editor", choices=["nvim", "vim"], help="Editor inside the container.") debug_parser.add_argument("--editor", choices=["nvim", "vim"], help="Editor inside the container.")
debug_parser.add_argument("--pane", help="Send the command to a host tmux pane, for example 0, node:0, :node.0 or %3.")
debug_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.") debug_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
run_parser = subparsers.add_parser("run", help="Build and run selected task inside a container profile.") run_parser = subparsers.add_parser("run", help="Build and run selected task inside a container profile.")
@@ -3982,6 +4061,7 @@ def build_parser() -> argparse.ArgumentParser:
run_parser.add_argument("selector", nargs="*", help="Optional selector: [task], [card task] or [series card task].") run_parser.add_argument("selector", nargs="*", help="Optional selector: [task], [card task] or [series card task].")
run_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.") run_parser.add_argument("--task", help="Task override. Defaults to selector or defaults.task.")
run_parser.add_argument("--instance", help="Container instance name.") run_parser.add_argument("--instance", help="Container instance name.")
run_parser.add_argument("--pane", help="Send the command to a host tmux pane, for example 0, node:0, :node.0 or %3.")
run_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.") run_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
shell_parser = subparsers.add_parser("shell", help="Open a shell inside a container profile.") shell_parser = subparsers.add_parser("shell", help="Open a shell inside a container profile.")
@@ -3989,6 +4069,7 @@ def build_parser() -> argparse.ArgumentParser:
shell_parser.add_argument("selector", nargs="*", help="Optional selector: [card] or [series card].") shell_parser.add_argument("selector", nargs="*", help="Optional selector: [card] or [series card].")
shell_parser.add_argument("--task", help="Task used only for instance naming.") shell_parser.add_argument("--task", help="Task used only for instance naming.")
shell_parser.add_argument("--instance", help="Container instance name.") shell_parser.add_argument("--instance", help="Container instance name.")
shell_parser.add_argument("--pane", help="Send the command to a host tmux pane, for example 0, node:0, :node.0 or %3.")
shell_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.") shell_parser.add_argument("--dry-run", action="store_true", help="Print command without starting Docker.")
tasks_parser = subparsers.add_parser("tasks", help="Shortcut for tasks in the default card.") tasks_parser = subparsers.add_parser("tasks", help="Shortcut for tasks in the default card.")