""" Host and container-runtime compatibility checks for redroid-android13. """ from __future__ import annotations import glob import os import platform import subprocess import sys from dataclasses import dataclass, field from pathlib import Path from typing import List, Tuple _REPO_ROOT = Path(__file__).resolve().parents[1] if str(_REPO_ROOT) not in sys.path: sys.path.insert(0, str(_REPO_ROOT)) from tools.helper import bcolors, print_color @dataclass class CompatReport: errors: List[str] = field(default_factory=list) warnings: List[str] = field(default_factory=list) notes: List[str] = field(default_factory=list) def _runtime_available(runtime: str) -> bool: try: result = subprocess.run( [runtime, "version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30, check=False, ) return result.returncode == 0 except OSError: return False def detect_dri_devices() -> List[str]: """Paths suitable for docker/podman --device (Linux DRM).""" found: List[str] = [] for pattern in ("/dev/dri/card*", "/dev/dri/renderD*"): found.extend(glob.glob(pattern)) return sorted(set(found)) def _linux_binder_notes() -> List[str]: if platform.system() != "Linux": return [] notes: List[str] = [] try: with open("/proc/modules", "r", encoding="utf-8", errors="ignore") as handle: loaded = handle.read() if "binder_linux" not in loaded: notes.append( "binder_linux not loaded; redroid usually needs: " "modprobe binder_linux devices=binder,hwbinder,vndbinder" ) except OSError: pass return notes def analyze_for_build(container_runtime: str) -> CompatReport: report = CompatReport() if not _runtime_available(container_runtime): report.errors.append( "Container runtime {!r} not found or not working (install Docker/Podman and check PATH).".format( container_runtime ) ) system = platform.system() if system == "Windows": report.warnings.append( "Running on Windows: redroid containers are meant for Linux hosts (binder/ashmem); " "building files here is fine, running the container typically happens on Linux." ) elif system != "Linux": report.warnings.append( "Host OS is not Linux; upstream redroid expects Linux with binder and related kernel pieces." ) return report def resolve_gpu_mode(requested: str, dri_devices: List[str]) -> Tuple[str, List[str]]: """ Map user --gpu-mode to an effective redroid mode and docker --device arguments. auto -> host if DRI nodes exist, else guest. """ req = (requested or "auto").strip().lower() if req == "auto": effective = "host" if dri_devices else "guest" elif req == "host": effective = "host" if dri_devices else "guest" elif req == "guest": effective = "guest" else: effective = "guest" device_args: List[str] = [] if effective == "host" and dri_devices: for path in dri_devices: device_args.extend(["--device", path]) return effective, device_args def analyze_for_run( container_runtime: str, gpu_mode: str, skip_check: bool, ) -> Tuple[CompatReport, str, List[str]]: """ Returns (report, effective_gpu_mode, flat docker --device args). """ report = CompatReport() dri = detect_dri_devices() if not skip_check: build = analyze_for_build(container_runtime) report.errors.extend(build.errors) report.warnings.extend(build.warnings) report.notes.extend(build.notes) if dri: report.notes.append("DRI present: {} — GPU passthrough can be used.".format(", ".join(dri))) elif platform.system() == "Linux": report.warnings.append( "No /dev/dri render nodes: software GLES (guest) often black-screens or flickers on headless servers." ) report.notes.extend(_linux_binder_notes()) effective, device_args = resolve_gpu_mode(gpu_mode, dri) req = (gpu_mode or "auto").strip().lower() if req == "host" and effective == "guest": report.warnings.append( "--gpu-mode host requested but no DRI devices found; falling back to guest." ) if req == "auto" and not skip_check: report.notes.append("Resolved --gpu-mode auto -> {}".format(effective)) if effective == "host" and not skip_check: report.notes.append( "GPU host mode: if ADB cannot connect (connection refused), Android may not have finished boot " "or init crashed (EGL/driver mismatch); try --gpu-mode guest or docker logs on the container." ) return report, effective, device_args def emit_compat_report(report: CompatReport, prefix: str = "[compat] ") -> None: for msg in report.errors: print_color(prefix + msg, bcolors.RED) for msg in report.warnings: print_color(prefix + msg, bcolors.YELLOW) for msg in report.notes: print_color(prefix + msg, bcolors.GREEN) if __name__ == "__main__": import argparse ap = argparse.ArgumentParser(description="Host/runtime compatibility probe (same logic as run_redroid.py)") ap.add_argument("-c", "--container", default="docker", help="docker or podman") ap.add_argument("--gpu-mode", default="auto", help="guest, host, or auto") ns = ap.parse_args() rep, gpu_eff, dev_args = analyze_for_run(ns.container, ns.gpu_mode, skip_check=False) emit_compat_report(rep) print("effective_gpu_mode={}".format(gpu_eff)) print("flat_device_args={}".format(dev_args))