's'
This commit is contained in:
167
tools/compat.py
Normal file
167
tools/compat.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
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))
|
||||
|
||||
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))
|
||||
Reference in New Issue
Block a user