diff --git a/script/install/install_debian_dependency.sh b/script/install/install_debian_dependency.sh index 8f1b15d..d8cb64f 100755 --- a/script/install/install_debian_dependency.sh +++ b/script/install/install_debian_dependency.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash . ./env_var.sh +. ./script/install/lib_qpdf.sh EL_USER=${USER} #EL_INSTALL_WKHTMLTOPDF="True" @@ -169,33 +170,11 @@ if [ "$(uname -m)" = "s390x" ]; then echo "Attention : libclang introuvable, la compilation de pymupdf va echouer." fi fi - # pikepdf n'a pas de roue s390x et se lie à qpdf, dont il exige une version - # PRÉCISE : 12.2.0 au minimum pour la 10.x. Ubuntu en livre 9.1.1 (20.04), - # 10.6.3 (22.04) et 11.9.0 (24.04) — trop anciennes, et aucune version de - # pikepdf ne descend sous qpdf 11.5. 25.10 et 26.04 passent, d'où le partage - # observé. On complète donc par les sources sous ce seuil ; /usr/local est - # déjà dans les chemins par défaut de g++ et de l'éditeur de liens. - QPDF_MIN=12.2.0 - QPDF_VER=12.3.2 - qpdf_have="$(pkg-config --modversion libqpdf 2>/dev/null || echo 0)" - if [ "$(printf '%s\n%s\n' "${QPDF_MIN}" "${qpdf_have}" | sort -V | head -1)" != "${QPDF_MIN}" ]; then - echo "qpdf ${qpdf_have} < ${QPDF_MIN} requis par pikepdf : compilation de qpdf ${QPDF_VER} (long en emulation)." - QPDF_TMP="$(mktemp -d)" - if curl -fsSL --max-time 600 -o "${QPDF_TMP}/qpdf.tar.gz" \ - "https://github.com/qpdf/qpdf/releases/download/v${QPDF_VER}/qpdf-${QPDF_VER}.tar.gz" && - tar -xzf "${QPDF_TMP}/qpdf.tar.gz" -C "${QPDF_TMP}" && - cmake -S "${QPDF_TMP}/qpdf-${QPDF_VER}" -B "${QPDF_TMP}/build" \ - -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ - -DBUILD_DOC=OFF -DBUILD_STATIC_LIBS=OFF && - cmake --build "${QPDF_TMP}/build" -j"$(nproc)" && - sudo cmake --install "${QPDF_TMP}/build"; then - sudo ldconfig - echo "qpdf $(PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config --modversion libqpdf 2>/dev/null) installe dans /usr/local." - else - echo "Attention : compilation de qpdf echouee, pikepdf ne pourra pas se construire." - fi - rm -rf "${QPDF_TMP}" - fi + # pikepdf se lie a qpdf, dont il exige 12.2.0 au minimum. Ubuntu 24.04 en + # livre 11.9.0 ; 25.10 et 26.04 passent, d'ou le partage observe. Le detail + # -- seuil, version batie, chemin d'installation -- est dans lib_qpdf.sh, + # partage avec les scripts dnf et zypper qui butaient sur le meme mur. + el_qpdf_ensure # cryptography ne publie aucune roue s390x : elle se compile, et son # Cargo.lock est en version 4, que seul cargo >= 1.78 sait lire. Ubuntu 24.04 # livre 1.75 et s'arrête sur « lock file version 4 requires diff --git a/script/install/install_fedora_dependency.sh b/script/install/install_fedora_dependency.sh index e530196..b60d38d 100755 --- a/script/install/install_fedora_dependency.sh +++ b/script/install/install_fedora_dependency.sh @@ -2,17 +2,33 @@ # © 2021-2026 TechnoLibre (http://www.technolibre.ca) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) # -# Dépendances système ERPLibre pour Fedora (dnf). Équivalent Fedora de -# install_debian_dependency.sh. « --skip-unavailable » tolère un nom de paquet -# absent (dnf5) au lieu d'échouer sur tout le lot. +# Dépendances système ERPLibre pour la famille dnf : Fedora, mais aussi +# AlmaLinux, Rocky et RHEL, qu'install_dev.sh aiguille ici sur ID_LIKE=rhel. +# Équivalent de install_debian_dependency.sh. +# +# Attention : Fedora 41+ livre dnf5, EL9 et EL10 encore dnf4. Les deux ne +# comprennent pas les mêmes options — voir DNF_SKIP plus bas. . ./env_var.sh +. ./script/install/lib_qpdf.sh EL_USER=${USER} +# « Sauter un paquet introuvable au lieu d'échouer sur tout le lot » ne +# s'écrit PAS pareil selon la version de dnf : « --skip-unavailable » est une +# option de dnf5 (Fedora 41+), et dnf4 — encore livré par EL9 et EL10, donc +# par AlmaLinux et Rocky — la refuse net : « dnf install: error: unrecognized +# arguments ». Son équivalent y est « --setopt=strict=0 ». On demande donc à +# dnf lui-même ce qu'il comprend, plutôt que de deviner d'après la distro. +if dnf install --help 2>&1 | grep -q -- "--skip-unavailable"; then + DNF_SKIP="--skip-unavailable" +else + DNF_SKIP="--setopt=strict=0" +fi +echo "dnf : option de tolerance retenue = ${DNF_SKIP}" # dnf résilient : rafraîchit le cache (évite « checksum doesn't match » / # signature après un cache périmé) et saute les paquets introuvables. -DNF="sudo dnf install -y --refresh --skip-unavailable" +DNF="sudo dnf install -y --refresh ${DNF_SKIP}" #-------------------------------------------------- # Dérivés RHEL : dépôts supplémentaires @@ -21,8 +37,8 @@ DNF="sudo dnf install -y --refresh --skip-unavailable" # les aiguille sur ID_LIKE=rhel). Contrairement à Fedora, leur jeu de dépôts # par défaut est ÉTROIT : la plupart des paquets « -devel » vivent dans CRB # (CodeReady Builder), et tout ce qui est communautaire dans EPEL. Sans ces -# deux dépôts, « --skip-unavailable » saute les paquets EN SILENCE et l'échec -# n'apparaît qu'à la compilation, très loin d'ici. +# deux dépôts, la tolérance aux paquets absents les saute EN SILENCE et +# l'échec n'apparaît qu'à la compilation, très loin d'ici. if [ -r /etc/os-release ]; then # shellcheck disable=SC1091 . /etc/os-release @@ -32,21 +48,106 @@ case "${ID}" in echo -e "\n---- Depots EPEL et CRB (famille RHEL) ----" sudo dnf install -y epel-release \ || echo "epel-release indisponible : certains paquets manqueront." - # « crb » sur EL9/EL10 ; « powertools » était le nom sur EL8. - sudo dnf config-manager --set-enabled crb 2> /dev/null \ + # /usr/bin/crb est l'outil fourni par epel-release, et ce que son propre + # scriptlet recommande d'exécuter — plus fiable que de deviner le nom du + # dépôt, qui vaut « crb » sur EL9/EL10 et « powertools » sur EL8. + sudo /usr/bin/crb enable 2> /dev/null \ + || sudo dnf config-manager --set-enabled crb 2> /dev/null \ || sudo dnf config-manager --set-enabled powertools 2> /dev/null \ || sudo dnf config-manager --enable crb 2> /dev/null \ || echo "CRB/PowerTools non active : certains -devel manqueront." + # Un dépôt fraîchement activé dont les métadonnées ne descendent pas fait + # échouer TOUS les dnf suivants. On le voit ici, avec son nom, plutôt que + # trois sections plus loin sur un paquet sans rapport. + sudo dnf makecache 2> /dev/null \ + || { sudo dnf clean all; sudo dnf makecache; } \ + || echo "Attention : metadonnees de depot incompletes." ;; esac #-------------------------------------------------- # Outils de compilation (build Python via pyenv, extensions Python) #-------------------------------------------------- -echo -e "\n---- Groupe outils de développement ----" -# Groupes par ID (le nom affiché « C Development Tools... » n'est pas matché). -sudo dnf group install -y --skip-unavailable development-tools c-development \ - || sudo dnf install -y gcc gcc-c++ make automake patch +echo -e "\n---- Outils de developpement ----" +# Les GROUPES ne sont pas les mêmes d'une famille à l'autre : Fedora a +# « c-development », EL ne connaît que « development » — vérifié dans le +# comps.xml d'AlmaLinux 10, qui n'a même pas « development-tools ». +# +# Pire, la tolérance aux paquets absents les rend INOFFENSIFS : un +# « dnf group install » de deux groupes inconnus REND ZÉRO sans rien poser, +# donc le « || » de repli ne se déclenchait jamais. gcc arrivait par la +# section pyenv plus bas, gcc-c++ par personne, et numpy s'arrêtait sur +# « Unknown compiler(s): [['c++'], ['g++'], …] ». +# +# On installe donc les compilateurs EXPLICITEMENT, sans dépendre d'un groupe. +# Le groupe reste ensuite, en complément et en best-effort. +${DNF} gcc gcc-c++ make automake patch +retVal=$? +if [[ $retVal -ne 0 ]]; then + echo "dnf install compilers error." + exit 1 +fi +sudo dnf group install -y ${DNF_SKIP} development c-development \ + > /dev/null 2>&1 || true + +#-------------------------------------------------- +# Mainframe s390x +#-------------------------------------------------- +# Sur s390x, AUCUNE roue PyPI n'existe : numpy, pillow, lxml, pikepdf, +# psycopg2, cryptography… tout se compile contre les bibliothèques de la +# distribution. Ces en-têtes ne servent à rien sur amd64, où pip pose des +# roues, mais leur absence ici arrête l'installation très loin de sa cause — +# « The headers or library files could not be found for jpeg » pour pillow. +if [ "$(uname -m)" = "s390x" ]; then + echo -e "\n---- Dependances de compilation s390x ----" + # Best-effort : un nom qui change d'une version à l'autre ne doit pas + # emporter le lot. Ce qui est vraiment indispensable est déjà installé + # au-dessus (compilateurs) ou plus bas (pyenv, PostgreSQL). + # « rust » et « cargo » : plusieurs paquets Python portent une extension + # RUST, pas seulement C — bcrypt et cryptography en tête. Sur amd64 leurs + # roues masquent le besoin ; ici tout compile, et bcrypt s'arrête net sur + # « error: can't find Rust compiler ». Les versions livrées suffisent + # (AlmaLinux 1.92, Fedora plus récent) au Cargo.lock v4 qui exige 1.78. + ${DNF} \ + rust cargo \ + libjpeg-turbo-devel zlib-devel geos-devel proj-devel \ + krb5-devel tbb-devel ninja-build clang-devel llvm-devel \ + GeographicLib-devel pkgconf-pkg-config cmake + + # qpdf : pikepdf en exige 12.2.0, et le paquet de la distribution ne le donne + # que sur Fedora. EL9 livre 10.3 — même pas « qpdf/QPDFJob.hh », d'où trois + # cents lignes de g++ sans un mot sur qpdf ; EL10 livre 11.9. + # + # On ne pose donc « qpdf-devel » QUE s'il atteint le seuil, au lieu de le + # poser puis de le contourner : contrairement à Debian, l'éditeur de liens de + # RHEL cherche /usr/lib64 AVANT /usr/local/lib64. Le -devel trop ancien ferait + # lier pikepdf contre l'ancienne bibliothèque malgré les en-têtes neufs de + # /usr/local/include — une incohérence bien plus difficile à lire qu'un + # en-tête absent. Sans lui, el_qpdf_ensure fournit tout depuis les sources. + qpdf_repo="$(dnf repoquery --qf '%{version}' --latest-limit 1 qpdf-devel \ + 2> /dev/null | tail -1)" + if el_qpdf_ge_min "${qpdf_repo}"; then + ${DNF} qpdf-devel + else + echo "qpdf-devel ${qpdf_repo:-absent} sous le seuil de pikepdf : compilation depuis les sources." + fi + el_qpdf_ensure + # Le dire ICI plutôt que de laisser bcrypt le découvrir une heure plus tard. + if ! command -v cargo > /dev/null 2>&1; then + echo "Attention : cargo absent, bcrypt et cryptography ne compileront pas." + fi + # pymupdf charge « libclang.so » par son nom nu, via ctypes. Le paquet le + # livre sous un nom versionné : il ne manque que le lien. + for d in /usr/lib64 /usr/lib; do + if [ -d "${d}" ] && [ ! -e "${d}/libclang.so" ]; then + so="$(ls -1 "${d}"/libclang.so.* 2> /dev/null | sort -V | tail -1)" + if [ -n "${so}" ]; then + sudo ln -s "${so}" "${d}/libclang.so" && sudo ldconfig + echo "libclang.so -> ${so}" + fi + fi + done +fi #-------------------------------------------------- # PostgreSQL diff --git a/script/install/install_suse_dependency.sh b/script/install/install_suse_dependency.sh index 9abddc2..a70a252 100755 --- a/script/install/install_suse_dependency.sh +++ b/script/install/install_suse_dependency.sh @@ -10,6 +10,7 @@ # demi-heure sous émulation s390x, ne s'y déclenche pas. . ./env_var.sh +. ./script/install/lib_qpdf.sh EL_USER=${USER} diff --git a/script/install/lib_qpdf.sh b/script/install/lib_qpdf.sh new file mode 100644 index 0000000..547aae3 --- /dev/null +++ b/script/install/lib_qpdf.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# © 2021-2026 TechnoLibre (http://www.technolibre.ca) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +# +# Bibliothèque SOURÇABLE : garantir un qpdf assez récent pour pikepdf. +# +# pikepdf 10 déclare QPDF_MIN_VERSION = "12.2.0" et se lie à la bibliothèque +# C++ de la distribution. Là où PyPI publie une roue, rien de tout cela ne +# s'exécute ; sur s390x il n'y en a aucune, pikepdf compile, et une version +# trop ancienne le fait échouer sur des en-têtes qui n'existent pas encore +# (« qpdf/QPDFJob.hh », apparu en 10.6 ; « qpdf/QPDFLogger.hh », en 11.0) puis +# sur des constantes renommées. Le message ne nomme jamais qpdf : on lit trois +# cents lignes de g++ avant de comprendre. +# +# Aucune famille n'est à jour partout, d'où le seuil plutôt qu'une liste de +# distributions : Ubuntu 24.04 livre 11.9, AlmaLinux et Rocky 9 livrent 10.3 +# (sans même QPDFJob.hh), leurs 10 livrent 11.9 ; Ubuntu 25.10, Fedora 43 et +# Tumbleweed dépassent le seuil et ne compilent rien. +# +# Ce fichier existe parce que le bloc vivait en double : corrigé côté apt, il +# ne l'était pas côté dnf, et EL9 est ressorti sur exactement le même mur le +# lendemain. Le seuil, la version et la façon de bâtir sont désormais écrits +# UNE fois pour les trois familles de paquets. + +EL_QPDF_MIN=12.2.0 +EL_QPDF_VER=12.3.2 + +# Vrai si la version passée en argument atteint le seuil. « sort -V » est le +# seul comparateur de versions disponible partout sans dépendance, mais il +# classe « 12.2 » AVANT « 12.2.0 » — un paquet numéroté sur deux composantes +# aurait donc déclenché une compilation d'une demi-heure pour rien. On complète +# les composantes manquantes avant de comparer. +el_qpdf_ge_min() { + local v="$1" dots + [ -n "${v}" ] || return 1 + dots="${v//[^.]/}" + while [ "${#dots}" -lt 2 ]; do + v="${v}.0" + dots="${dots}." + done + [ "$(printf '%s\n%s\n' "${EL_QPDF_MIN}" "${v}" | sort -V | head -1)" = "${EL_QPDF_MIN}" ] +} + +# Version actuellement visible du compilateur, ou « 0 ». +# /usr/local n'est pas dans le chemin par défaut de pkg-config sur toutes les +# familles : sans ces deux entrées, un qpdf déjà compilé passerait inaperçu et +# on le recompilerait à chaque passage du script. +el_qpdf_version() { + PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/usr/local/lib64/pkgconfig:${PKG_CONFIG_PATH}" \ + pkg-config --modversion libqpdf 2> /dev/null || echo 0 +} + +# Compile et installe qpdf dans /usr/local si, et seulement si, ce qui est +# présent ne suffit pas. Ne rend jamais un code non nul : l'échec est signalé +# et l'installation continue, pikepdf dira lui-même ce qui manque. +el_qpdf_ensure() { + local have build dir + have="$(el_qpdf_version)" + if el_qpdf_ge_min "${have}"; then + echo "qpdf ${have} >= ${EL_QPDF_MIN} : rien a compiler pour pikepdf." + return 0 + fi + + echo "qpdf ${have} < ${EL_QPDF_MIN} requis par pikepdf : compilation de qpdf ${EL_QPDF_VER} (long en emulation)." + build="$(mktemp -d)" + if curl -fsSL --max-time 600 -o "${build}/qpdf.tar.gz" \ + "https://github.com/qpdf/qpdf/releases/download/v${EL_QPDF_VER}/qpdf-${EL_QPDF_VER}.tar.gz" \ + && tar -xzf "${build}/qpdf.tar.gz" -C "${build}" \ + && cmake -S "${build}/qpdf-${EL_QPDF_VER}" -B "${build}/build" \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DBUILD_DOC=OFF -DBUILD_STATIC_LIBS=OFF \ + && cmake --build "${build}/build" -j"$(nproc)" \ + && sudo cmake --install "${build}/build"; then + # CMake choisit « lib » ou « lib64 » selon la famille, et /usr/local/lib64 + # n'est pas dans le chemin de ld.so d'EL : sans cette déclaration, pikepdf + # se construirait pour échouer au CHARGEMENT, encore plus loin de la cause. + for dir in /usr/local/lib64 /usr/local/lib; do + if [ -e "${dir}/libqpdf.so" ]; then + echo "${dir}" | sudo tee /etc/ld.so.conf.d/qpdf-local.conf > /dev/null + break + fi + done + sudo ldconfig + echo "qpdf $(el_qpdf_version) installe dans /usr/local." + else + echo "Attention : compilation de qpdf echouee, pikepdf ne pourra pas se construire." + fi + rm -rf "${build}" +}