fix(esp32): resolve libqemu path on every call + read VELXIO_QEMU_PATH

The desktop Tauri wrapper drops the downloaded libqemu-xtensa.<ext>
inside the directory it exports as VELXIO_QEMU_PATH. The sidecar
previously only checked the dedicated QEMU_ESP32_LIB env var (full
path) plus a fixed file beside the module, so the in-app installer's
output was invisible — adding an ESP32 to the canvas right after
install reported "unavailable" until the sidecar was restarted.

Two changes:

1. New _resolve_lib() helper checks QEMU_ESP32_LIB first, then
   VELXIO_QEMU_PATH/<lib_name>, then the legacy module-adjacent path.
   Both Xtensa and RISC-V flow through the same resolver.

2. lib_xtensa_path() / lib_riscv_path() functions replace the module
   constants for all internal call sites (is_available,
   is_riscv_available, start_instance). Resolution happens per call,
   so a post-boot install is picked up without restarting the
   sidecar. The LIB_PATH / LIB_RISCV_PATH constants stay for any
   external readers but reflect import-time state only.

Pairs with the Tauri-side change that writes libqemu-xtensa.<ext>
directly to VELXIO_QEMU_PATH (no archive extraction).
This commit is contained in:
David Montero 2026-05-27 03:43:54 +02:00
parent fddfcfd2a6
commit 9a21df7a98
1 changed files with 54 additions and 13 deletions

View File

@ -65,16 +65,60 @@ else:
# Xtensa library (ESP32, ESP32-S3)
_LIB_XTENSA_NAME = f'libqemu-xtensa{_LIB_EXT}'
_DEFAULT_LIB_XTENSA = str(_SERVICES_DIR / _LIB_XTENSA_NAME)
LIB_PATH: str = os.environ.get('QEMU_ESP32_LIB', '') or (
_DEFAULT_LIB_XTENSA if os.path.isfile(_DEFAULT_LIB_XTENSA) else ''
)
# RISC-V library (ESP32-C3)
_LIB_RISCV_NAME = f'libqemu-riscv32{_LIB_EXT}'
_DEFAULT_LIB_RISCV = str(_SERVICES_DIR / _LIB_RISCV_NAME)
LIB_RISCV_PATH: str = os.environ.get('QEMU_RISCV32_LIB', '') or (
_DEFAULT_LIB_RISCV if os.path.isfile(_DEFAULT_LIB_RISCV) else ''
)
def _resolve_lib(env_var: str, lib_name: str, default_path: str) -> str:
"""Three-step resolution for the libqemu shared library.
1. Explicit env var (`QEMU_ESP32_LIB` / `QEMU_RISCV32_LIB`) full
path including filename, used by Docker images that download the
library to a fixed location at build time.
2. `VELXIO_QEMU_PATH` directory set by the Tauri desktop wrapper
when the user runs the in-app "Install ESP32 support" download.
The Tauri side drops the file as `libqemu-xtensa.<ext>` /
`libqemu-riscv32.<ext>` inside that directory; we just join.
3. Beside this module (`_DEFAULT_LIB_*`) the legacy layout for
hand-installed dev environments.
First match wins. Empty string when nothing is found, in which case
the manager reports the ESP32 board kind as unavailable.
Resolved on every call (not cached) so the desktop's in-app
installer can drop the library after the sidecar boots without
requiring a sidecar restart.
"""
direct = os.environ.get(env_var, '')
if direct and os.path.isfile(direct):
return direct
qemu_dir = os.environ.get('VELXIO_QEMU_PATH', '')
if qemu_dir:
candidate = os.path.join(qemu_dir, lib_name)
if os.path.isfile(candidate):
return candidate
if os.path.isfile(default_path):
return default_path
return ''
def lib_xtensa_path() -> str:
"""Current resolved path to libqemu-xtensa.<ext>, or '' if missing."""
return _resolve_lib('QEMU_ESP32_LIB', _LIB_XTENSA_NAME, _DEFAULT_LIB_XTENSA)
def lib_riscv_path() -> str:
"""Current resolved path to libqemu-riscv32.<ext>, or '' if missing."""
return _resolve_lib('QEMU_RISCV32_LIB', _LIB_RISCV_NAME, _DEFAULT_LIB_RISCV)
# Module-level snapshots kept for callers that still read the constant.
# Prefer the functions above — these reflect import-time state only and
# won't pick up a post-boot install.
LIB_PATH: str = lib_xtensa_path()
LIB_RISCV_PATH: str = lib_riscv_path()
_WORKER_SCRIPT = _SERVICES_DIR / 'esp32_worker.py'
@ -159,16 +203,13 @@ class EspLibManager:
@staticmethod
def is_available() -> bool:
"""Returns True if the Xtensa DLL is present (minimum for ESP32/ESP32-S3)."""
return (
bool(LIB_PATH)
and os.path.isfile(LIB_PATH)
and _WORKER_SCRIPT.exists()
)
path = lib_xtensa_path()
return bool(path) and _WORKER_SCRIPT.exists()
@staticmethod
def is_riscv_available() -> bool:
"""Returns True if the RISC-V DLL is present (required for ESP32-C3)."""
return bool(LIB_RISCV_PATH) and os.path.isfile(LIB_RISCV_PATH)
return bool(lib_riscv_path())
# ── Public API ────────────────────────────────────────────────────────────
@ -197,7 +238,7 @@ class EspLibManager:
return
machine = _MACHINE.get(board_type, 'esp32-picsimlab')
lib_path = LIB_RISCV_PATH if board_type in _RISCV_BOARDS else LIB_PATH
lib_path = lib_riscv_path() if board_type in _RISCV_BOARDS else lib_xtensa_path()
config = json.dumps({
'lib_path': lib_path,
'firmware_b64': firmware_b64,