"""Compilar y ejecutar un juego vía subprocess.""" from __future__ import annotations import os import subprocess from collections.abc import Callable from pathlib import Path from .config import Game from .paths import repo_dir LogFn = Callable[[str], None] # Directorios habituales de herramientas de línea de comandos (Homebrew, MacPorts…). # Una .app de macOS lanzada desde Finder/Dock NO hereda el PATH del shell de login, # así que herramientas como cmake, instaladas con Homebrew en /opt/homebrew/bin # (Apple Silicon) o /usr/local/bin (Intel), quedan fuera del PATH y `make` no las # encuentra. Las añadimos explícitamente. En Linux estos paths no existen y se # filtran solos. _EXTRA_PATH_DIRS = ( "/opt/homebrew/bin", "/opt/homebrew/sbin", "/usr/local/bin", "/usr/local/sbin", "/opt/local/bin", ) def _noop(_: str) -> None: pass def _launch_env() -> dict[str, str]: """Entorno para los subprocesos con un PATH que incluye los directorios de herramientas habituales aunque la app se lance desde Finder/Dock.""" env = os.environ.copy() parts = env.get("PATH", "").split(os.pathsep) parts = [p for p in parts if p] for d in _EXTRA_PATH_DIRS: if d not in parts and os.path.isdir(d): parts.append(d) env["PATH"] = os.pathsep.join(parts) return env def _stream(cmd: str, cwd: Path, log: LogFn) -> int: """Ejecuta un comando de shell en cwd, retransmitiendo stdout/err línea a línea.""" log(f"$ {cmd} (cwd={cwd})") proc = subprocess.Popen( cmd, cwd=str(cwd), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, env=_launch_env(), ) assert proc.stdout is not None for line in proc.stdout: log(line.rstrip()) return proc.wait() def run_game(root: Path, game: Game, log: LogFn = _noop) -> int: """Compila (si hay build_cmd) y ejecuta el juego. Devuelve el código de salida. Si build_cmd falla, aborta sin ejecutar. Lanza FileNotFoundError si el repo no está clonado. """ repo = repo_dir(root, game.id) if not (repo / ".git").exists(): raise FileNotFoundError( f"{game.name} no està descarregat. Prem Descarrega primer." ) if game.build_cmd.strip(): log(f"Compilant {game.name}…") code = _stream(game.build_cmd, repo, log) if code != 0: log(f"La compilació ha fallat (codi {code}). No s'executa.") return code log(f"Executant {game.name}…") return _stream(game.run_cmd, repo, log)