Source code for scitex_container.host._packages

#!/usr/bin/env python3
# Timestamp: "2026-02-25"
# File: src/scitex_container/host/_packages.py
"""Host package installation and verification."""

from __future__ import annotations

import shutil
import subprocess
from pathlib import Path

from scitex_container._compat import supports_return_as

# ---------------------------------------------------------------------------
# Package root resolution
# ---------------------------------------------------------------------------

_PKG_ROOT = Path(__file__).resolve().parents[4]  # scitex-container root
_INSTALL_SCRIPT = _PKG_ROOT / "scripts" / "install-host-packages.sh"

# ---------------------------------------------------------------------------
# Binary → package group mapping
# ---------------------------------------------------------------------------

_TEXLIVE_BINARIES = [
    "pdflatex",
    "bibtex",
    "latexmk",
    "latexdiff",
    "kpsewhich",
    "makeindex",
    "biber",
]
_TEXLIVE_EXTRA_BINARIES = ["gs", "pdfinfo"]  # ghostscript, poppler-utils

_IMAGEMAGICK_BINARIES = ["convert", "identify", "mogrify"]


def _find_version(cmd: str) -> str:
    """Return first line of --version output, or empty string on failure."""
    try:
        result = subprocess.run(
            [cmd, "--version"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        output = (result.stdout or result.stderr or "").strip()
        return output.splitlines()[0] if output else ""
    except Exception:
        return ""


[docs] @supports_return_as def check_packages() -> dict: """Check which host packages are installed. Returns ------- dict Structured status per package group:: { "texlive": { "installed": True, "version": "pdfTeX 3.141592653...", "binaries": ["pdflatex", "bibtex", ...], }, "imagemagick": { "installed": True, "version": "Version: ImageMagick 6.9...", "binaries": ["convert", "identify"], }, } """ result: dict = {} # TeXLive all_tex_bins = _TEXLIVE_BINARIES + _TEXLIVE_EXTRA_BINARIES found_tex = [b for b in all_tex_bins if shutil.which(b)] tex_version = _find_version("pdflatex") if shutil.which("pdflatex") else "" result["texlive"] = { "installed": bool(found_tex), "version": tex_version, "binaries": found_tex, } # ImageMagick found_im = [b for b in _IMAGEMAGICK_BINARIES if shutil.which(b)] im_version = _find_version("convert") if shutil.which("convert") else "" result["imagemagick"] = { "installed": bool(found_im), "version": im_version, "binaries": found_im, } return result
[docs] @supports_return_as def install_packages( texlive: bool = False, imagemagick: bool = False, all: bool = False, # noqa: A002 check_only: bool = False, ) -> dict: """Install host packages by calling the shell script. Parameters ---------- texlive : bool Install TeXLive packages. imagemagick : bool Install ImageMagick. all : bool Install all packages (overrides texlive/imagemagick flags). check_only : bool Run the script in --check mode without installing anything. Returns ------- dict Status per package group:: { "texlive": {"status": "installed", "returncode": 0}, "imagemagick": {"status": "skipped", "returncode": None}, "script": "/abs/path/to/install-host-packages.sh", } Raises ------ FileNotFoundError If the install script cannot be found. """ if not _INSTALL_SCRIPT.exists(): raise FileNotFoundError( f"Install script not found: {_INSTALL_SCRIPT}\n" "Expected at: scitex-container/scripts/install-host-packages.sh" ) result: dict = {"script": str(_INSTALL_SCRIPT)} if check_only: proc = subprocess.run( ["bash", str(_INSTALL_SCRIPT), "--check"], capture_output=False, text=True, ) result["check"] = {"returncode": proc.returncode} return result # Build flags flags: list[str] = [] if all: flags = ["--all"] else: if texlive: flags.append("--texlive") if imagemagick: flags.append("--imagemagick") # Default: install everything when no flag given if not flags: flags = ["--all"] cmd = ["sudo", "bash", str(_INSTALL_SCRIPT)] + flags proc = subprocess.run(cmd, capture_output=False, text=True) if "--texlive" in flags or "--all" in flags: result["texlive"] = { "status": "installed" if proc.returncode == 0 else "failed", "returncode": proc.returncode, } else: result["texlive"] = {"status": "skipped", "returncode": None} if "--imagemagick" in flags or "--all" in flags: result["imagemagick"] = { "status": "installed" if proc.returncode == 0 else "failed", "returncode": proc.returncode, } else: result["imagemagick"] = {"status": "skipped", "returncode": None} return result
# EOF