Files
aiturk-hermes-ide/tools/windows_native_runtime.py
yilsemandClaude Opus 5 233885d3a7 fix(desktop): ship the VC++ runtime the Windows voice wheels need
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>
2026-09-15 19:46:39 +03:00

78 lines
2.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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})"