"""Detect native voice wheels that are installed but cannot load on Windows. Package metadata (what ``tools.lazy_deps`` checks) reports onnxruntime and ctranslate2 as installed even when their DLLs cannot load because the Microsoft Visual C++ runtime (msvcp140.dll) is missing. A lazy reinstall cannot fix that, so callers need to tell the two cases apart. Importing the module in a child interpreter does so without risking the calling process. """ from __future__ import annotations import functools import os import re import subprocess import sys from typing import Optional _MODULE_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_.]*$") # Windows reports the same loader failure in the user's UI language. _DLL_LOAD_MARKERS = ( "dll load failed", "could not find module", "specified module could not be found", "belirtilen modül bulunamadı", "[winerror 126]", ) VC_RUNTIME_HINT = ( "Ses bileşeni yüklenemedi: Microsoft Visual C++ çalışma zamanı (msvcp140.dll) eksik. " "AITURK IDE'yi kapatıp yeniden açın; IDE eksik dosyaları otomatik olarak geri yükler." ) def looks_like_dll_load_failure(message: str) -> bool: text = (message or "").lower() return any(marker in text for marker in _DLL_LOAD_MARKERS) @functools.lru_cache(maxsize=None) def native_load_error(module: str) -> Optional[str]: """Return the DLL load error for *module* on Windows, otherwise None. None also covers "not installed" and inconclusive probes: this helper only answers "installed but its native libraries cannot load". Cached per process because the runtime cannot change under a running backend (the desktop restores it before the backend starts). """ if sys.platform != "win32": return None if not _MODULE_RE.match(module): raise ValueError(f"invalid module name: {module!r}") try: proc = subprocess.run( [sys.executable, "-c", f"import {module}"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=120, env=dict(os.environ, PYTHONIOENCODING="utf-8"), creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), ) except (OSError, subprocess.TimeoutExpired): return None stderr = proc.stderr or "" if proc.returncode == 0 or not looks_like_dll_load_failure(stderr): return None lines = [line.strip() for line in stderr.splitlines() if line.strip()] return lines[-1] if lines else f"import {module} failed" def voice_runtime_error_message(error: str) -> str: return f"{VC_RUNTIME_HINT} ({error})"