#!/usr/bin/env bash # Descarga y EMBEBE (build-time) todo lo que el binario Linux necesita en runtime: # - 7zz (7-Zip para Linux) # - Driver MS ODBC 18 + unixODBC # - Libs de sistema de Qt (cluster xcb/X11 + EGL) y del stack ODBC # (libltdl, Kerberos/GSSAPI, OpenSSL) que PySide6/pyodbc cargan por dlopen # y que NO vienen dentro del wheel. # Objetivo: que el onefile corra en un servidor Linux "pelado" sin instalar nada. set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" VERSIONS="$ROOT/packaging/bundled-versions.json" BUNDLED="$ROOT/packaging/bundled/linux" SEVEN="$BUNDLED/7zip" ODBC="$BUNDLED/odbc/lib" QTLIB="$BUNDLED/qt/lib" # libs de sistema extra a embeder en la raíz de _MEIPASS mkdir -p "$SEVEN" "$ODBC" "$QTLIB" read_url() { python3 -c "import json; print(json.load(open('$VERSIONS'))['$1'])" } # Descarga uno o más paquetes .deb (y NO sus dependencias), extrae sus .so y los # copia al directorio destino con el NOMBRE DE SONAME (lo que busca el loader: # libfoo.so.N), dereferenciando symlinks para no dejar enlaces colgantes. # Requiere apt-get/dpkg-deb. fetch_pkg_libs() { local destdir="$1"; shift if ! command -v apt-get >/dev/null 2>&1; then echo " (apt-get no disponible; se omite descarga de $* — el entorno de build debe proveerlas)" return 0 fi local tmp; tmp=$(mktemp -d) ( cd "$tmp" apt-get download "$@" >/dev/null 2>&1 || { # Reintento tras refrescar índices (build env con red). apt-get update -qq >/dev/null 2>&1 || true apt-get download "$@" >/dev/null 2>&1 || true } for deb in *.deb; do [[ -f "$deb" ]] || continue dpkg-deb -x "$deb" x done [[ -d x ]] || exit 0 # 1) Symlinks SONAME del propio paquete (libfoo.so.N -> libfoo.so.N.M.P): # copiar el contenido REAL bajo el nombre del symlink (= SONAME exacto). while IFS= read -r link; do cp -Lf "$link" "$destdir/$(basename "$link")" 2>/dev/null || true done < <(find x -type l -name '*.so.*') # 2) Archivos reales sin symlink: derivar SONAME por truncación de versión # (libfoo.so.N.M.P -> libfoo.so.N) para no dejarlos con nombre versionado. while IFS= read -r f; do local soname soname=$(basename "$f" | sed -E 's/(\.so\.[0-9]+)\..*/\1/') [[ -e "$destdir/$soname" ]] || cp -Lf "$f" "$destdir/$soname" 2>/dev/null || true done < <(find x -type f -name '*.so.*') ) rm -rf "$tmp" } echo "==> 7-Zip Linux (7zz)" LINUX_7Z_URL="$(read_url seven_zip_linux_x64)" if [[ ! -f "$SEVEN/7zz" ]]; then TMP=$(mktemp -d) curl -fsSL -o "$TMP/7z.tar.xz" "$LINUX_7Z_URL" tar -xJf "$TMP/7z.tar.xz" -C "$TMP" cp "$TMP/7zz" "$SEVEN/7zz" chmod +x "$SEVEN/7zz" rm -rf "$TMP" echo " 7zz instalado" else echo " 7zz ya existe" fi echo "==> ODBC Driver 18 + unixODBC" MSODBC_DEB="$(read_url msodbcsql18_deb)" if [[ -z "$(ls -A "$ODBC" 2>/dev/null || true)" ]]; then TMP=$(mktemp -d) cd "$TMP" curl -fsSL -o msodbcsql18.deb "$MSODBC_DEB" if command -v apt-get >/dev/null 2>&1; then export DEBIAN_FRONTEND=noninteractive apt-get update -qq || true apt-get install -y -qq unixodbc dpkg-dev > /dev/null 2>&1 || true if dpkg -l unixodbc 2>/dev/null | grep -q ^ii; then # -L: dereferencia symlinks para no dejar enlaces colgantes en el destino. find /usr/lib -name 'libodbc*.so*' -exec cp -Lf {} "$ODBC/" \; 2>/dev/null || true find /usr/lib -name 'libodbcinst*.so*' -exec cp -Lf {} "$ODBC/" \; 2>/dev/null || true fi fi dpkg-deb -x msodbcsql18.deb msodbcsql # Copiar SOLO el archivo real versionado del driver (evita el symlink -> /opt). find msodbcsql -type f -name 'libmsodbcsql-18*.so*' -exec cp -Lf {} "$ODBC/" \; cd "$ROOT" rm -rf "$TMP" # Stack de dependencias del driver MS ODBC 18, JUNTO al driver, para que un # servidor pelado (sin unixODBC/krb5/openssl) pueda cargarlo. El driver se # patchelf-ea a RPATH=$ORIGIN (más abajo) para resolverlas en su propio dir. ODBC_DEP_PKGS=( libltdl7 libkrb5-3 libgssapi-krb5-2 libk5crypto3 libkrb5support0 libcom-err2 libkeyutils1 libssl3 libodbc2 libodbcinst2 ) fetch_pkg_libs "$ODBC" "${ODBC_DEP_PKGS[@]}" || true fetch_pkg_libs "$ODBC" unixodbc || true echo " Bibliotecas ODBC + deps en $ODBC" else echo " ODBC ya existe" fi # RPATH=$ORIGIN en TODAS las .so del cluster ODBC: así el driver y sus deps # (libkrb5 -> libk5crypto/libkrb5support/libkeyutils, libgssapi, libltdl, # libodbcinst, openssl) se resuelven entre sí en su propio directorio una vez # copiados a config/odbc/lib, sin depender de LD_LIBRARY_PATH. if command -v patchelf >/dev/null 2>&1; then for so in "$ODBC"/*.so*; do [[ -f "$so" && ! -L "$so" ]] || continue patchelf --set-rpath '$ORIGIN' "$so" 2>/dev/null || true done echo " RPATH=\$ORIGIN aplicado a todo el cluster ODBC" else echo " ADVERTENCIA: patchelf no disponible; el driver ODBC podría no resolver" >&2 echo " sus deps en un servidor sin unixODBC/krb5. Instálalo en el entorno de build." >&2 fi echo "==> Libs de sistema Qt (cluster xcb/X11 + EGL) para la GUI con display" if [[ -z "$(ls -A "$QTLIB" 2>/dev/null || true)" ]]; then # Plataforma Qt xcb + su cierre X11/xcb + EGL. Van junto a las libs Qt en el # onefile (PySide6/Qt/lib) para que libQt6XcbQpa las encuentre por $ORIGIN. QT_XCB_PKGS=( libxcb-cursor0 libxcb-icccm4 libxcb-util1 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-render0 libxcb-shape0 libxcb-xkb1 libxkbcommon-x11-0 libxkbcommon0 libxtst6 libxcomposite1 libxdamage1 libxrandr2 libxkbfile1 libxcb1 libx11-6 libx11-xcb1 libxext6 libxrender1 libxi6 libxfixes3 libxcb-glx0 libxcb-randr0 libxcb-shm0 libxcb-sync1 libxcb-present0 libxcb-dri2-0 libxcb-dri3-0 libxcb-xfixes0 libxau6 libxdmcp6 libbsd0 libsm6 libice6 ) QT_GL_PKGS=(libglvnd0 libegl1 libgl1 libopengl0 libglx0 libgles2) fetch_pkg_libs "$QTLIB" "${QT_XCB_PKGS[@]}" fetch_pkg_libs "$QTLIB" "${QT_GL_PKGS[@]}" COUNT=$(find "$QTLIB" -type f -name '*.so*' 2>/dev/null | wc -l) echo " $COUNT libs Qt embebidas en $QTLIB" if [[ "$COUNT" -eq 0 ]]; then echo " ADVERTENCIA: no se embebió ninguna lib Qt. El entorno de build" >&2 echo " (docker-build-linux.sh) debe traerlas para que PyInstaller las incluya." >&2 fi else echo " Libs Qt ya existen ($(find "$QTLIB" -type f -name '*.so*' | wc -l) archivos)" fi echo "Listo: $BUNDLED"