When launched via pythonw.exe (no console), sys.stdout/stderr are None. Accessing .encoding on None raised AttributeError, caught by the GUI's pipeline import guard and shown as 'Failed to load pipeline'. Added None check before the encoding check in fix_console_encoding(), added a test, and documented the pitfall in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
"""
|
|
arma_modlist_tools.compat
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
OS detection and cross-platform utilities shared by all CLI scripts.
|
|
|
|
Supported platforms:
|
|
- Windows / Windows Server (sys.platform == "win32")
|
|
- Ubuntu / Ubuntu Server (sys.platform == "linux")
|
|
|
|
Typical usage::
|
|
|
|
from arma_modlist_tools.compat import is_windows, get_os_label, fix_console_encoding
|
|
|
|
fix_console_encoding() # call once at script start on Windows
|
|
print(get_os_label()) # "Windows Server", "Ubuntu", etc.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import platform
|
|
import sys
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Platform detection
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def is_windows() -> bool:
|
|
"""Return ``True`` on Windows and Windows Server."""
|
|
return sys.platform == "win32"
|
|
|
|
|
|
def is_linux() -> bool:
|
|
"""Return ``True`` on Linux (Ubuntu, Ubuntu Server, and other distros)."""
|
|
return sys.platform == "linux"
|
|
|
|
|
|
def get_os_label() -> str:
|
|
"""
|
|
Return a human-readable OS label.
|
|
|
|
Possible values: ``"Windows"``, ``"Windows Server"``, ``"Ubuntu"``,
|
|
``"Ubuntu Server"``, ``"Linux"``, ``"Unknown"``.
|
|
"""
|
|
if is_windows():
|
|
ver = platform.version()
|
|
# Windows Server versions contain "Server" in the version string
|
|
# e.g. "10.0.17763 ... Windows Server 2019 ..."
|
|
if "Server" in platform.version() or "Server" in platform.uname().version:
|
|
return "Windows Server"
|
|
return "Windows"
|
|
|
|
if is_linux():
|
|
# Read /etc/os-release for distro name
|
|
os_release = _read_os_release()
|
|
name = os_release.get("NAME", "").lower()
|
|
|
|
if "ubuntu" in name:
|
|
# Distinguish desktop vs server: server images have no display server
|
|
if _is_headless():
|
|
return "Ubuntu Server"
|
|
return "Ubuntu"
|
|
|
|
return "Linux"
|
|
|
|
return "Unknown"
|
|
|
|
|
|
def _read_os_release() -> dict[str, str]:
|
|
"""Parse /etc/os-release into a dict (Linux only)."""
|
|
result: dict[str, str] = {}
|
|
try:
|
|
with open("/etc/os-release", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if "=" in line and not line.startswith("#"):
|
|
k, _, v = line.partition("=")
|
|
result[k] = v.strip('"')
|
|
except OSError:
|
|
pass
|
|
return result
|
|
|
|
|
|
def _is_headless() -> bool:
|
|
"""Return True if no graphical display server is detected (headless/server)."""
|
|
import os
|
|
# Check for common display environment variables
|
|
return not (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Console encoding
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def fix_console_encoding() -> None:
|
|
"""
|
|
Force UTF-8 output on Windows terminals that default to cp1252.
|
|
|
|
Call once at the top of any CLI script that uses Unicode characters
|
|
(checkmarks, arrows, etc.). No-op on Linux.
|
|
"""
|
|
if not is_windows():
|
|
return
|
|
if sys.stdout is None or sys.stderr is None:
|
|
return
|
|
if sys.stdout.encoding and sys.stdout.encoding.lower() == "utf-8":
|
|
return
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|