# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Shared mechanics of the llama.cpp / whisper.cpp in-app prebuilt updates. The component modules (utils.llama_cpp_update / utils.whisper_cpp_update) keep their public names, job dicts, and update policy (version comparison, pinning, pre/post install steps); everything mechanical (managed-root resolution, local-link detection, the resolve probe, the streamed installer run) lives here, parameterized so the modules' monkeypatch seams keep working. """ from __future__ import annotations import json import os import re import subprocess import sys import threading import time from pathlib import Path from typing import Callable, Optional import structlog from utils.process_lifetime import child_popen_kwargs logger = structlog.get_logger(__name__) # Markerless (source-build) resolve answers are memoized for 24h; only # successful answers are cached so a network blip retries. RESOLVE_TTL_SECONDS = 24 * 60 * 60 # Matches the installer's download progress lines, e.g. # "Downloading x.zip: 35.0% (12.3 MiB/35.1 MiB) at 8.2 MiB/s". PROGRESS_LINE_RE = re.compile(r"(\d+(?:\.\d+)?)%\s*\(") # The download dominates the update; extract/validate fill the last slice. DOWNLOAD_PROGRESS_CEILING = 0.95 class InstallerExit(RuntimeError): """Installer subprocess exited nonzero; carries the exit code so phase runners can special-case contractual codes (whisper's 2 = unavailable).""" def __init__(self, returncode: int, message: str) -> None: super().__init__(message) self.returncode = returncode JOB_IDLE = "idle" JOB_RUNNING = "running" JOB_SUCCESS = "success" JOB_ERROR = "error" # Per-phase states inside a chained job's "phases" breakdown. PHASE_PENDING = "pending" PHASE_RUNNING = "running" PHASE_SUCCESS = "success" PHASE_ERROR = "error" PHASE_SKIPPED = "skipped" _IDLE_JOB_FIELDS = dict( state = JOB_IDLE, message = "", from_tag = None, to_tag = None, reload_required = None, error = None, progress = None, started_at = None, finished_at = None, phases = None, ) def new_job() -> dict: """A fresh idle job-state dict (one per component module).""" return dict(_IDLE_JOB_FIELDS) def reset_job(job: dict, job_lock: threading.Lock) -> None: """Return a job tracker to idle (test seam).""" with job_lock: job.update(_IDLE_JOB_FIELDS) def utcnow() -> str: return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) def is_under(path: Path, root: Path) -> bool: try: p, r = path.resolve(), root.resolve() except (OSError, ValueError): p, r = path, root return p == r or r in p.parents def install_dir_for(binary_path: Optional[str], *, marker_name: str) -> Optional[Path]: """The directory holding the install marker: the install root the installer wrote and the one we re-install into. Walks up from the binary like the freshness marker reader does.""" if not binary_path: return None p = Path(binary_path) for parent in p.parents[:5]: if (parent / marker_name).is_file(): return parent return None def find_installer_script(*, env_var: str, script_name: str) -> Optional[Path]: """Locate the installer script. Honours the env override, then searches up from this file for both ``/