onnxruntime (wake word) and ctranslate2 (local speech-to-text) link against msvcp140.dll and msvcp140_1.dll, which the managed CPython does not ship. On Windows without the system VC++ Redistributable both imports failed with "DLL load failed: The specified module could not be found", so "Hey Hermes" and voice chat transcription never worked. - Bundle the Microsoft-signed VC143 CRT set (14.44.35211, matching the managed CPython's vcruntime140) as a Windows extra resource with a SHA-256 manifest. - Before every local backend start, copy missing or mismatching CRT files beside the managed python.exe (the process image directory, searched first), verifying digests; never overwrite the interpreter's own vcruntime and never block boot. Existing installs heal on their next launch. - Report an unloadable native voice wheel as a VC++ runtime problem in the wake-word requirements, local transcription and `hermes doctor`, instead of treating installed package metadata as proof the wheel loads. - Bump AITURK IDE to 1.0.0-beta.12. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
"""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})"
|