Files
2026-04-04 16:18:52 -05:00

189 lines
6.3 KiB
Python

"""
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 -> guest by default (many servers have /dev/dri but host-EGL still breaks boot/adbd).
Set REDROID_USE_HOST_GPU=1 (or true/yes/on) with --gpu-mode auto to pass DRI + use host when nodes exist.
"""
req = (requested or "auto").strip().lower()
if req == "auto":
want_host = os.environ.get("REDROID_USE_HOST_GPU", "").strip().lower() in (
"1",
"true",
"yes",
"on",
)
effective = "host" if (want_host and 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: {} — for host GPU use --gpu-mode host or REDROID_USE_HOST_GPU=1 with --gpu-mode auto.".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 -> {} (set REDROID_USE_HOST_GPU=1 to prefer host when DRI exists).".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="guest", 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))