[FIX] install: the EL9, EL10 and Fedora build chain

"dnf install: error: unrecognized arguments" on AlmaLinux 10 and Rocky 10,
on every call: PostgreSQL, build dependencies, pyenv. The tools group got
away with its fallback, which made the trace misleading -- gcc installed,
nothing after it did.

"--skip-unavailable" is a dnf5 option, which the script's own comment
already said while assuming it everywhere. Fedora 41+ ships dnf5, but EL9
and EL10 stay on dnf4, whose equivalent is "--setopt=strict=0". We now ask
dnf what it understands instead of inferring it from the distribution.

Three neighbouring fixes: the "c-development" group does not exist on EL,
where it is called "development"; CRB is enabled through /usr/bin/crb; and
g++ plus the missing headers are installed explicitly. Rust is added only
where wheels are absent, and qpdf is built when the distribution ships one
older than pikepdf demands.

--- FR ---

« dnf install: error: unrecognized arguments » sur AlmaLinux 10 et
Rocky 10, à chaque appel : PostgreSQL, dépendances de compilation, pyenv.
Le groupe d'outils s'en tirait par son repli, ce qui rendait la trace
trompeuse — gcc installé, rien après lui.

« --skip-unavailable » est une option de dnf5, ce que le commentaire du
script disait déjà tout en la supposant partout. Fedora 41+ livre dnf5,
mais EL9 et EL10 restent sur dnf4, dont l'équivalent est
« --setopt=strict=0 ». On demande maintenant à dnf ce qu'il comprend
plutôt que de le déduire de la distribution.

Trois correctifs voisins : le groupe « c-development » n'existe pas sur
EL, où il s'appelle « development » ; CRB s'active par /usr/bin/crb ; et
g++ ainsi que les en-têtes manquants sont posés explicitement. Rust n'est
ajouté que là où les roues manquent, et qpdf est compilé quand la
distribution en livre un plus ancien que ce qu'exige pikepdf.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-11 20:46:07 -04:00
parent 7120732c25
commit a579cc9cf3
4 changed files with 209 additions and 39 deletions

View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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}"
}