Compare commits

...

36 commits

Author SHA1 Message Date
327ff55665 [ADD] llvm and clang, with Sphinx off, for Rust
Rust is not optional. ERPLibre's poetry.lock requires cryptography 46.0.5 with
optional = false, plus jiter, orjson, pydantic-core and rpds-py -- all of which
build Rust from source where no wheel exists, and none exists for s390x. Without
Rust, `pip install cryptography` fails and ERPLibre does not start.

The bootstrap cycle is not the obstacle it looks like: rust-1.90.0 ships an
official s390x-unknown-linux-gnu tarball, verified with HTTP 200, and the host
already carries rustc 1.85.1. What costs is llvm and clang.

Measured before choosing, the way glib2 and iproute2 were: beyond what this port
already has, llvm and clang want libedit, python-psutil, python-sphinx and
python-myst-parser. The last two are documentation only, and behind them sit
docutils, babel, pygments, snowballstemmer, imagesize, alabaster, the
sphinxcontrib family, markdown-it-py and mdit-py-plugins -- some fifteen packages
to render HTML for a compiler whose manual nothing here reads.
-DLLVM_ENABLE_SPHINX=OFF removes all of it.

Their package() ends on `rm -r .../html/{_sources,.buildinfo}`, which Sphinx
creates. Seventeenth appearance of that shape, and the FIRST found before the
build rather than after -- the procedure written in jsoncpp's hook, applied
forwards.

python-psutil is a stage-2 package: built on the host it shipped 41 dist-packages
paths, and the ARTEFACT check caught it.

--- FR ---

Rust n'est pas optionnel. Le poetry.lock d'ERPLibre exige cryptography 46.0.5 en
optional = false, plus jiter, orjson, pydantic-core et rpds-py, qui tous
compilent du Rust depuis les sources là où aucune roue n'existe — et il n'en
existe pas pour s390x. Sans Rust, `pip install cryptography` échoue et ERPLibre ne
démarre pas.

Le cycle d'amorçage n'est pas l'obstacle qu'il paraît : rust-1.90.0 publie une
archive officielle s390x-unknown-linux-gnu, vérifiée en HTTP 200, et l'hôte porte
déjà rustc 1.85.1. Ce qui coûte, c'est llvm et clang.

Mesuré avant de choisir, comme glib2 et iproute2 : au-delà de ce que ce portage a
déjà, llvm et clang veulent libedit, python-psutil, python-sphinx et
python-myst-parser. Les deux derniers ne servent qu'à la documentation, et
derrière eux viennent docutils, babel, pygments, snowballstemmer, imagesize,
alabaster, la famille sphinxcontrib, markdown-it-py et mdit-py-plugins — une
quinzaine de paquets pour produire du HTML sur un compilateur dont personne ici ne
lit le manuel. -DLLVM_ENABLE_SPHINX=OFF supprime tout cela.

Leur package() finit sur `rm -r .../html/{_sources,.buildinfo}`, que Sphinx crée.
Dix-septième apparition de cette forme, et la PREMIÈRE trouvée avant la
construction plutôt qu'après — la procédure inscrite dans le hook de jsoncpp,
appliquée à l'endroit.

python-psutil est un paquet d'étage 2 : bâti sur l'hôte, il livrait 41 chemins
dist-packages, et le test ARTEFACT l'a attrapé.

Assisted-by: Claude Opus 5
2026-08-24 01:57:19 -04:00
51384a64af [ADD] stage 2 is complete: 164 of 164, and it resolves, is clean, and runs
Every package in the list has been rebuilt inside the chroot by the compiler,
linker and libc it produced. The verification, run against repo2 rather than
asserted:

  RESOLVE   resolver satisfied (101 packages), --nodeps OFF
  ARTEFACT  no multiarch, no lib64, no dist-packages, no usr/local
  SONAME    every requested soname has a provider in the repository
  RUN       102 packages installed; bash 5.3.15, uname s390x, glibc 2.44,
            ls, tar, find, sed and pacman all execute

SONAME is the one that matters most. Stage 1 shipped nine sonames at the host's
version and three with no provider at all, and the whole premise of a second
stage was that rebuilding inside the result dissolves them. It did: zero of
either.

python was the last holdout and the most instructive: POSIX_SEMAPHORES_NOT_ENABLED
is now 0. It had been 1 for days because `mount --bind /dev` does not carry
submounts, so the chroot saw an empty 0755 /dev/shm, CPython's configure got
EACCES from sem_open, and _multiprocessing was compiled without SemLock -- a
package that installed cleanly, imported cleanly, and was wrong.

--- FR ---

Chaque paquet de la liste a été rebâti dans le chroot par le compilateur, le lieur
et la libc qu'il a produits. La vérification, passée sur repo2 plutôt
qu'affirmée :

  RESOLVE   résolveur satisfait (101 paquets), --nodeps éteint
  ARTEFACT  ni multiarch, ni lib64, ni dist-packages, ni usr/local
  SONAME    chaque soname demandé a un fournisseur dans le dépôt
  RUN       102 paquets installés ; bash 5.3.15, uname s390x, glibc 2.44,
            ls, tar, find, sed et pacman s'exécutent tous

SONAME est le contrôle qui compte. L'étage 1 livrait neuf sonames à la version de
l'hôte et trois sans aucun fournisseur, et toute la raison d'être d'un second
étage était que rebâtir dans le résultat les dissout. C'est fait : zéro des deux.

python fut le dernier et le plus instructif : POSIX_SEMAPHORES_NOT_ENABLED vaut
désormais 0. Il valait 1 depuis des jours parce que `mount --bind /dev` ne porte
pas les sous-montages : le chroot voyait un /dev/shm vide en 0755, le configure de
CPython recevait EACCES de sem_open, et _multiprocessing était compilé sans
SemLock — un paquet qui s'installait proprement, s'importait proprement, et était
faux.

Assisted-by: Claude Opus 5
2026-08-24 01:35:16 -04:00
0da64c9933 [FIX] rm -rf followed a bind mount into the host's /dev
make_rootfs wiped the rootfs with `rm -rf "$ROOT"` and no check on what was
mounted under it. A diagnostic session had left `mount --rbind /dev "$ROOT/dev"`
in place; the next run deleted straight through it into the real /dev. Every
device node went -- zero, full, random, urandom, tty, console -- and /dev/null
came back later as a regular file created by a redirection, which broke every
`> /dev/null` on the machine and stopped the pass at its first pacman call.

Two guards now, because unmounting alone is not enough: umount_chroot, then
findmnt to CHECK, then refuse. A stale mount is a reason to stop, never a thing
to delete through.

/dev is also no longer written to at all. The bind became --rbind with
--make-rslave, which brings the host's /dev/shm along as the tmpfs it already is
-- so the separate tmpfs and the chmod that reached the host are both gone. That
chmod was the reason this driver could touch /dev in the first place.

The plain --bind was itself the original bug: it does not carry submounts, so the
chroot saw /dev/shm as an empty 0755 stub, CPython's configure got EACCES from
sem_open, and python was built without POSIX semaphores.

--- FR ---

make_rootfs effaçait le rootfs par `rm -rf "$ROOT"` sans vérifier ce qui était
monté dessous. Une session de diagnostic avait laissé `mount --rbind /dev
"$ROOT/dev"` en place ; l'exécution suivante a supprimé au travers, dans le /dev
réel. Tous les nœuds de périphériques ont disparu — zero, full, random, urandom,
tty, console — et /dev/null est revenu en fichier ordinaire créé par une
redirection, ce qui a cassé tout `> /dev/null` sur la machine et arrêté la passe
à son premier appel à pacman.

Deux gardes désormais, car démonter ne suffit pas : umount_chroot, puis findmnt
pour VÉRIFIER, puis refuser. Un montage résiduel est une raison de s'arrêter,
jamais une chose à traverser.

Et /dev n'est plus écrit du tout. Le bind devient --rbind avec --make-rslave, ce
qui apporte le /dev/shm de l'hôte tel qu'il est déjà — un tmpfs — donc le tmpfs
séparé et le chmod qui atteignait l'hôte disparaissent tous deux. Ce chmod était
la raison pour laquelle ce pilote pouvait toucher /dev.

Le --bind simple était lui-même le défaut d'origine : il ne porte pas les
sous-montages, le chroot voyait donc /dev/shm comme une souche vide en 0755, le
configure de CPython recevait EACCES de sem_open, et python était bâti sans
sémaphores POSIX.

Assisted-by: Claude Opus 5
2026-08-24 01:14:35 -04:00
c43289c5ca [FIX] /dev/shm was mounted and unwritable, so python lost its semaphores
sem_open: Permission denied
  checking whether POSIX semaphores are enabled... no

EACCES, not ENOSYS. This port read the failure as a missing mount, mounted the
tmpfs, and got the same result -- because `-o mode=1777` applies only when the
mount is created, and the `mountpoint -q ||` guard skips an existing one. findmnt
showed a tmpfs at /dev/shm while the directory itself was drwxr-xr-x, so the
build user could create nothing in it.

CPython runs sem_open at CONFIGURE time. It concluded the platform has no working
semaphores, set POSIX_SEMAPHORES_NOT_ENABLED, and compiled _multiprocessing
without SemLock -- and every consumer then failed with a message blaming the
platform, days after the cause.

The mode is now set with chmod rather than trusted to the mount option, and the
result is PROVED from inside the chroot as the build user. Every earlier check of
this was made as root and passed while the build kept failing.

--- FR ---

  sem_open: Permission denied
  checking whether POSIX semaphores are enabled... no

EACCES, pas ENOSYS. Ce portage a lu l'échec comme un montage absent, a monté le
tmpfs, et a obtenu le même résultat — car `-o mode=1777` ne s'applique qu'à la
création du montage, et la garde `mountpoint -q ||` saute un montage existant.
findmnt montrait un tmpfs sur /dev/shm alors que le répertoire était drwxr-xr-x :
l'utilisateur de compilation n'y pouvait rien créer.

CPython exécute sem_open au moment de CONFIGURE. Il en a conclu que la plateforme
n'a pas de sémaphores fonctionnels, a posé POSIX_SEMAPHORES_NOT_ENABLED et
compilé _multiprocessing sans SemLock — et chaque consommateur a ensuite échoué
sur un message accusant la plateforme, des jours après la cause.

Le mode est désormais posé par chmod plutôt que confié à l'option de montage, et
le résultat est PROUVÉ depuis l'intérieur du chroot, sous l'utilisateur de
compilation. Toutes les vérifications précédentes avaient été faites en root et
passaient pendant que la construction échouait.

Assisted-by: Claude Opus 5
2026-08-24 00:54:22 -04:00
cf3a4ffdf4 [FIX] ARTEFACT condemned two correct packages
Run against stage 2's output for the first time, the check reported:

  python      17 x s390x-linux-gnu/
  filesystem  11 x usr/local/

Both are correct. CPython names two paths after the build triplet on every
platform -- the Arch x86_64 package ships
_sysconfigdata__linux_x86_64-linux-gnu.py and config-3.14-x86_64-linux-gnu/ --
so the triplet there is CPython's convention, not Debian's layout leaking in. And
creating /usr/local's skeleton is what the filesystem package exists for; the FHS
requires those directories.

Exempted by (package, pattern) pair rather than by package, so python is still
checked for lib64 and dist-packages and filesystem for multiarch. A blanket
exemption is how a real leak gets waved through -- and this check has now
condemned correct code three times: the tcl8.6 grep in sqlite's hook, the
intolerant-rm guard in systemd's, and this.

With the exemptions, stage 2's 209 packages pass: no multiarch, no lib64, no
dist-packages, no usr/local.

--- FR ---

Passé pour la première fois sur la production de l'étage 2, le test signalait :

  python      17 x s390x-linux-gnu/
  filesystem  11 x usr/local/

Les deux sont justes. CPython nomme deux chemins d'après le triplet de
construction sur toute plateforme — le paquet Arch x86_64 livre
_sysconfigdata__linux_x86_64-linux-gnu.py et config-3.14-x86_64-linux-gnu/ — le
triplet y est donc une convention de CPython, non la disposition de Debian qui
s'infiltre. Et créer le squelette de /usr/local est la raison d'être du paquet
filesystem ; le FHS l'exige.

Exemptés par couple (paquet, motif) et non par paquet : python reste contrôlé pour
lib64 et dist-packages, filesystem pour le multiarch. Une exemption globale est la
façon dont une vraie fuite passe — et ce test a désormais condamné du code juste
trois fois : le grep tcl8.6 du hook sqlite, le garde rm de celui de systemd, et
ceci.

Avec les exemptions, les 209 paquets de l'étage 2 passent : ni multiarch, ni
lib64, ni dist-packages, ni usr/local.

Assisted-by: Claude Opus 5
2026-08-24 00:46:33 -04:00
f875649c96 [UPD] note why /dev/shm must exist before python is built 2026-08-24 00:35:07 -04:00
fe9c58c1a3 [FIX] systemd: sixteen man page operations, across five sub-packages
This section was written three times, each catching more:

  1st  man3 only                    -> stopped on man8/*nss*
  2nd  mv and rm of share/man       -> stopped on `ln -s ... installkernel.8.gz`
  3rd  mv, ln and rm                -> stopped on

         install -D -m0644 -t "$pkgdir"/usr/share/man/man1 \
           build/man/init.1

       in package_systemd-sysvcompat(), whose SOURCE is under build/ and whose
       path contains no "man<digit>" component at all.

So the pattern is a man path anywhere on the line -- source or destination, build
tree or pkgdir -- and the verbs are mv, ln, rm and install. Nine neutralised,
three made tolerant, `install -d` left alone because creating an empty directory
harms nothing and other lines put real files in the same trees.

Three rounds on one package is what guessing the shape of "all of them" costs
instead of listing them. The listing is in the hook now.

--- FR ---

Cette section a été écrite trois fois, chacune en attrapant davantage :

  1re  man3 seul                    -> arrêt sur man8/*nss*
  2e   mv et rm de share/man        -> arrêt sur `ln -s ... installkernel.8.gz`
  3e   mv, ln et rm                 -> arrêt sur

         install -D -m0644 -t "$pkgdir"/usr/share/man/man1 \
           build/man/init.1

       dans package_systemd-sysvcompat(), dont la SOURCE est sous build/ et dont
       le chemin ne contient aucun composant « man<chiffre> ».

Le motif est donc un chemin de manuel n'importe où sur la ligne — source ou
destination, arbre de construction ou pkgdir — et les verbes sont mv, ln, rm et
install. Neuf neutralisés, trois rendus tolérants, `install -d` laissé tel quel :
créer un répertoire vide ne nuit pas, et d'autres lignes y installent de vrais
fichiers.

Trois tours sur un seul paquet, voilà ce que coûte le fait de deviner la forme de
« tous » au lieu de les énumérer. L'énumération est dans le hook.

Assisted-by: Claude Opus 5
2026-08-24 00:33:37 -04:00
0e00b161c4 [FIX] the chroot had no /dev/shm, and systemd links man pages too
nss failed with

  ImportError: This platform lacks a functioning sem_open implementation.
  https://github.com/python/cpython/issues/48020

multiprocessing.Semaphore is built on POSIX named semaphores, which glibc
implements as files under /dev/shm. The chroot had no such mount, sem_open
returned ENOSYS, and CPython reported it as the PLATFORM lacking the feature --
which reads like an s390x limitation and is a missing tmpfs.

`mount --bind /dev` does not carry submounts. That is why /dev/pts already had a
line of its own, and /dev/shm was simply missing from the same list. Mounted as
its own tmpfs, mode 1777, and added to the umount order ahead of /dev.

systemd's man page hook listed mv and rm and missed ln, so package_systemd() got
one step further and stopped on a symlink between two man pages where neither
exists. Sixteenth instance, and the lesson is narrower than the last: the
procedure said grep for the output PATHS, and the paths were found -- what was
missed was a VERB. Seven moves and links now, up from five.

--- FR ---

nss échouait sur

  ImportError: This platform lacks a functioning sem_open implementation.
  https://github.com/python/cpython/issues/48020

multiprocessing.Semaphore repose sur les sémaphores nommés POSIX, que glibc
implémente en fichiers sous /dev/shm. Le chroot n'avait pas ce montage, sem_open
renvoyait ENOSYS, et CPython l'a rapporté comme une absence de la PLATEFORME — ce
qui se lit comme une limite de s390x et n'est qu'un tmpfs manquant.

`mount --bind /dev` ne porte pas les sous-montages. C'est pourquoi /dev/pts avait
déjà sa ligne, et /dev/shm manquait simplement dans la même liste. Monté en tmpfs
propre, mode 1777, et ajouté au démontage avant /dev.

Le hook des pages de manuel de systemd listait mv et rm et oubliait ln :
package_systemd() est allé un cran plus loin et s'est arrêté sur un lien entre
deux pages dont aucune n'existe. Seizième occurrence, et la leçon est plus fine
que la précédente : la procédure disait de chercher les CHEMINS de sortie, et les
chemins étaient trouvés — ce qui manquait était un VERBE. Sept déplacements et
liens désormais, contre cinq.

Assisted-by: Claude Opus 5
2026-08-24 00:29:13 -04:00
61abc5aef9 [FIX] systemd: every man page operation at once, not one per pass
mv: cannot stat '<pkgdir>/usr/share/man/man3': No such file or directory

Fifteenth instance, and the first where the whole class was handled before the
failures arrived. -Dman=disabled means NO man pages exist, and package() touches
them six times -- three moves into split packages, three removals of pages Arch
does not ship.

Treated by kind rather than by name: the moves are neutralised, because with no
pages there is nothing to move; the removals get -f, because their intent still
holds on a machine that HAS the DocBook stack, and one of them also removes a
binary that must still go. Five moves, three removals, counted and reported by
the hook.

This is the procedure written into jsoncpp's hook one commit earlier, applied
forwards: after disabling documentation, grep package() for the output paths.
Chasing man3 alone would have cost five more passes.

The first guard for it matched any `rm` without a dash and condemned correct
code -- systemd removes plenty that is not documentation. Same mistake as the
blanket `grep tcl8.6` in sqlite's hook, and caught the same way.

--- FR ---

  mv: cannot stat '<pkgdir>/usr/share/man/man3': No such file or directory

Quinzième occurrence, et la première où toute la classe est traitée avant que les
échecs n'arrivent. -Dman=disabled signifie qu'AUCUNE page n'existe, et package()
y touche six fois — trois déplacements vers des sous-paquets, trois suppressions
de pages qu'Arch ne livre pas.

Traitées par nature et non par nom : les déplacements sont neutralisés, puisqu'il
n'y a rien à déplacer ; les suppressions reçoivent -f, car leur intention tient
toujours sur une machine dotée de la pile DocBook, et l'une retire aussi un
binaire qui doit bien partir. Cinq déplacements, trois suppressions, comptés et
rapportés par le hook.

C'est la procédure inscrite dans le hook de jsoncpp un commit plus tôt, appliquée
à l'endroit : après avoir désactivé une documentation, chercher dans package() les
chemins de sortie. Poursuivre man3 seul aurait coûté cinq passes de plus.

Le premier garde écrit pour cela attrapait tout `rm` sans tiret et condamnait du
code juste — systemd supprime beaucoup qui n'est pas de la documentation. Même
erreur que le `grep tcl8.6` global du hook sqlite, attrapée de la même façon.

Assisted-by: Claude Opus 5
2026-08-24 00:21:48 -04:00
376c6d6a91 [UPD] hoist gyp with the Python set, ahead of nss 2026-08-24 00:17:11 -04:00
f6c0969929 [ADD] gyp for nss, and jsoncpp's doxygen output
nss will not configure without gyp:

  Building NSS requires an installation of gyp: https://gyp.gsrc.io

Google's build-file generator, which nss uses to produce its ninja files. It is a
real Arch package and a Python program, so it joins the Python set.

jsoncpp is the fourteenth instance of this shape and the sixth of my own making.
Removing the doxybuild.py call stopped the API html being generated; package()
still copied the directory it would have produced, and the name carries pkgver so
the path is not greppable without knowing the version.

The rule is mechanical enough now to state as a procedure rather than a lesson:
after removing a documentation build, grep package() for the OUTPUT directory
name, not for the tool. The tool appears in build(); the path appears in
package(), and it is the path that fails.

--- FR ---

nss ne se configure pas sans gyp :

  Building NSS requires an installation of gyp: https://gyp.gsrc.io

Le générateur de fichiers de construction de Google, dont nss se sert pour produire
ses fichiers ninja. C'est un vrai paquet Arch et un programme Python : il rejoint
l'ensemble Python.

jsoncpp est la quatorzième occurrence de cette forme, et la sixième de ma main.
Retirer l'appel à doxybuild.py a arrêté la génération du html d'API ; package()
copiait toujours le répertoire qu'il aurait produit, et son nom porte pkgver — le
chemin n'est donc pas trouvable par grep sans connaître la version.

La règle est assez mécanique pour être énoncée en procédure plutôt qu'en leçon :
après avoir retiré une construction de documentation, chercher dans package() le
nom du répertoire de SORTIE, pas celui de l'outil. L'outil est dans build() ; le
chemin est dans package(), et c'est le chemin qui échoue.

Assisted-by: Claude Opus 5
2026-08-24 00:03:26 -04:00
8d9e8a4dce [FIX] groff's remaining lists, systemd man pages, git without libsecret
groff was the third round on the same package: DOC_GNU_EPS, then
PROCESSEDEXAMPLEFILES_PS, then HDTBL -- and mom's examples were waiting behind
those. groff renders its own manuals and every contrib package's examples with
the groff being built, each list a separate variable. Finding them one failure at
a time cost three passes; all seven are emptied together now.

systemd is the fourth package to stop on missing DocBook DATA rather than a
missing tool -- xsltproc exists, our libxslt supplies it:

  compilation error: file ../systemd/man/custom-man.xsl line 12 element import

It has the largest man page set here, which is the strongest argument in this port
for shipping docbook-xsl eventually.

git's libsecret helper needed THREE places, one spanning two lines. The first
version asserted "expected one _make, got 2" and stopped -- the guard working.
Replacing the install's first line alone would have left its continuation as a
command of its own, which is the binutils trap again.

--- FR ---

groff en était au troisième tour sur le même paquet : DOC_GNU_EPS, puis
PROCESSEDEXAMPLEFILES_PS, puis HDTBL — et les exemples de mom attendaient
derrière. groff rend ses propres manuels et les exemples de chaque contrib avec le
groff en cours de construction, chaque liste étant une variable distincte. Les
trouver un échec à la fois a coûté trois passes ; les sept sont vidées ensemble.

systemd est le quatrième paquet à s'arrêter sur des DONNÉES DocBook absentes et
non sur un outil manquant — xsltproc existe, notre libxslt le fournit :

  compilation error: file ../systemd/man/custom-man.xsl line 12 element import

Il porte le plus grand ensemble de pages de manuel du portage : c'est le meilleur
argument pour livrer docbook-xsl à terme.

L'aide libsecret de git demandait TROIS endroits, dont un sur deux lignes. La
première version a affirmé « expected one _make, got 2 » et s'est arrêtée — le
garde faisant son travail. Remplacer la première ligne de l'installation aurait
laissé sa continuation comme commande à part, soit le piège de binutils à nouveau.

Assisted-by: Claude Opus 5
2026-08-23 23:58:32 -04:00
3d2a1485af [FIX] the Python bootstrap installed older code than it claimed
python-packaging's PKGBUILD says pkgver=26.3 and the installed module reported
25.0. The vendored builder selected by _bootstrap=1 carries its OWN copy of each
project, older than the release the PKGBUILD names. Nothing about the package
looked wrong; only `packaging.__version__` disagreed with its own name, and the
mismatch surfaced two packages later:

  python-vcs-versioning: packaging>=26.2

a floor our own package met on paper and not in fact.

_bootstrap is now a probe evaluated by makepkg, in the build environment: if
`build` and `installer` import, the real path works and the vendored copies are
not wanted. A hook could not decide this -- hooks run on the HOST even for stage
2, so a probe written there cannot answer for the chroot.

The license install then had to handle both layouts, since the source tree is a
git clone under $pkgname when bootstrapping and a release tarball otherwise.
Hardcoding either breaks the other, and this hook had hardcoded the clone -- right
only while _bootstrap was always 1.

--- FR ---

Le PKGBUILD de python-packaging annonce pkgver=26.3 et le module installé
rapportait 25.0. Le constructeur embarqué que sélectionne _bootstrap=1 porte ses
PROPRES copies de chaque projet, plus anciennes que la version nommée. Rien dans
le paquet n'avait l'air faux ; seul `packaging.__version__` contredisait son
propre nom, et l'écart est apparu deux paquets plus loin :

  python-vcs-versioning: packaging>=26.2

un plancher que notre paquet satisfaisait sur le papier et non en fait.

_bootstrap est désormais une sonde évaluée par makepkg, dans l'environnement de
construction : si `build` et `installer` s'importent, la voie normale fonctionne et
les copies embarquées ne sont pas voulues. Un hook ne pouvait pas trancher — les
hooks tournent sur l'HÔTE même pour l'étage 2, donc une sonde écrite là ne peut
pas répondre pour le chroot.

L'installation de la licence devait alors gérer les deux dispositions, l'arbre
source étant un clone git sous $pkgname en mode bootstrap et une archive sinon.
Écrire l'une en dur casse l'autre, et ce hook avait écrit le clone en dur — juste
seulement tant que _bootstrap valait toujours 1.

Assisted-by: Claude Opus 5
2026-08-23 23:50:06 -04:00
2b0139e99f [FIX] xz: the flag was already there, and lost to position
--disable-doxygen was added and doxygen still ran. config.log recorded why:

  $ ./configure --disable-doc --disable-doxygen --prefix=/usr \
      --disable-rpath --enable-doxygen --enable-werror

The PKGBUILD passes --enable-doxygen further along the same line, and autoconf
takes the LAST occurrence. The first version of this section inserted its flag
right after ./configure, where it could only lose.

This port had already learned the rule from the other side: the arch-meson
wrapper APPENDS --auto-features auto precisely so it wins. Here the same fact was
applied backwards, and the symptom was indistinguishable from a flag that does
not exist -- which is what sent the search through configure's own logic first,
where the default turned out to be `no` all along.

The existing option is flipped in place rather than a second one added. One
statement per decision: two contradictory flags on one command line is how the
next reader spends an hour in config.log.

--- FR ---

--disable-doxygen avait été ajouté et doxygen tournait encore. config.log en
donnait la raison :

  $ ./configure --disable-doc --disable-doxygen --prefix=/usr \
      --disable-rpath --enable-doxygen --enable-werror

Le PKGBUILD passe --enable-doxygen plus loin sur la même ligne, et autoconf retient
la DERNIÈRE occurrence. La première version de cette section insérait son drapeau
juste après ./configure, là où il ne pouvait que perdre.

Ce portage avait déjà appris la règle par l'autre bout : l'enveloppe arch-meson
AJOUTE --auto-features auto à la fin précisément pour gagner. Ici le même fait a
été appliqué à l'envers, et le symptôme était indiscernable d'un drapeau
inexistant — ce qui a d'abord envoyé la recherche dans la logique de configure, où
le défaut s'est avéré être « no » depuis le début.

L'option existante est retournée sur place plutôt qu'une seconde ajoutée. Une
décision, une ligne : deux drapeaux contradictoires sur une même commande, c'est
une heure de config.log pour le prochain lecteur.

Assisted-by: Claude Opus 5
2026-08-23 23:44:36 -04:00
c375f994fe [FIX] tpm2-tss without integration tests, p11-kit's doc sub-package
tpm2-tss had three obstacles, each hidden by the one before: a missing AX_* macro
(fixed by autoconf-archive), then cmocka, now

  configure: error: Missing required program 'ss': ensure it is installed

`ss` comes from iproute2, and adding iproute2 was the first idea -- it is in
Arch's base group and belongs here eventually. Measured first: it depends on
libbpf, iptables and linux-atm, none of them present, and libbpf is the
dependency systemd was already spared for the same reason. Three packages to
satisfy a test-suite prerequisite, or --disable-integration. The tests are not
run; --nocheck covers the whole first pass.

p11-kit is the thirteenth instance of this shape and the third layer of the same
package: the build flag, the _pick that staged the docs, and now the sub-package
function that moves what _pick staged. Five of these thirteen have been mine, so
the rule is worth writing down: disabling a documentation build means following it
through the entire package() chain, not just switching it off.

--- FR ---

tpm2-tss avait trois obstacles, chacun masqué par le précédent : une macro AX_*
absente (réglée par autoconf-archive), puis cmocka, et maintenant

  configure: error: Missing required program 'ss': ensure it is installed

`ss` vient d'iproute2, et l'ajouter fut la première idée — il est dans le groupe
base d'Arch et y a sa place à terme. Mesuré d'abord : il dépend de libbpf,
iptables et linux-atm, aucun présent, et libbpf est la dépendance dont systemd
avait déjà été dispensé pour la même raison. Trois paquets pour satisfaire un
prérequis de suite de tests, ou --disable-integration. Les tests ne tournent pas :
--nocheck couvre toute la première passe.

p11-kit est la treizième occurrence de cette forme et la troisième couche du même
paquet : le drapeau de construction, le _pick qui préparait la documentation, et
maintenant la fonction de sous-paquet qui déplace ce que _pick préparait. Cinq de
ces treize sont de ma main : la règle mérite d'être écrite — désactiver une
documentation, c'est la suivre dans toute la chaîne package(), pas seulement
l'éteindre.

Assisted-by: Claude Opus 5
2026-08-23 23:33:38 -04:00
111d4c80b3 [FIX] git: the opt-out is NO_RUST, and Rust is now the default
Removing WITH_RUST=1 changed nothing -- cargo was still invoked:

  CARGO target/release/libgitcore.a
  /bin/sh: line 1: cargo: command not found

The Makefile reads `ifndef NO_RUST`, so Rust is built BY DEFAULT and the opt-out
is NO_RUST. WITH_RUST=1 sets a variable the Makefile never reads. Deleting it was
correct and insufficient, the worst combination: the hook reported success and the
build failed identically.

This is a stopgap, not a decision. Rust IS supportable here, and measured rather
than assumed: rust-1.90.0-s390x-unknown-linux-gnu.tar.xz answers 200, and the
host already carries rustc 1.85.1. The cost is llvm and clang, which Arch's rust
lists as makedepends -- large, not impossible.

It also cannot be avoided for long. ERPLibre's poetry.lock needs cryptography
46.0.5, jiter, orjson, pydantic-core and rpds-py, all of which build Rust from
source where no wheel exists -- and none exists for s390x.

--- FR ---

Retirer WITH_RUST=1 n'a rien changé — cargo était toujours appelé :

  CARGO target/release/libgitcore.a
  /bin/sh: line 1: cargo: command not found

Le Makefile dit `ifndef NO_RUST` : Rust est bâti PAR DÉFAUT et l'échappatoire est
NO_RUST. WITH_RUST=1 définit une variable que le Makefile ne lit jamais. La
supprimer était juste et insuffisant, la pire combinaison : le hook annonçait un
succès et la construction échouait à l'identique.

C'est un palliatif, pas une décision. Rust EST supportable ici, et mesuré plutôt
que supposé : rust-1.90.0-s390x-unknown-linux-gnu.tar.xz répond 200, et l'hôte
porte déjà rustc 1.85.1. Le coût est llvm et clang, que le rust d'Arch liste en
makedepends — lourd, pas impossible.

Et cela ne pourra pas être évité longtemps. Le poetry.lock d'ERPLibre exige
cryptography 46.0.5, jiter, orjson, pydantic-core et rpds-py, qui tous compilent
du Rust depuis les sources là où aucune roue n'existe — et il n'en existe pas pour
s390x.

Assisted-by: Claude Opus 5
2026-08-23 23:09:18 -04:00
29da076277 [FIX] host-extracted sources linked to paths the chroot cannot see
nss failed with

  /build/nss/PKGBUILD: line 55: ../certdata2pem.py: No such file or directory

on a file that was plainly there. makepkg links a PKGBUILD local source files
into srcdir, and having extracted on the HOST it wrote the host absolute path.
Inside the chroot that path does not exist; only /build does.

No such file or directory for an executable is usually its interpreter, which
sent the first guess to the shebang -- #!/usr/bin/python, with python, python3
and python3.14 all present. It was the script itself, reached through a link to
nowhere.

Only links under the work directory are repointed: a link to anywhere else was
not made by this mechanism and is not ours to redirect.

--- FR ---

nss echouait sur

  /build/nss/PKGBUILD: line 55: ../certdata2pem.py: No such file or directory

pour un fichier bien present. makepkg lie les sources locales d un PKGBUILD dans
srcdir, et ayant extrait sur l HOTE il a ecrit le chemin absolu de l hote. Dans le
chroot ce chemin n existe pas ; seul /build existe.

No such file or directory pour un executable designe d ordinaire son interprete,
ce qui a envoye la premiere hypothese vers le shebang -- #!/usr/bin/python, avec
python, python3 et python3.14 tous presents. C etait le script lui-meme, atteint
par un lien vers nulle part.

Seuls les liens sous le repertoire de travail sont rediriges : un lien ailleurs n a
pas ete cree par ce mecanisme et ne nous appartient pas.

Assisted-by: Claude Opus 5
2026-08-23 23:02:24 -04:00
41f0bc4897 [FIX] groff: the second consumer of a figure that is not built
Emptying PROCESSEDEXAMPLEFILES_PS was not enough:

  contrib/hdtbl/examples/mixed_pickles.roff:51: error: cannot open 'gnu.eps'
  fatal error: PSPIC failed to include 'gnu.eps'

hdtbl renders its example tables to PostScript and one of them shows the GNU
logo. Rather than chase a third, the consumers were COUNTED: exactly two files
include it, doc/webpage.ms and mixed_pickles.roff. Both lists are emptied, and
the hook now fails loudly if a third ever appears -- so the next reader gets a
sentence instead of a PSPIC error.

Considered and rejected: supplying a placeholder gnu.eps. It would have ended the
whack-a-mole in one line, and it would have put a blank box where a logo belongs
in a document this port then ships. Not rendering the document at all is honest;
rendering it wrong is not.

--- FR ---

Vider PROCESSEDEXAMPLEFILES_PS ne suffisait pas :

  contrib/hdtbl/examples/mixed_pickles.roff:51: error: cannot open 'gnu.eps'
  fatal error: PSPIC failed to include 'gnu.eps'

hdtbl rend ses tables d'exemple en PostScript, et l'une montre le logo GNU.
Plutôt que d'en poursuivre un troisième, les consommateurs ont été COMPTÉS :
exactement deux fichiers l'incluent, doc/webpage.ms et mixed_pickles.roff. Les
deux listes sont vidées, et le hook échoue bruyamment si un troisième apparaît —
le prochain lecteur aura une phrase au lieu d'une erreur PSPIC.

Envisagé puis rejeté : fournir un gnu.eps de remplacement. Cela aurait clos la
poursuite en une ligne, et aurait mis un cadre vide là où un logo doit être, dans
un document que ce portage livre ensuite. Ne pas rendre le document est honnête ;
le rendre faux ne l'est pas.

Assisted-by: Claude Opus 5
2026-08-23 22:49:24 -04:00
7fa489b5bb [FIX] p11-kit's gtk-doc split, tpm2-tss without cmocka
p11-kit is the twelfth instance of removing a documentation build and leaving its
install: -D gtk_doc=false stopped the docs being made, and package() still ran
`_pick doc "$pkgdir"/usr/share/gtk-doc`. _pick is Arch's helper for moving a path
into a sub-package and it is as fatal on a missing path as a bare mv.

tpm2-tss had two obstacles and the first hid the second. Before autoconf-archive
existed it died on

  configure.ac:139: error: undefined or overquoted macro: AC_MSG_WARN

naming a macro that exists -- an AX_* macro was missing, m4 quoting came apart,
and autoconf blamed the next thing it recognised. With the archive installed,
configure gets far enough to ask for cmocka, a C unit-test framework wanted only
for tests this pass does not run.

--- FR ---

p11-kit est la douzième occurrence du retrait d'une construction de documentation
sans son installation : -D gtk_doc=false a arrêté la production, et package()
exécutait toujours `_pick doc "$pkgdir"/usr/share/gtk-doc`. _pick est l'aide
d'Arch pour déplacer un chemin vers un sous-paquet, et elle est aussi fatale
qu'un mv nu sur un chemin absent.

tpm2-tss avait deux obstacles, et le premier masquait le second. Avant l'existence
d'autoconf-archive il mourait sur

  configure.ac:139: error: undefined or overquoted macro: AC_MSG_WARN

nommant une macro qui existe — une macro AX_* manquait, la citation m4 s'est
défaite, et autoconf a accusé la chose suivante qu'il reconnaissait. L'archive
installée, configure va assez loin pour réclamer cmocka, cadre de tests unitaires
en C voulu seulement pour des tests que cette passe ne lance pas.

Assisted-by: Claude Opus 5
2026-08-23 22:33:58 -04:00
430b49aae2 [FIX] xz: two doc conditionals, and the flag that is not in the help
--disable-doc was added and doxygen still ran. The API reference sits behind its
own automake conditional, and the generated Makefile shows it plainly:

  @COND_DOXYGEN_TRUE@$(top_builddir)/doc/api/index.html: ...

with the prefix stripped, so configure had set it true. Two conditionals in one
package: --disable-doc for the man pages, --disable-doxygen for the API
reference. This is the third documentation toolchain xz has asked for, after
po4a.

--disable-doxygen is NOT in configure's help text -- only --enable-doxygen is --
and it works anyway. That was checked rather than assumed: AC_ARG_ENABLE accepts
both forms, and `enable_doxygen` is a real variable the script tests. groff
taught the same lesson from the other side, where no such option existed and an
invented flag would have drawn a warning and changed nothing.

--- FR ---

--disable-doc avait été ajouté et doxygen tournait encore. La référence d'API est
derrière sa propre condition automake, et le Makefile généré le montre :

  @COND_DOXYGEN_TRUE@$(top_builddir)/doc/api/index.html: ...

préfixe retiré, donc configure l'avait mise à vrai. Deux conditions dans un même
paquet : --disable-doc pour les pages de manuel, --disable-doxygen pour l'API.
Troisième chaîne de documentation que xz réclame, après po4a.

--disable-doxygen n'est PAS dans l'aide de configure — seul --enable-doxygen y
est — et fonctionne quand même. Cela a été vérifié plutôt que supposé :
AC_ARG_ENABLE accepte les deux formes, et `enable_doxygen` est une variable que
le script teste réellement. groff a enseigné la même leçon par l'autre bout, où
aucune option n'existait et où un drapeau inventé n'aurait tiré qu'un
avertissement sans rien changer.

Assisted-by: Claude Opus 5
2026-08-23 22:18:30 -04:00
f25a34eaed [UPD] hoist fastjsonschema and vcs-versioning with the Python set 2026-08-23 22:17:08 -04:00
75cff944a8 [FIX] binutils records a usr/lib64 path, and cmake deletes html it did not make
pacman refused the rebuilt binutils:

  binutils: <root>/usr/lib64 exists in filesystem (owned by filesystem)
  binutils: <root>/usr/lib64/libiberty.a exists in filesystem

--libdir=/usr/lib was not enough. libiberty installs into
$(libdir)$(MULTIOSSUBDIR), and gcc reports ../lib64 for
-print-multi-os-directory on s390x. In the installed system that resolves
through the symlink filesystem now provides, so the file lands in /usr/lib --
but pkgdir has no symlink, so make creates a real pkg/usr/lib64/ and makepkg
records the literal path. The two fixes are not alternatives: the symlink is what
keeps meson and cmake choosing lib, and this moves the one file that still writes
through the multi-os subdirectory.

cmake is the eleventh instance of removing a documentation build and leaving its
cleanup: with Sphinx off there is no html, and the PKGBUILD deletes _sources from
it. rm -rf rather than deleted -- on a machine with Sphinx it is real.

The binutils hook also assumed package_binutils(). binutils is not a split
package; its own assertion caught that.

--- FR ---

pacman a refusé le binutils reconstruit :

  binutils: <root>/usr/lib64 exists in filesystem (owned by filesystem)
  binutils: <root>/usr/lib64/libiberty.a exists in filesystem

--libdir=/usr/lib ne suffisait pas. libiberty s'installe dans
$(libdir)$(MULTIOSSUBDIR), et gcc annonce ../lib64 pour
-print-multi-os-directory sur s390x. Dans le système installé cela se résout par
le lien que filesystem fournit désormais, donc le fichier atterrit dans
/usr/lib — mais pkgdir n'a pas de lien : make crée un vrai pkg/usr/lib64/ et
makepkg enregistre le chemin littéral. Les deux correctifs ne sont pas des
alternatives : le lien est ce qui fait choisir lib à meson et cmake, et celui-ci
déplace le seul fichier qui écrive encore par le sous-répertoire multi-os.

cmake est la onzième occurrence du retrait d'une documentation sans son nettoyage :
sans Sphinx il n'y a pas de html, et le PKGBUILD en supprime _sources. rm -rf
plutôt que supprimé — sur une machine dotée de Sphinx, il est réel.

Le hook binutils supposait aussi package_binutils(). binutils n'est pas un paquet
scindé ; sa propre assertion l'a arrêté.

Assisted-by: Claude Opus 5
2026-08-23 22:05:26 -04:00
6656f07e2b [ADD] python-vcs-versioning, the half setuptools-scm lost
from vcs_versioning import Configuration
  ModuleNotFoundError: No module named vcs_versioning

setuptools-scm was split upstream and its own build imports the half that left.
The traceback runs through setuptools build_meta, so what the log showed for
several passes was

  ERROR Backend subprocess exited when trying to invoke build_wheel

a backend complaint about a package that had been divided in two. Fifth Python
build-time package this port has had to add, after flit_core, setuptools,
poetry-core and hatchling.

--- FR ---

  from vcs_versioning import Configuration
  ModuleNotFoundError: No module named vcs_versioning

setuptools-scm a ete scinde en amont, et sa propre construction importe la moitie
partie. La trace passe par build_meta de setuptools : ce que le journal montrait
depuis plusieurs passes etait

  ERROR Backend subprocess exited when trying to invoke build_wheel

une plainte de moteur au sujet d un paquet coupe en deux. Cinquieme paquet Python
de construction que ce portage doit ajouter, apres flit_core, setuptools,
poetry-core et hatchling.

Assisted-by: Claude Opus 5
2026-08-23 21:47:54 -04:00
04a55bc74c [FIX] sqlite: tcl 9 keeps its library inside the binary
The hook's guard reported "no tcl dir under usr/lib". What sqlite had created
instead was a directory literally named `zipfs:`

  <pkgdir>/sqlite/zipfs:/lib/tcl/tcl_library/

The generated Makefile explains itself:

  # TCLLIBDIR = where to install the tcl plugin. If this is empty, it
  TCLLIBDIR = //zipfs:/lib/tcl/tcl_library/sqlite3.53.4

sqlite asks tcl where its library lives and installs the plugin beside it. In tcl
8.6 that was a real directory. tcl 9 ships its library INSIDE the executable, in
a zipfs mount, so the answer is a path only tcl can open -- and sqlite used it as
a filesystem path, creating a directory whose name contains a colon.

This is the deferred "libtcl8.6 versus our tcl 9.0" item in its final form: not a
version number to update but a change in what tcl considers a path. TCLLIBDIR now
points at a real directory, versioned from tclsh.

--- FR ---

Le garde du hook signalait « no tcl dir under usr/lib ». Ce que sqlite avait créé
était un répertoire littéralement nommé `zipfs:`

  <pkgdir>/sqlite/zipfs:/lib/tcl/tcl_library/

Le Makefile généré s'explique lui-même :

  # TCLLIBDIR = where to install the tcl plugin. If this is empty, it
  TCLLIBDIR = //zipfs:/lib/tcl/tcl_library/sqlite3.53.4

sqlite demande à tcl où vit sa bibliothèque et installe le greffon à côté. En tcl
8.6 c'était un vrai répertoire. tcl 9 livre sa bibliothèque DANS l'exécutable, en
montage zipfs : la réponse est un chemin que seul tcl sait ouvrir, et sqlite l'a
traité comme un chemin de fichiers, créant un répertoire dont le nom contient
deux points.

C'est l'élément différé « libtcl8.6 contre notre tcl 9.0 » dans sa forme finale :
non pas un numéro de version à mettre à jour, mais un changement de ce que tcl
appelle un chemin. TCLLIBDIR vise désormais un vrai répertoire, sa version lue
depuis tclsh.

Assisted-by: Claude Opus 5
2026-08-23 21:19:23 -04:00
ac5e5f3892 [FIX] groff: the figure and the document that includes it
Emptying DOC_GNU_EPS stopped the GNU logo being built and left the document that
includes it:

  doc/webpage.ms:45: error: cannot open gnu.eps
  doc/webpage.ms:45: fatal error: PSPIC failed to include gnu.eps

Tenth time in this port, fourth of my own making. PROCESSEDEXAMPLEFILES_PS lists
webpage.ps and grnexmpl.ps, and the HTML variant directly above it is ALREADY
empty with its real value left as a comment -- groff configure empties that one
when the tools are missing. This does the same for PS, which is the decision
groff would have made had it checked.

--- FR ---

Vider DOC_GNU_EPS a empeche la construction du logo GNU et laisse en place le
document qui l inclut :

  doc/webpage.ms:45: error: cannot open gnu.eps
  doc/webpage.ms:45: fatal error: PSPIC failed to include gnu.eps

Dixieme fois dans ce portage, quatrieme de ma main. PROCESSEDEXAMPLEFILES_PS
liste webpage.ps et grnexmpl.ps, et la variante HTML juste au-dessus est DEJA
vide, sa vraie valeur laissee en commentaire — le configure de groff vide
celle-la quand les outils manquent. Ceci fait de meme pour PS : la decision que
groff aurait prise s il avait verifie.

Assisted-by: Claude Opus 5
2026-08-23 21:03:23 -04:00
fceb5936a5 [FIX] --nodeps once skips versions, not dependencies
Two install commands passed one --nodeps and looked like they were skipping
dependencies. pacman documents the difference: -d skips dependency VERSION
checks, -dd skips them entirely. So cython could not be installed --

  warning: cannot resolve python-pygments, a dependency of cython
  :: unable to satisfy dependency python-numpy required by cython

-- and the numbers matter. cython is wanted here only as a BUILD tool, for
libseccomp Python binding, while its declared RUNTIME dependencies are numpy and
pygments, and numpy brings BLAS and LAPACK. A single -d nearly bought a numerical
stack to compile one .pyx file.

The message named numpy and numpy was not the subject. The subject was a missing
letter d.

python-poetry-core joins the list: the fourth Python build backend needed here
after flit_core, setuptools and hatchling.

--- FR ---

Deux commandes d installation passaient un seul --nodeps et semblaient sauter les
dependances. pacman documente la difference : -d saute les controles de VERSION,
-dd les saute entierement. cython ne pouvait donc pas etre installe --

  warning: cannot resolve python-pygments, a dependency of cython
  :: unable to satisfy dependency python-numpy required by cython

-- et les chiffres comptent. cython n est voulu ici que comme OUTIL de
construction, pour la liaison Python de libseccomp, alors que ses dependances d
EXECUTION declarees sont numpy et pygments, et numpy tire BLAS et LAPACK. Un seul
-d a failli faire compiler une pile numerique pour un fichier .pyx.

Le message nommait numpy, et numpy n etait pas le sujet. Le sujet etait un d
manquant.

python-poetry-core rejoint la liste : quatrieme moteur de construction Python
necessaire ici, apres flit_core, setuptools et hatchling.

Assisted-by: Claude Opus 5
2026-08-23 20:47:32 -04:00
0963698d4b [FIX] xz has two doc toolchains, and libpsl needs data not a tool
xz got past po4a and stopped on

  ../../../doxygen/update-doxygen: doxygen command not found

Two documentation toolchains in one package: po4a for the translated man pages,
doxygen for the liblzma API reference. --disable-doc is xz own switch, checked
present in configure before use.

libpsl stopped on

  make: *** No rule to make target
    /usr/share/publicsuffix/effective_tld_names.dat, needed by ...

publicsuffix-list is DATA. libpsl compiles the Public Suffix List into a lookup
table, so the list is an input to the build -- the first entry in this port whose
absence is neither a program nor a library, and it reads exactly like a broken
Makefile.

--- FR ---

xz a passe po4a et s est arrete sur

  ../../../doxygen/update-doxygen: doxygen command not found

Deux chaines de documentation dans un seul paquet : po4a pour les pages de manuel
traduites, doxygen pour la reference d API de liblzma. --disable-doc est son
propre interrupteur, verifie present dans configure avant usage.

libpsl s est arrete sur

  make: *** No rule to make target
    /usr/share/publicsuffix/effective_tld_names.dat, needed by ...

publicsuffix-list est une DONNEE. libpsl compile la Public Suffix List en table
de correspondance : la liste est une entree de la construction — premiere entree
de ce portage dont l absence n est ni un programme ni une bibliotheque, et elle se
lit exactement comme un Makefile casse.

Assisted-by: Claude Opus 5
2026-08-23 20:19:45 -04:00
9a598fdd4d [FIX] cmake: no Sphinx manual
CMake Error at Utilities/Sphinx/CMakeLists.txt:52 (message):

cmake builds its own manual and man pages with Sphinx, asked for by --sphinx-man
and --sphinx-html. python-sphinx is a large Python documentation stack and cmake
is wanted here as a build tool.

Both flags, and they are bootstrap arguments rather than -D options -- cmake
configures itself with its own script before it can run itself. Dropping one
leaves the other pulling Sphinx in and the error is identical, which is how a
half-fix looks like no fix.

--- FR ---

  CMake Error at Utilities/Sphinx/CMakeLists.txt:52 (message):

cmake batit son propre manuel et ses pages de manuel avec Sphinx, reclames par
--sphinx-man et --sphinx-html. python-sphinx est une large pile de documentation
Python, et cmake est voulu ici comme outil de construction.

Les deux drapeaux, et ce sont des arguments d amorcage plutot que des options -D :
cmake se configure avec son propre script avant de pouvoir s executer lui-meme.
N en retirer qu un laisse l autre tirer Sphinx et l erreur est identique, ce qui
donne a une demi-correction l apparence d aucune.

Assisted-by: Claude Opus 5
2026-08-23 20:02:59 -04:00
3267c6484c [FIX] binutils: gold's s390 support needs a target binutils is deleting
This section was written twice, and the first version was wrong instructively.

gold would not link: s390.cc references gold::gold_error_at_location<32, true>,
and those instantiations exist only when a 32-bit s390 target is configured. The
PKGBUILD asked for --enable-targets=x86_64-pep, so the first fix translated that
to s390-linux-gnu -- the honest-looking equivalent. bfd's configure answered:

  *** Specify --enable-obsolete to build it anyway.
  *** Support will be REMOVED in the next major release of BINUTILS,
  *** unless a maintainer comes forward.

32-bit s390 is obsolete in binutils. So gold cannot be built here without
enabling a target upstream has announced it is deleting -- and gold is itself
deprecated, with bfd ld already the default (--enable-ld=default). Turning on one
deprecated thing to build another is not a trade worth making.

gold is dropped and the extra target list keeps only bpf-unknown-none. What is
lost is /usr/bin/ld.gold, which nothing here invokes. Recorded rather than
hidden: the stage-2 binutils will not match Arch's file list, and this is why.

--- FR ---

Cette section a été écrite deux fois, et la première version se trompait de façon
instructive.

gold ne se liait pas : s390.cc référence gold::gold_error_at_location<32, true>,
instanciations qui n'existent que si une cible s390 32 bits est configurée. Le
PKGBUILD demandait --enable-targets=x86_64-pep, donc le premier correctif l'a
traduit en s390-linux-gnu — l'équivalent d'apparence honnête. Le configure de bfd
a répondu :

  *** Specify --enable-obsolete to build it anyway.
  *** Support will be REMOVED in the next major release of BINUTILS,
  *** unless a maintainer comes forward.

Le s390 32 bits est obsolète dans binutils. gold ne peut donc pas être bâti ici
sans activer une cible dont l'amont annonce la suppression — et gold est lui-même
abandonné, ld de bfd étant déjà le défaut (--enable-ld=default). Activer un objet
déprécié pour en bâtir un autre n'est pas un échange qui vaille.

gold est retiré et la liste de cibles ne garde que bpf-unknown-none. Ce qui est
perdu est /usr/bin/ld.gold, que rien ici n'invoque. Consigné plutôt que masqué :
le binutils d'étage 2 ne correspondra pas à la liste de fichiers d'Arch, et voici
pourquoi.

Assisted-by: Claude Opus 5
2026-08-23 19:48:19 -04:00
8a41d00a90 [FIX] our flit_core is too new, and savannah would not serve a patch
python-jinja: ERROR Missing dependencies: flit_core<4

flit_core is installed and importable -- 4.0.2. jinja's pyproject.toml pins
flit_core<4, so build refuses before compiling anything. A new shape here, and
the mirror image of one already recorded: libxslt could not be built on the host
because Ubuntu's libxml2 was too OLD; this is our own package being too NEW for
another's ceiling. Both read as a missing dependency and neither is one.
--skip-dependency-check is what Arch's own python-packaging passes for the same
class of reason.

  autoconf-archive: curl: (52) Empty reply from server

The tarball arrives fine from ftpmirror.gnu.org. What fails is one patch from
savannah's cgit /patch/?id= endpoint, which returns nothing while savannah's root
answers 302 -- measured on both, plus 200 and 2463 bytes from a git mirror for
the same commit hash. Asking a mirror for the SAME COMMIT changes where the bytes
come from, not which bytes they are.

Also: pkill -f "[c]url.*autoconf-arc" killed the shell running it, because the
pattern matched that shell's own command line. Fourth time in this port.

--- FR ---

  python-jinja: ERROR Missing dependencies: flit_core<4

flit_core est installé et importable — 4.0.2. Le pyproject.toml de jinja épingle
flit_core<4 : build refuse avant de compiler. Forme nouvelle ici, et miroir d'une
déjà consignée : libxslt ne se bâtissait pas sur l'hôte car le libxml2 d'Ubuntu
était trop ANCIEN ; ici notre propre paquet est trop RÉCENT pour le plafond d'un
autre. Les deux se lisent comme une dépendance manquante, aucune ne l'est.
--skip-dependency-check est ce que passe le python-packaging d'Arch pour la même
raison.

  autoconf-archive: curl: (52) Empty reply from server

L'archive arrive très bien de ftpmirror.gnu.org. Ce qui échoue est un correctif
tiré du point /patch/?id= de cgit chez savannah, qui ne renvoie rien alors que la
racine de savannah répond 302 — mesuré sur les deux, plus 200 et 2463 octets
depuis un miroir git pour le même commit. Demander le MÊME COMMIT à un miroir
change d'où viennent les octets, pas lesquels.

Aussi : pkill -f "[c]url.*autoconf-arc" a tué le shell qui l'exécutait, son motif
correspondant à la ligne de commande de ce shell. Quatrième fois dans ce portage.

Assisted-by: Claude Opus 5
2026-08-23 19:27:14 -04:00
a8eef0e460 [FIX] the second half of two of my own hooks
libevent: mv: cannot stat '<pkgdir>/usr/share/doc': No such file or directory
  pam:      rm: cannot remove '.../Linux-PAM/*.pdf': No such file or directory

Both are hooks I wrote in this session, and both stopped a build in exactly the
way this port has documented eight times: the documentation build was disabled
and its install was left in place. Turning EVENT__DOXYGEN off does not stop
package() moving usr/share/doc into the libevent-doc split; -Ddocs=disabled does
not stop pam deleting PDFs it no longer produces.

fakeroot was the first one I made myself. This makes three, so the rule is worth
stating instead of rediscovering: after disabling documentation anywhere, search
package() for share/doc, man, and the split arrays before believing the hook is
finished.

pam's rm becomes tolerant rather than deleted -- on a machine with the DocBook
stack the PDFs are real, and the comment above the line says why they must go:
they are not reproducible.

--- FR ---

  libevent: mv: cannot stat '<pkgdir>/usr/share/doc': No such file or directory
  pam:      rm: cannot remove '.../Linux-PAM/*.pdf': No such file or directory

Deux hooks que j'ai écrits dans cette session, et deux arrêts de construction de
la façon exacte que ce portage a documentée huit fois : la construction de la
documentation désactivée, son installation laissée en place. Éteindre
EVENT__DOXYGEN n'empêche pas package() de déplacer usr/share/doc vers le
sous-paquet libevent-doc ; -Ddocs=disabled n'empêche pas pam de supprimer des PDF
qu'il ne produit plus.

fakeroot fut le premier de ma main. Cela en fait trois : la règle mérite d'être
énoncée plutôt que redécouverte — après avoir désactivé une documentation,
chercher dans package() share/doc, man, et les tableaux de sous-paquets avant de
croire le hook terminé.

Le rm de pam devient tolérant plutôt que supprimé : sur une machine dotée de la
pile DocBook les PDF existent, et le commentaire au-dessus dit pourquoi ils
doivent partir — ils ne sont pas reproductibles.

Assisted-by: Claude Opus 5
2026-08-23 19:09:10 -04:00
f26a6a7115 [FIX] stage 2 could not clone, its pacman had no user, and it installed conflicts
Three gaps in the driver, all found by packages advancing to their next obstacle.

  no checkout for python-jinja

Stage 2 could only rebuild what stage 1 had cloned -- wrong for every package
stage 2 builds and stage 1 cannot. libxslt was the first (Ubuntu's libxml2 is too
old to configure it), the Python set followed. They are named in
CHROOT_STAGE2_PKGS because only this stage can produce them, and this stage would
not fetch them. It clones now, pinned to upstream.lock.

  error: problem setting DownloadUser 'alpm' (user does not exist)

The rebuilt pacman drops privileges for downloads to a user created by
sysusers.d, which `pacman --root` does not run -- the same reason the CA bundle
was never generated. Nothing in the chroot downloads anything.

  :: zlib-ng-compat and zlib are in conflict

zlib-ng produces zlib-ng-compat, correct and belonging in repo2, uninstallable
beside the zlib this chroot uses. CHROOT_EXCLUDE already said so for populate;
the same list now applies to the per-package install.

--- FR ---

Trois lacunes du pilote, toutes révélées par des paquets parvenus à leur obstacle
suivant.

  no checkout for python-jinja

L'étage 2 ne savait reconstruire que ce que l'étage 1 avait cloné — faux pour
tout paquet que l'étage 2 bâtit et que l'étage 1 ne peut pas. libxslt fut le
premier (le libxml2 d'Ubuntu est trop ancien), l'ensemble Python a suivi. Ils
figurent dans CHROOT_STAGE2_PKGS parce que seul cet étage les produit, et cet
étage refusait de les chercher. Il clone désormais, épinglé sur upstream.lock.

  error: problem setting DownloadUser 'alpm' (user does not exist)

Le pacman reconstruit abaisse ses privilèges vers un utilisateur créé par
sysusers.d, que `pacman --root` n'exécute pas — la raison même pour laquelle le
faisceau de certificats n'était jamais généré. Rien ne télécharge dans ce chroot.

  :: zlib-ng-compat et zlib sont en conflit

zlib-ng produit zlib-ng-compat, juste et à sa place dans repo2, ininstallable à
côté du zlib de ce chroot. CHROOT_EXCLUDE le disait déjà pour le peuplement ; la
même liste vaut maintenant pour l'installation par paquet.

Assisted-by: Claude Opus 5
2026-08-23 18:38:46 -04:00
5418dace22 [FIX] p11-kit: the error named glib, and glib was not the subject
meson.build:65:18: ERROR: Dependency "glib-2.0" not found

This was nearly a sub-project. glib2 is not in this port and adding it is not one
package: Arch glib2 wants gobject-introspection, which itself needs glib2 -- a
cycle to bootstrap -- plus libsysprof-capture, dconf and gi-docgen.

Reading where the requirement comes from changed the answer entirely. The file is
doc/manual/meson.build and the line is

  glib_prefix = dependency("glib-2.0").get_variable(pkgconfig : "prefix")

glib is consulted to find where gtk-doc keeps its cross-reference files. It is
not linked, not called, and no p11-kit code touches it. -D gtk_doc=false drops
the subdirectory and the dependency with it. Six lines of context replaced
several packages and a dependency cycle.

--- FR ---

  meson.build:65:18: ERROR: Dependency "glib-2.0" not found

C etait presque un sous-projet. glib2 n est pas dans ce portage et l ajouter n est
pas un paquet : le glib2 d Arch veut gobject-introspection, qui depend lui-meme de
glib2 -- un cycle a amorcer -- plus libsysprof-capture, dconf et gi-docgen.

Lire d ou vient reellement l exigence a change la reponse. Le fichier est
doc/manual/meson.build et la ligne est

  glib_prefix = dependency("glib-2.0").get_variable(pkgconfig : "prefix")

glib est consulte pour trouver ou gtk-doc range ses references croisees. Il n est
ni lie, ni appele, et aucun code de p11-kit ne le touche. -D gtk_doc=false
supprime le sous-repertoire et la dependance avec lui. Six lignes de contexte ont
remplace plusieurs paquets et un cycle de dependances.

Assisted-by: Claude Opus 5
2026-08-23 18:03:10 -04:00
8c7d8f52a9 [UPD] hoist jinja2 ahead of systemd
systemd generates source from jinja2 templates, and the Python set sits at the
end of packages.sh while systemd is in the middle. The list order is the runtime
closure, not the build order -- so the packages that must exist before another
one builds are named in STAGE2_FIRST rather than moved.

--- FR ---

systemd genere du code depuis des gabarits jinja2, et l ensemble Python figure en
fin de packages.sh quand systemd est au milieu. L ordre de la liste est la
fermeture d execution, pas celle de construction : les paquets qui doivent
exister avant qu un autre se batisse sont nommes dans STAGE2_FIRST plutot que
deplaces.

Assisted-by: Claude Opus 5
2026-08-23 18:00:35 -04:00
22c0eca955 [ADD] gnupg without its diagrams, and jinja2 for systemd
gnupg builds two architecture diagrams into its manual, one needing ImageMagick
and one transfig -- two toolchains for two pictures. --disable-doc is gnupg own
switch, verified present in configure before use.

The hook was written twice. The first version looked for a ./configure line
ending in a backslash and asserted its way out; gnupg uses an options ARRAY.
Both shapes appear in this port, so a hook has to read the file rather than
assume the house style.

systemd generates source from jinja2 templates and stops without it. Not
documentation this time -- the build cannot proceed.

--- FR ---

gnupg construit deux diagrammes dans son manuel, l un exigeant ImageMagick et
l autre transfig -- deux chaines d outils pour deux images. --disable-doc est son
propre interrupteur, verifie present dans configure avant usage.

Le hook a ete ecrit deux fois. La premiere version cherchait une ligne
./configure terminee par une contre-oblique et s est arretee sur son assertion ;
gnupg emploie un TABLEAU d options. Les deux formes existent dans ce portage : un
hook doit lire le fichier plutot que supposer le style maison.

systemd genere du code depuis des gabarits jinja2 et s arrete sans lui. Pas de la
documentation cette fois -- la construction ne peut pas avancer.

Assisted-by: Claude Opus 5
2026-08-23 17:25:27 -04:00
cb3a32101a [FIX] a hook's scope belongs to its sections, not to its filename
git and meson kept failing in stage 2 on exactly what their own hooks fix, while
the log said

  hook skipped (stage-1 only)

Both were named in STAGE2_SKIP_HOOKS, correctly, when their whole content was a
host workaround: git dropped ZLIB_NG because the host had no zlib-ng headers,
meson moved a wheel out of /usr/local. Then each grew a second section that BOTH
stages need -- git's asciidoc man pages, meson's hotdoc reference manual -- and
the list skips the whole FILE, so the new sections never ran. Two hooks that were
by then two thirds relevant, silently ignored.

A list of filenames cannot say why a hook is listed, and cannot notice when that
reason stops covering the file. The hooks now read EL_STAGE and decide per
section, with the reason written beside each guard; the list is gone. libgcrypt
and libarchive keep a whole-file guard, which now states its reason instead of
being an entry somewhere else.

Verified at both stages: git drops its man pages in each, and keeps ZLIB_NG in
the chroot where zlib-ng exists.

--- FR ---

git et meson échouaient à l'étage 2 sur précisément ce que leurs propres hooks
corrigent, pendant que le journal disait

  hook skipped (stage-1 only)

Tous deux étaient nommés dans STAGE2_SKIP_HOOKS, à juste titre quand tout leur
contenu était un contournement de l'hôte : git abandonnait ZLIB_NG faute
d'en-têtes zlib-ng, meson déplaçait une roue hors de /usr/local. Puis chacun a
gagné une seconde section utile aux DEUX étages — les pages asciidoc de git, le
manuel hotdoc de meson — et la liste saute le FICHIER entier : ces sections n'ont
jamais tourné. Deux hooks devenus pertinents aux deux tiers, ignorés en silence.

Une liste de noms de fichiers ne peut pas dire pourquoi un hook y figure, ni
remarquer que cette raison a cessé de couvrir le fichier. Les hooks lisent
désormais EL_STAGE et tranchent par section, la raison écrite à côté de chaque
garde ; la liste disparaît. libgcrypt et libarchive gardent une garde de fichier
entier, qui énonce sa raison au lieu d'être une entrée ailleurs.

Vérifié aux deux étages : git abandonne ses pages de manuel dans les deux, et
conserve ZLIB_NG dans le chroot, où zlib-ng existe.

Assisted-by: Claude Opus 5
2026-08-23 17:23:30 -04:00
27 changed files with 1379 additions and 60 deletions

View file

@ -0,0 +1,47 @@
#!/usr/bin/env bash
# autoconf-archive: take the patch from a mirror savannah's cgit will serve.
#
# curl: (52) Empty reply from server
# ==> ERROR: Failure while downloading
# https://cgit.git.savannah.gnu.org/cgit/autoconf-archive.git/patch/?id=241f7ae...
#
# The tarball comes from ftpmirror.gnu.org and arrives fine. What fails is a
# single patch fetched from savannah's cgit /patch/?id= endpoint, which returns
# nothing at all -- measured, not assumed:
#
# savannah /patch/?id= (no response)
# github /commit/.patch 200, 2463 bytes
#
# savannah itself is up (its root answers 302); it is that endpoint, which cgit
# generates on demand and rate-limits.
#
# The mirror is asked for THE SAME COMMIT, by its full hash, so this changes
# where the bytes come from and not which bytes they are -- the substantive
# difference from replacing a source with a different version of itself. GitHub
# renders `git format-patch` output where cgit renders its own; both are what
# `patch` expects, and the build fails loudly if that were untrue.
#
# WHY NOT DROP THE PATCH. It is 0001_fix_AX_CXX_COMPILE_STDCXX.patch, a fix to a
# macro this port needs -- tpm2-tss stopped on a missing AX_* macro, which is why
# autoconf-archive is in the list at all.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
s = io.open("PKGBUILD", encoding="utf-8").read()
old = "https://cgit.git.savannah.gnu.org/cgit/autoconf-archive.git/patch/?id="
assert s.count(old) == 1, "autoconf-archive: expected one cgit patch URL, got %d" % s.count(old)
# The commit hash follows the URL, up to the closing quote. Reused verbatim: the
# point of the substitution is that the content is identical.
m = re.search(re.escape(old) + r"([0-9a-f]{40})", s)
assert m, "autoconf-archive: no 40-character commit hash after the cgit URL"
sha = m.group(1)
s = s.replace(old + sha,
"https://github.com/autoconf-archive/autoconf-archive/commit/%s.patch" % sha, 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
print(" patch commit: %s" % sha[:12])
ZZPY
grep -q "cgit.git.savannah.gnu.org" PKGBUILD && {
echo "autoconf-archive: a cgit URL survived" >&2; exit 1; }
grep -qE "github.com/autoconf-archive/autoconf-archive/commit/[0-9a-f]{40}\.patch" PKGBUILD || {
echo "autoconf-archive: the mirror URL is malformed" >&2; exit 1; }
echo "autoconf-archive: patch taken from the git mirror (same commit)"

View file

@ -98,36 +98,105 @@ grep -qE "^[[:space:]]*--enable-pgo-build" PKGBUILD && {
echo "binutils: an active --enable-pgo-build line survived" >&2; exit 1; }
echo "binutils: PGO+LTO build dropped"
# --- the extra targets are x86_64's --------------------------------------------
# --- no gold, and no s390-specific extra target -------------------------------
#
# s390.cc:(.text+0x2d76): undefined reference to
# `void gold::gold_error_at_location<32, true>(...)'
# This section was written twice, and the first version was wrong in an
# instructive way.
#
# gold's s390 target file compiles code for BOTH s390 (32-bit) and s390x, and
# references template instantiations for <32, true>. Those exist only if a
# 32-bit s390 target is configured -- and the PKGBUILD asks for
# gold would not link:
#
# --enable-targets=x86_64-pep,bpf-unknown-none
# s390.cc: undefined reference to `gold::gold_error_at_location<32, true>'
#
# x86_64-pep is the PE+ target for x86_64. It means nothing on this machine, and
# its presence is why gold linked on Arch and not here: on x86_64 that line
# happens to bring in the 32-bit instantiations gold's target files want.
# gold's s390 target file compiles for both s390 and s390x and needs <32, true>
# template instantiations, which exist only when a 32-bit s390 target is
# configured. The PKGBUILD asked for --enable-targets=x86_64-pep, so the first
# fix translated that to s390-linux-gnu -- the honest-looking equivalent. bfd's
# configure answered:
#
# So this is not a workaround for a gold bug -- it is the same x86_64 assumption
# as the -march=x86-64 in makepkg.conf, one layer further in. s390-linux-gnu is
# the honest translation of that line, and it is what makes the templates exist.
# bpf-unknown-none stays: it is architecture-neutral.
# *** Specify --enable-obsolete to build it anyway.
# *** Support will be REMOVED in the next major release of BINUTILS,
# *** unless a maintainer comes forward.
#
# 32-bit s390 is OBSOLETE in binutils. So gold on s390x cannot be built without
# enabling a target upstream has announced it is deleting -- and gold itself is
# deprecated, with bfd ld the default here already (--enable-ld=default). Turning
# on one deprecated thing to build another is not a trade worth making.
#
# So: gold is dropped, and the extra target list keeps only bpf-unknown-none,
# which is architecture-neutral. x86_64-pep goes because it names a format for a
# machine this is not.
#
# WHAT IS LOST: /usr/bin/ld.gold. Nothing in this port invokes it, ld is the
# default, and upstream is removing gold. Recorded rather than hidden -- the
# stage-2 binutils will not match Arch's file list, and this is why.
set -euo pipefail
python3 - <<'ZZPY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
old = "--enable-targets=x86_64-pep,bpf-unknown-none"
assert s.count(old) == 1, "binutils: expected one --enable-targets, got %d" % s.count(old)
s = s.replace(old, "--enable-targets=s390-linux-gnu,bpf-unknown-none", 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
t = [i for i, l in enumerate(lines) if "--enable-targets=" in l]
assert len(t) == 1, "binutils: expected one --enable-targets, got %d" % len(t)
assert "x86_64-pep" in lines[t[0]], "binutils: --enable-targets is not the x86 one: %r" % lines[t[0]]
lines[t[0]] = lines[t[0]].replace("x86_64-pep,", "")
g = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*--enable-gold[ \t]*\\?$", l)]
assert len(g) == 1, "binutils: expected one --enable-gold line, got %d" % len(g)
lines[g[0]] = lines[g[0]].replace("--enable-gold", "--disable-gold")
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "--enable-targets=s390-linux-gnu" PKGBUILD || {
echo "binutils: the target list was not translated" >&2; exit 1; }
grep -q "x86_64-pep" PKGBUILD && {
echo "binutils: an x86_64 target survived" >&2; exit 1; }
echo "binutils: extra targets translated to s390 (gold needs the 32-bit one)"
grep -q "x86_64-pep" PKGBUILD && { echo "binutils: an x86_64 target survived" >&2; exit 1; }
grep -q -- "--disable-gold" PKGBUILD || { echo "binutils: gold not disabled" >&2; exit 1; }
grep -q -- "--enable-gold" PKGBUILD && { echo "binutils: gold still enabled" >&2; exit 1; }
echo "binutils: gold dropped (its s390 support needs an obsolete target)"
# --- libiberty still records a usr/lib64 path ---------------------------------
#
# --libdir=/usr/lib above is not enough. pacman refused to install the result:
#
# binutils: <root>/usr/lib64 exists in filesystem (owned by filesystem)
# binutils: <root>/usr/lib64/libiberty.a exists in filesystem
#
# libiberty installs into $(libdir)$(MULTIOSSUBDIR), and on s390x gcc reports
# ../lib64 for -print-multi-os-directory. In the INSTALLED system that resolves
# through the symlink the filesystem package now provides, so the file lands in
# /usr/lib -- but pkgdir has no such symlink, so make creates a real
# pkg/usr/lib64/ and makepkg records `usr/lib64/libiberty.a` as the package's
# own path. Installing it then collides with the symlink.
#
# The two fixes are not alternatives: filesystem's symlink is what keeps meson
# and cmake choosing lib, and this moves the one file that still writes through
# the multi-os subdirectory. Done in package(), on pkgdir, so nothing depends on
# the order the two packages are installed in.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
# The last package function is where pkgdir is complete.
# package(), not package_binutils(). binutils is NOT a split package here --
# pkgname=binutils, one function -- and the first version of this assumed the
# split form and asserted its way out. Both shapes are in this port; a hook has
# to look.
hit = [i for i, l in enumerate(lines) if re.match(r"^package(_binutils)?\(\)", l)]
assert len(hit) == 1, "binutils: expected one package function, got %d" % len(hit)
# Find that function's closing brace.
i = hit[0] + 1
while i < len(lines) and lines[i] != "}":
i += 1
assert i < len(lines), "binutils: package_binutils() has no closing brace"
lines[i:i] = [
"",
" # libiberty writes through gcc's multi-os subdirectory (../lib64 on",
" # s390x). In the installed system usr/lib64 is a symlink to lib, but pkgdir",
" # has no symlink, so the path is recorded literally and collides with the",
" # filesystem package. Move it to where it resolves to anyway.",
' if [ -d "$pkgdir/usr/lib64" ]; then',
' install -d "$pkgdir/usr/lib"',
' mv "$pkgdir"/usr/lib64/* "$pkgdir/usr/lib/"',
' rmdir "$pkgdir/usr/lib64"',
' fi',
]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q 'rmdir "\$pkgdir/usr/lib64"' PKGBUILD || {
echo "binutils: the lib64 move was not inserted" >&2; exit 1; }
echo "binutils: libiberty moved out of usr/lib64"

39
patches/pkgbuild/clang.sh Executable file
View file

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# clang: no Sphinx documentation.
#
# -DLLVM_ENABLE_SPHINX=ON pulls in python-sphinx and python-myst-parser, and
# behind those sit docutils, babel, pygments, snowballstemmer, imagesize,
# alabaster, the sphinxcontrib-* family, markdown-it-py and mdit-py-plugins --
# fifteen or so packages whose entire purpose here would be to render HTML for a
# compiler nothing in this port reads the manual of.
#
# MEASURED BEFORE CHOOSING, the way glib2 and iproute2 were: llvm and clang need
# only libedit and python-psutil beyond what this port already has. Sphinx is the
# whole of the rest of the bill, and one flag removes it.
#
# AND THE CLEANUP, found before it failed. package() ends on
#
# rm -r "$pkgdir"/usr/share/doc/.../html/{_sources,.buildinfo}
#
# which Sphinx creates and which will not exist. This is the seventeenth
# appearance of that shape in this port; the procedure written in jsoncpp's hook
# says to grep package() for the OUTPUT paths after disabling a documentation
# build, and this is the first time it was done before the build rather than
# after. rm -rf rather than deleted: on a machine with Sphinx the directory is
# real and still has to go.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
s = io.open("PKGBUILD", encoding="utf-8").read()
old = "-DLLVM_ENABLE_SPHINX=ON"
assert s.count(old) == 1, "clang: expected one LLVM_ENABLE_SPHINX, got %d" % s.count(old)
s = s.replace(old, "-DLLVM_ENABLE_SPHINX=OFF", 1)
lines = s.split("\n")
hit = [i for i, l in enumerate(lines) if "_sources" in l and l.lstrip().startswith("rm ")]
assert len(hit) == 1, "clang: expected one _sources removal, got %d" % len(hit)
lines[hit[0]] = lines[hit[0]].replace("rm -r ", "rm -rf ", 1)
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "-DLLVM_ENABLE_SPHINX=OFF" PKGBUILD || { echo "clang: sphinx not disabled" >&2; exit 1; }
grep -qE "^[[:space:]]*rm -rf .*_sources" PKGBUILD || { echo "clang: the cleanup is not tolerant" >&2; exit 1; }
echo "clang: Sphinx documentation disabled, cleanup made tolerant"

View file

@ -82,3 +82,54 @@ sed -n '/^makedepends=(/,/)/p' PKGBUILD | grep -qw emacs && {
grep -q 'cmake-mode' PKGBUILD && {
echo "cmake: a cmake-mode reference remains -- check it is harmless" >&2; }
echo "cmake: emacs byte-compilation dropped (the .el is installed regardless)"
# --- no Sphinx documentation ---------------------------------------------------
#
# CMake Error at Utilities/Sphinx/CMakeLists.txt:52 (message):
#
# cmake builds its own manual and man pages with Sphinx, requested by
# --sphinx-man and --sphinx-html in the bootstrap arguments. python-sphinx is not
# in this port: it is a large Python documentation stack, and cmake is wanted here
# as a build tool.
#
# BOTH FLAGS, and they are bootstrap arguments rather than -D options -- cmake
# configures itself with its own script before it can run itself. Dropping only
# one leaves the other pulling Sphinx in, and the error is identical, which is how
# a half-fix looks like no fix.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*--sphinx-(man|html)[ \t]*\\\\?$", l)]
assert len(hit) == 2, "cmake: expected two --sphinx flags, got %d" % len(hit)
# Backwards: deleting shifts everything after it.
for i in reversed(hit):
del lines[i]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "^[[:space:]]*--sphinx-" PKGBUILD && {
echo "cmake: a --sphinx flag survived" >&2; exit 1; }
echo "cmake: Sphinx manual and man pages dropped"
# --- and the html it no longer generates --------------------------------------
#
# rm: cannot remove '<pkgdir>/usr/share/doc/cmake/html/_sources': No such file
#
# ELEVENTH time in this port. With Sphinx off, cmake generates no html, and the
# PKGBUILD deletes _sources from it -- Sphinx leaves the reStructuredText inputs
# beside the rendered pages and they are not wanted in a package.
#
# rm -rf rather than deleted: on a machine with Sphinx the directory is real and
# still has to go.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if "_sources" in l and l.lstrip().startswith("rm ")]
assert len(hit) == 1, "cmake: expected one _sources removal, got %d" % len(hit)
i = hit[0]
lines[i] = lines[i].replace("rm -r ", "rm -rf ", 1)
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "^[[:space:]]*rm -rf .*_sources" PKGBUILD || {
echo "cmake: the _sources removal was not made tolerant" >&2; exit 1; }
echo "cmake: _sources cleanup tolerates its absence"

View file

@ -24,6 +24,17 @@
# choice will have to be made then: build Rust, or drop the flag. Recorded now
# rather than discovered twice.
set -euo pipefail
# STAGE 1 ONLY -- this section, not this file.
#
# It used to be expressed by naming this hook in STAGE2_SKIP_HOOKS, which skips
# the whole FILE. Then a section was appended that both stages need, and it
# silently never ran: the package kept failing in stage 2 on the very thing the
# new section fixes, and the log said "hook skipped (stage-1 only)" about a hook
# that was now two thirds relevant.
#
# The scope belongs next to the reason, so each section decides for itself.
if [ "${EL_STAGE:-1}" = 1 ]; then
python3 - <<'PY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
@ -45,6 +56,9 @@ grep -q "'zlib-ng'" PKGBUILD && {
grep -qF "'grep' 'shadow' 'zlib')" PKGBUILD || {
echo "git: zlib not substituted in depends" >&2; exit 1; }
echo "git: plain zlib (no zlib-ng headers on the host); STAGE-1 ONLY"
else
echo "git: zlib-ng available in the chroot; keeping Arch's choice"
fi
# --- no man pages -------------------------------------------------------------
#
@ -76,3 +90,110 @@ ZZPY
grep -qE "\ball man\b|\binstall-man\b" PKGBUILD && {
echo "git: a man target survived" >&2; exit 1; }
echo "git: man pages dropped (no asciidoc, four places)"
# --- no Rust ------------------------------------------------------------------
#
# cargo: command not found
#
# git's PKGBUILD passes WITH_RUST=1 and lists rust in makedepends. This port does
# not package Rust, and packaging it is not a package: rustc bootstraps from a
# previous rustc, which is the one thing a from-source distribution cannot start
# from -- Arch ships a binary to break that cycle, and this port has no such
# escape on s390x.
#
# WHY IT SURFACED ONLY NOW. Stage 1 built git happily: the HOST has rustc, from
# apt. So this is another entry in the longest-running theme of the port -- a
# dependency the host satisfied invisibly, appearing the first time the chroot is
# the one being asked. And it appeared only after the man-page section above
# started running at stage 2, which it had not been doing.
#
# WITH_RUST=1 selects Rust reimplementations of some internals; git builds and
# behaves the same without it. Both stages, so the two agree: stage 1 was
# producing a git that stage 2 could not reproduce.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
# NO_RUST=1, not merely dropping WITH_RUST=1.
#
# Removing WITH_RUST=1 changed nothing -- cargo was still invoked:
#
# CARGO target/release/libgitcore.a
# /bin/sh: line 1: cargo: command not found
#
# The Makefile reads
#
# ifndef NO_RUST
# BASIC_CFLAGS += -DWITH_RUST
# GITLIBS += $(RUST_LIB)
#
# so Rust is built BY DEFAULT and the opt-out is NO_RUST. WITH_RUST=1 in the
# PKGBUILD sets a variable the Makefile never reads; deleting it was correct and
# insufficient, which is the worst combination -- the hook reported success and
# the build failed identically.
#
# Worth recording as an upstream change: git now needs a Rust toolchain unless
# told otherwise. For a distribution bootstrapped from source that is a real
# obstacle, because rustc bootstraps from a previous rustc.
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*WITH_RUST=1[ \t]*$", l)]
assert len(hit) == 1, "git: expected one WITH_RUST=1 line, got %d" % len(hit)
lines[hit[0]] = re.match(r"^[ \t]*", lines[hit[0]]).group(0) + "NO_RUST=1"
out = [l.replace(" 'rust'", "") if "makedepends" in l else l for l in lines]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(out))
ZZPY
grep -qE "^[[:space:]]*WITH_RUST=1" PKGBUILD && { echo "git: WITH_RUST survived" >&2; exit 1; }
grep -qE "^[[:space:]]*NO_RUST=1" PKGBUILD || { echo "git: NO_RUST=1 was not set" >&2; exit 1; }
grep -q "'rust'" PKGBUILD && { echo "git: rust still in makedepends" >&2; exit 1; }
echo "git: built without Rust (no rustc to bootstrap from on s390x)"
# --- no libsecret credential helper -------------------------------------------
#
# make: *** [Makefile:20: git-credential-libsecret.o] Error 1
#
# contrib/credential/libsecret needs libsecret's headers, and libsecret is not in
# this port -- it is a GNOME keyring client, and behind it sit glib2 and
# gobject-introspection, the sub-project already declined for p11-kit.
#
# THREE PLACES, and one of them spans two lines:
#
# 52 _make -C contrib/credential/libsecret
# 106 install -m 0755 contrib/credential/libsecret/git-credential-libsecret \
# 107 "$pkgdir"/usr/lib/git-core/git-credential-libsecret
# 108 _make -C contrib/credential/libsecret clean
#
# The first version of this section asserted "expected one _make, got 2" and
# stopped, which is the guard doing its job. Replacing line 106 alone would leave
# 107 as a command of its own -- the binutils continuation trap again, and the
# reason the install is deleted as a range rather than commented.
#
# The optdepend line stays: it describes what the helper would need if someone
# built it, and it is a sentence, not a dependency.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
mk = [i for i, l in enumerate(lines)
if re.search(r"_make -C contrib/credential/libsecret", l)]
assert len(mk) == 2, "git: expected two libsecret _make lines, got %d" % len(mk)
ins = [i for i, l in enumerate(lines)
if re.match(r"^[ \t]*install ", l) and "credential/libsecret" in l]
assert len(ins) == 1, "git: expected one libsecret install, got %d" % len(ins)
i = ins[0]
j = i
while lines[j].rstrip().endswith("\\"):
j += 1
# Delete the install and its continuations, then neutralise both _make lines.
# Backwards, so earlier indices stay valid.
for k in reversed(mk):
ind = re.match(r"^[ \t]*", lines[k]).group(0)
lines[k] = ind + ": # libsecret is not packaged here (glib2, gobject-introspection)."
del lines[i:j + 1]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "^[[:space:]]*_make -C contrib/credential/libsecret" PKGBUILD && {
echo "git: a libsecret build survived" >&2; exit 1; }
grep -qE "^[[:space:]]*install .*credential/libsecret" PKGBUILD && {
echo "git: the libsecret install survived" >&2; exit 1; }
grep -q "git-credential-libsecret" PKGBUILD && {
echo "git: a reference to the helper binary survived" >&2; exit 1; }
echo "git: libsecret credential helper dropped"

41
patches/pkgbuild/gnupg.sh Executable file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
# gnupg: no manual, because two diagrams in it need two image toolchains.
#
# /bin/sh: line 1: convert: command not found
# make[2]: *** [Makefile:1199: gnupg-module-overview.pdf] Error 127
# /bin/sh: line 1: fig2dev: command not found
# make[2]: *** [Makefile:1211: gnupg-card-architecture.pdf] Error 127
#
# `convert` is ImageMagick, `fig2dev` is transfig. Two separate toolchains, for
# two architecture diagrams in gnupg's own documentation.
#
# --disable-doc is gnupg's own switch, and it was CHECKED before being used:
# `grep -oE '\-\-(disable|enable)-doc' configure` finds both, so autoconf knows
# the option. groff had no such switch and needed its generated Makefile edited
# instead -- the difference matters, because an invented flag draws only a
# warning from autoconf and the build fails exactly as before, with the hook's
# own check passing.
set -euo pipefail
# AN ARRAY, not a continued command. The first version of this hook looked for a
# ./configure line ending in a backslash and asserted its way out:
#
# AssertionError: gnupg: ./configure does not continue
#
# gnupg collects its options in `local configure_options=( ... )` and then calls
# `./configure "${configure_options[@]}"`. Both shapes appear in this port --
# binutils, cryptsetup and libxcrypt use continued commands; gnupg and shadow use
# arrays -- so a hook has to look at the file rather than assume the house style.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*local configure_options=\($", l)]
assert len(hit) == 1, "gnupg: expected one configure_options array, got %d" % len(hit)
i = hit[0] + 1
ind = re.match(r"^[ \t]*", lines[i]).group(0)
# The array is kept in alphabetical order upstream; --disable-doc sorts first.
lines.insert(i, ind + "--disable-doc # no ImageMagick, no transfig for the diagrams")
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -A2 "local configure_options=(" PKGBUILD | grep -q -- "--disable-doc" || {
echo "gnupg: the flag is not inside the options array" >&2; exit 1; }
echo "gnupg: manual disabled (no ImageMagick, no transfig)"

View file

@ -36,6 +36,53 @@ lines[i:i] = [
ind + "# package. The install side already skips the file when it is absent.",
ind + 'sed -i "s|^DOC_GNU_EPS = doc/gnu.eps$|DOC_GNU_EPS =|" Makefile',
ind + 'grep -q "^DOC_GNU_EPS =$" Makefile',
ind + "# And its CONSUMER. Emptying DOC_GNU_EPS removed the rule that",
ind + "# builds the figure and not the document that includes it:",
ind + "#",
ind + "# doc/webpage.ms:45: error: cannot open 'gnu.eps'",
ind + "# doc/webpage.ms:45: fatal error: PSPIC failed to include 'gnu.eps'",
ind + "#",
ind + "# PROCESSEDEXAMPLEFILES_PS lists webpage.ps and grnexmpl.ps. The",
ind + "# HTML variant right above it is ALREADY empty, with its real value",
ind + "# left as a comment -- groff's own configure empties that one when",
ind + "# the tools are missing. This does the same for PS, which is the",
ind + "# decision groff would have made had it checked.",
ind + 'sed -i "/^PROCESSEDEXAMPLEFILES_PS = /,/^$/ s|^.*$||" Makefile',
ind + 'grep -q "^PROCESSEDEXAMPLEFILES_PS" Makefile && { echo "groff: PS example list not emptied" >&2; exit 1; }',
ind + "# THE SECOND consumer. Two files include the logo, and only two --",
ind + "# counted rather than guessed:",
ind + "#",
ind + "# doc/webpage.ms (above)",
ind + "# contrib/hdtbl/examples/mixed_pickles.roff (this)",
ind + "#",
ind + "# hdtbl renders its example tables to PostScript, and one of them shows",
ind + "# the GNU logo. HDTBLPROCESSEDEXAMPLEFILES lists all twelve; the whole",
ind + "# list goes, because there is no reason to render eleven examples of a",
ind + "# table macro package into a documentation set nothing here reads.",
ind + "# ALL of them, not one per failure. This is the third round on the",
ind + "# same package: DOC_GNU_EPS, then PROCESSEDEXAMPLEFILES_PS, then",
ind + "# HDTBL, and mom's examples were waiting behind those --",
ind + "#",
ind + "# pdfmom: fatal error: test-groff exited with status 4",
ind + "# contrib/mom/examples/typesetting.pdf",
ind + "#",
ind + "# groff renders its own manuals and every contrib package's examples",
ind + "# to PostScript and PDF using the groff being built. Each list is a",
ind + "# separate variable, and finding them one failure at a time is how",
ind + "# three passes went by. They are emptied together.",
ind + 'for _v in HDTBLPROCESSEDEXAMPLEFILES MOMPROCESSEDEXAMPLEFILES \\',
ind + ' PROCESSEDDOCFILES_PDF PROCESSEDDOCFILES_PS \\',
ind + ' PROCESSEDDOCFILES_TXT PROCESSEDDOCFILES \\',
ind + ' GENERATEDDOCFILES; do',
ind + ' sed -i "/^$_v = /,/^$/ s|^.*$||" Makefile',
ind + ' grep -q "^$_v" Makefile && { echo "groff: $_v not emptied" >&2; exit 1; }',
ind + 'done',
ind + "# And say it out loud: if a THIRD consumer appears, this is the pattern",
ind + "# to look for, not a new kind of problem.",
ind + 'if grep -rl "gnu\\.eps" --include="*.roff" --include="*.ms" . 2>/dev/null | grep -qv "^\\./doc/webpage.ms$\\|mixed_pickles"; then',
ind + ' echo "groff: a third file includes gnu.eps; see patches/pkgbuild/groff.sh" >&2',
ind + ' exit 1',
ind + 'fi',
]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY

View file

@ -26,3 +26,34 @@ io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q "doxybuild" PKGBUILD && { echo "jsoncpp: doxybuild survived" >&2; exit 1; }
echo "jsoncpp: doxygen build removed"
# --- and the html it no longer generates --------------------------------------
#
# cp: cannot stat 'dist/doxygen/jsoncpp-api-html-1.9.6': No such file or dir
#
# FOURTEENTH time, and the sixth of my own making. Removing the doxybuild.py call
# above stopped the API html being generated, and package() still copies the
# directory it would have produced. The name carries the version, so the path is
# not even greppable without knowing pkgver.
#
# By now the rule is mechanical and worth stating as a procedure rather than a
# lesson: after removing a documentation build, grep package() for the OUTPUT
# directory name, not for the tool. The tool appears in build(); the path appears
# in package(), and it is the path that fails.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if "doxygen" in l and re.match(r"^[ \t]*(cp|mv|install) ", l)]
assert len(hit) >= 1, "jsoncpp: no doxygen output copy found in package()"
for i in reversed(hit):
j = i
while lines[j].rstrip().endswith("\\"):
j += 1
ind = re.match(r"^[ \t]*", lines[i]).group(0)
lines[i:j + 1] = [ind + ": # the doxygen html is not generated (no doxygen here)."]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "^[[:space:]]*(cp|mv|install) .*doxygen" PKGBUILD && {
echo "jsoncpp: a doxygen output copy survived" >&2; exit 1; }
echo "jsoncpp: doxygen html copy removed"

View file

@ -37,16 +37,41 @@
# It also means these build on the HOST, where they had failed, so they need no
# stage-2 special handling at all.
set -euo pipefail
# A PROBE, not a constant, and it runs in the BUILD ENVIRONMENT.
#
# The first version set _bootstrap=1 unconditionally. That is right on a machine
# with no Python packaging stack and wrong the moment there is one, because the
# vendored builder carries its OWN copy of each project -- older than the
# PKGBUILD's pkgver. python-packaging was labelled 26.3 and installed 25.0, and
# the mismatch surfaced two packages later:
#
# python-vcs-versioning: packaging>=26.2
#
# a floor our own package met on paper and not in fact. Nothing about the
# installed package looked wrong; only `import packaging; packaging.__version__`
# disagreed with its name.
#
# So the PKGBUILD decides at build time, where the answer is knowable: if `build`
# and `installer` import, the real path works and the vendored copies are not
# wanted. Hooks run on the HOST even for stage 2, so a probe written here could
# not answer for the chroot -- this one is evaluated by makepkg, inside it.
python3 - <<'ZZPY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
old = "_bootstrap=0"
assert s.count(old) == 1, "python bootstrap: expected one _bootstrap=0, got %d" % s.count(old)
s = s.replace(old, "_bootstrap=1 # see patches/pkgbuild/lib-python-bootstrap.sh", 1)
new = ("# Bootstrap only when the real path is unavailable -- see\n"
"# patches/pkgbuild/lib-python-bootstrap.sh. Evaluated by makepkg, in the\n"
"# build environment, because that is where the answer lives.\n"
"_bootstrap=1\n"
"python -c 'import build, installer' >/dev/null 2>&1 && _bootstrap=0")
s = s.replace(old, new, 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
ZZPY
grep -q "^_bootstrap=1" PKGBUILD || {
echo "python bootstrap: _bootstrap was not switched" >&2; exit 1; }
grep -q "^_bootstrap=1$" PKGBUILD || {
echo "python bootstrap: the default was not set to 1" >&2; exit 1; }
grep -q "import build, installer" PKGBUILD || {
echo "python bootstrap: the probe was not inserted" >&2; exit 1; }
# The branch it selects must actually exist, or the switch silently builds
# nothing -- these PKGBUILDs are not required to have both halves.
grep -q "bootstrap.build" PKGBUILD || {

View file

@ -37,6 +37,15 @@
# leaves the package uninstallable for a reason the diff appears to fix -- the
# fifth place, and the one that has cost the most time in this port.
set -euo pipefail
# STAGE 1 ONLY, the whole file. Expressed here rather than in a list kept by the
# driver: a list of file names cannot say WHY, and when a hook later grows a
# section that both stages need, the list silently skips that too. That happened
# to git and meson.
if [ "${EL_STAGE:-1}" != 1 ]; then
echo "libarchive: nothing to do at stage 2 -- our libxml2 and icu agree in the chroot"
exit 0
fi
python3 - <<'ZZPY'
import io, re
s = io.open("PKGBUILD", encoding="utf-8").read()

View file

@ -18,3 +18,32 @@ io.open("PKGBUILD", "w", encoding="utf-8").write(s)
ZZPY
grep -q "EVENT__DOXYGEN=OFF" PKGBUILD || { echo "libevent: doxygen not disabled" >&2; exit 1; }
echo "libevent: Doxygen reference disabled"
# --- and the docs split, which I left behind ----------------------------------
#
# mv: cannot stat '<pkgdir>/usr/share/doc': No such file or directory
#
# Turning EVENT__DOXYGEN off above stopped the documentation being BUILT and left
# package() moving it into the libevent-doc split:
#
# mv {"$pkgdir",doc}/usr/share/doc
#
# NINTH time in this port, and the second time I have made it myself rather than
# inherited it: fakeroot was the other. Removing a documentation build without
# removing its install is apparently the easiest mistake available here, so it is
# worth stating the rule plainly -- after disabling documentation anywhere,
# search package() for share/doc, man, and the split arrays before believing the
# hook is finished.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.search(r"share/doc", l) and l.lstrip().startswith("mv ")]
assert len(hit) == 1, "libevent: expected one share/doc move, got %d" % len(hit)
i = hit[0]
ind = re.match(r"^[ \t]*", lines[i]).group(0)
lines[i] = ind + ': # doxygen is off above, so usr/share/doc is never created.'
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "^[[:space:]]*mv .*share/doc" PKGBUILD && {
echo "libevent: the docs move survived" >&2; exit 1; }
echo "libevent: docs split removed (nothing builds them now)"

View file

@ -53,6 +53,15 @@
# MAKEFLAGS bound inside build() rather than at hook level.
set -euo pipefail
# STAGE 1 ONLY, the whole file. Expressed here rather than in a list kept by the
# driver: a list of file names cannot say WHY, and when a hook later grows a
# section that both stages need, the list silently skips that too. That happened
# to git and meson.
if [ "${EL_STAGE:-1}" != 1 ]; then
echo "libgcrypt: nothing to do at stage 2 -- our libgpg-error is installed in the chroot"
exit 0
fi
WORK="${WORK:-$HOME/work/arch-s390x}"
PREFIX="$WORK/stage1-prefix"

39
patches/pkgbuild/llvm.sh Executable file
View file

@ -0,0 +1,39 @@
#!/usr/bin/env bash
# llvm: no Sphinx documentation.
#
# -DLLVM_ENABLE_SPHINX=ON pulls in python-sphinx and python-myst-parser, and
# behind those sit docutils, babel, pygments, snowballstemmer, imagesize,
# alabaster, the sphinxcontrib-* family, markdown-it-py and mdit-py-plugins --
# fifteen or so packages whose entire purpose here would be to render HTML for a
# compiler nothing in this port reads the manual of.
#
# MEASURED BEFORE CHOOSING, the way glib2 and iproute2 were: llvm and clang need
# only libedit and python-psutil beyond what this port already has. Sphinx is the
# whole of the rest of the bill, and one flag removes it.
#
# AND THE CLEANUP, found before it failed. package() ends on
#
# rm -r "$pkgdir"/usr/share/doc/.../html/{_sources,.buildinfo}
#
# which Sphinx creates and which will not exist. This is the seventeenth
# appearance of that shape in this port; the procedure written in jsoncpp's hook
# says to grep package() for the OUTPUT paths after disabling a documentation
# build, and this is the first time it was done before the build rather than
# after. rm -rf rather than deleted: on a machine with Sphinx the directory is
# real and still has to go.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
s = io.open("PKGBUILD", encoding="utf-8").read()
old = "-DLLVM_ENABLE_SPHINX=ON"
assert s.count(old) == 1, "llvm: expected one LLVM_ENABLE_SPHINX, got %d" % s.count(old)
s = s.replace(old, "-DLLVM_ENABLE_SPHINX=OFF", 1)
lines = s.split("\n")
hit = [i for i, l in enumerate(lines) if "_sources" in l and l.lstrip().startswith("rm ")]
assert len(hit) == 1, "llvm: expected one _sources removal, got %d" % len(hit)
lines[hit[0]] = lines[hit[0]].replace("rm -r ", "rm -rf ", 1)
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "-DLLVM_ENABLE_SPHINX=OFF" PKGBUILD || { echo "llvm: sphinx not disabled" >&2; exit 1; }
grep -qE "^[[:space:]]*rm -rf .*_sources" PKGBUILD || { echo "llvm: the cleanup is not tolerant" >&2; exit 1; }
echo "llvm: Sphinx documentation disabled, cleanup made tolerant"

View file

@ -40,6 +40,17 @@
# will actually import this. Deriving it from the interpreter running the build
# is the intuitive thing and the wrong one.
set -euo pipefail
# STAGE 1 ONLY -- this section, not this file.
#
# It used to be expressed by naming this hook in STAGE2_SKIP_HOOKS, which skips
# the whole FILE. Then a section was appended that both stages need, and it
# silently never ran: the package kept failing in stage 2 on the very thing the
# new section fixes, and the log said "hook skipped (stage-1 only)" about a hook
# that was now two thirds relevant.
#
# The scope belongs next to the reason, so each section decides for itself.
if [ "${EL_STAGE:-1}" = 1 ]; then
python3 - <<'ZZPY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
@ -89,6 +100,9 @@ ZZPY
grep -q 'posix_local scheme installs' PKGBUILD || {
echo "meson: the relocation was not inserted" >&2; exit 1; }
echo "meson: wheel relocated out of /usr/local; STAGE-1 ONLY"
else
echo "meson: chroot python has no posix_local scheme; no relocation needed"
fi
# --- no reference manual ------------------------------------------------------
#

106
patches/pkgbuild/p11-kit.sh Executable file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env bash
# p11-kit: no gtk-doc manual.
#
# meson.build:65:18: ERROR: Dependency "glib-2.0" not found (tried pkgconfig)
#
# THIS ONE WAS NEARLY A SUB-PROJECT. glib-2.0 is not in this port, and adding it
# is not one package: Arch's glib2 wants gobject-introspection (which itself
# needs glib2 -- a cycle to bootstrap), libsysprof-capture, dconf and gi-docgen.
# Several packages and a circular dependency, for one entry in one build.
#
# Reading where the requirement actually comes from changed the answer entirely.
# The file is doc/manual/meson.build, and the line is
#
# glib_prefix = dependency('glib-2.0').get_variable(pkgconfig : 'prefix')
#
# glib is consulted to find where gtk-doc keeps its cross-reference files. It is
# not linked, not called, and nothing in p11-kit's code touches it. -D gtk_doc
# is already in the PKGBUILD's option list, set to true; turning it false drops
# the whole doc/manual subdirectory and the dependency with it.
#
# The lesson is the one this port keeps relearning: the error names glib, and
# glib is not the subject. Six lines of context said so.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*-D gtk_doc=true[ \t]*$", l)]
assert len(hit) == 1, "p11-kit: expected one -D gtk_doc=true, got %d" % len(hit)
lines[hit[0]] = lines[hit[0]].replace("gtk_doc=true", "gtk_doc=false")
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "-D gtk_doc=false" PKGBUILD || { echo "p11-kit: gtk_doc not disabled" >&2; exit 1; }
echo "p11-kit: gtk-doc manual disabled (glib was only locating its xrefs)"
# --- and no man pages either --------------------------------------------------
#
# With gtk_doc off, the next line was
#
# meson.build:94:4: ERROR: Problem encountered: Docbook stylesheet for man
# pages not found
#
# The stylesheets, not xsltproc -- which our libxslt supplies. Third package to
# stop on exactly this: pam validates DocBook sources it cannot fetch, shadow
# renders man pages from them. Shipping docbook-xsl would be a package that only
# documentation needs; -D man is already in the option list.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*-D man=true[ \t]*$", l)]
assert len(hit) == 1, "p11-kit: expected one -D man=true, got %d" % len(hit)
lines[hit[0]] = lines[hit[0]].replace("man=true", "man=false")
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "-D man=false" PKGBUILD || { echo "p11-kit: man not disabled" >&2; exit 1; }
echo "p11-kit: man pages disabled (DocBook stylesheets absent, not xsltproc)"
# --- and the gtk-doc split ----------------------------------------------------
#
# mv: cannot stat '<pkgdir>/usr/share/gtk-doc': No such file or directory
#
# TWELFTH time. -D gtk_doc=false above stopped the documentation being built and
# left package() splitting it out:
#
# _pick doc "$pkgdir"/usr/share/gtk-doc
#
# _pick is Arch's helper for moving a path into a sub-package, and it is as fatal
# on a missing path as a bare mv.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if "gtk-doc" in l and "_pick" in l]
assert len(hit) == 1, "p11-kit: expected one gtk-doc _pick, got %d" % len(hit)
i = hit[0]
ind = re.match(r"^[ \t]*", lines[i]).group(0)
lines[i] = ind + ": # gtk_doc is false above, so usr/share/gtk-doc is never created."
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "_pick doc .*gtk-doc" PKGBUILD && {
echo "p11-kit: the gtk-doc pick survived" >&2; exit 1; }
echo "p11-kit: gtk-doc split removed (nothing builds it now)"
# --- and the doc sub-package that now stages nothing --------------------------
#
# mv: cannot stat 'doc/*': No such file or directory
#
# THIRTEENTH time, and the third layer of the same package. Neutralising the
# _pick above stopped anything being staged into doc/, and package_p11-kit-doc()
# still runs `mv doc/* "$pkgdir"`. _pick, mv, and now the sub-package function
# that consumes what _pick staged: three places for one decision.
#
# The lesson is stated plainly because five of these have been mine: disabling a
# documentation build means following it through the ENTIRE package() chain --
# the build flag, the staging, and the sub-package that reads the staging area.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*mv doc/\* ", l)]
assert len(hit) == 1, "p11-kit: expected one `mv doc/*`, got %d" % len(hit)
i = hit[0]
ind = re.match(r"^[ \t]*", lines[i]).group(0)
lines[i] = ind + ": # nothing is staged into doc/: gtk_doc and man are both off."
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "^[[:space:]]*mv doc/\*" PKGBUILD && {
echo "p11-kit: the doc move survived" >&2; exit 1; }
echo "p11-kit: doc sub-package stages nothing, and no longer tries"

View file

@ -22,3 +22,31 @@ io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "-Ddocs=disabled" PKGBUILD || { echo "pam: docs not disabled" >&2; exit 1; }
echo "pam: DocBook documentation disabled"
# --- and the PDFs it no longer produces ---------------------------------------
#
# rm: cannot remove '<pkgdir>/usr/share/doc/Linux-PAM/*.pdf': No such file
#
# The line above it says why it exists:
#
# # remove unreproducible pdf files
#
# pam builds PDFs whose bytes differ between runs, so the PKGBUILD deletes them
# to keep the package reproducible. With -Ddocs=disabled they are never built,
# and a cleanup step that finds nothing to clean is fatal under set -e.
#
# The rm becomes tolerant rather than being deleted: on a machine that does have
# the DocBook stack, the PDFs WILL be there and they still have to go.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.search(r"\*\.pdf", l) and l.lstrip().startswith("rm ")]
assert len(hit) == 1, "pam: expected one pdf removal, got %d" % len(hit)
i = hit[0]
ind = re.match(r"^[ \t]*", lines[i]).group(0)
lines[i] = ind + "rm -f " + lines[i].lstrip()[3:].lstrip()
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -qE "^[[:space:]]*rm -f .*\*\.pdf" PKGBUILD || {
echo "pam: the pdf removal was not made tolerant" >&2; exit 1; }
echo "pam: PDF cleanup tolerates their absence"

View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# python-jinja: our flit_core is NEWER than jinja will accept.
#
# * Getting build dependencies for wheel...
# ERROR Missing dependencies:
# flit_core<4
#
# flit_core IS installed, and importable: 4.0.2. jinja's pyproject.toml pins
# `flit_core<4`, so `python -m build` refuses before compiling anything.
#
# A NEW SHAPE for this port, and the mirror image of the one already recorded.
# libxslt could not be built on the host because Ubuntu's libxml2 was too OLD.
# Here our own package is too NEW for another package's declared ceiling. Both
# read as a missing dependency; neither is one.
#
# --skip-dependency-check tells build to compile anyway, and it is not an
# invention: Arch's own python-packaging PKGBUILD passes `-wn
# --skip-dependency-check` for the same class of reason. The pin is upstream
# caution -- flit_core 4 changed no API jinja uses, and jinja builds and imports
# fine against it.
#
# The honest alternative was pinning flit-core to 3.x for the whole
# distribution, which would trade one package's ceiling for every other
# package's floor.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.search(r"python -m build\b", l)]
assert len(hit) == 1, "python-jinja: expected one `python -m build`, got %d" % len(hit)
i = hit[0]
assert "--skip-dependency-check" not in lines[i], "python-jinja: already skipping"
lines[i] = lines[i].rstrip() + " --skip-dependency-check"
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "python -m build .*--skip-dependency-check" PKGBUILD || {
echo "python-jinja: the flag is not on the build line" >&2; exit 1; }
echo "python-jinja: dependency check skipped (our flit_core is 4.x, the pin says <4)"

View file

@ -23,10 +23,34 @@ import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if "LICENSE*" in l and "$_name-$pkgver" in l]
assert len(hit) == 1, "python-packaging: expected one license install, got %d" % len(hit)
lines[hit[0]] = lines[hit[0]].replace("$srcdir/$_name-$pkgver/LICENSE*",
"$srcdir/$pkgname/LICENSE*")
# BOTH LAYOUTS, because _bootstrap is now a probe rather than a constant
# (see lib-python-bootstrap.sh). With the vendored builder the sources are git
# clones under $srcdir/$pkgname; without it they are the release tarball under
# $srcdir/$_name-$pkgver. Hardcoding either one breaks the other, and the first
# version of this hook hardcoded the clone -- correct only while _bootstrap was
# always 1.
lines[hit[0]] = re.sub(
r"\$srcdir/\$_name-\$pkgver/LICENSE\*",
'"$srcdir/$pkgname"/LICENSE* "$srcdir/$_name-$pkgver"/LICENSE*',
lines[hit[0]])
# install -t with two globs would fail on whichever does not exist, so the
# whole line becomes a loop over what is actually there.
ind = re.match(r"^[ \t]*", lines[hit[0]]).group(0)
lines[hit[0]:hit[0] + 1] = [
ind + '# The sources are a git clone under $pkgname when the vendored builder',
ind + '# is used, and the release tarball otherwise. Take whichever exists.',
ind + 'for _lic in "$srcdir/$pkgname"/LICENSE* "$srcdir/$_name-$pkgver"/LICENSE*; do',
ind + ' [ -f "$_lic" ] && install -vDm644 "$_lic" -t "$pkgdir/usr/share/licenses/$pkgname"',
ind + 'done',
]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q 'srcdir/\$pkgname/LICENSE\*' PKGBUILD || {
echo "python-packaging: the license path was not redirected" >&2; exit 1; }
grep -q 'Take whichever exists' PKGBUILD || {
echo "python-packaging: the license loop was not inserted" >&2; exit 1; }
# Matched against what is actually written -- the inserted line quotes the
# directory, `"$srcdir/$_name-$pkgver"/LICENSE*`, so a pattern expecting the
# slash to follow $pkgver directly finds nothing. The first version of this guard
# did exactly that and failed on a correct file.
grep -q '_name-\$pkgver"/LICENSE\*' PKGBUILD || {
echo "python-packaging: the tarball layout was dropped" >&2; exit 1; }
echo "python-packaging: license taken from the bootstrap clone"

View file

@ -79,3 +79,56 @@ grep -qE '^[[:space:]]*mv .*usr/lib/tcl8\.6/sqlite' PKGBUILD && {
echo "sqlite: the hardcoded upstream move survived" >&2; exit 1; }
grep -q '_tcldir=' PKGBUILD || { echo "sqlite: the tcl dir is not discovered" >&2; exit 1; }
echo "sqlite: upstream tcl move reads the version from the tree"
# --- tcl 9 keeps its library inside the binary -------------------------------
#
# The section above discovers whatever tcl directory exists under usr/lib. In
# the chroot there was none, and its guard said so:
#
# sqlite: no tcl dir under usr/lib
#
# What sqlite had created instead was a directory literally named `zipfs:`:
#
# <pkgdir>/sqlite/zipfs:/lib/tcl/tcl_library/
#
# The generated Makefile explains itself:
#
# # $(TCLLIBDIR) = where to install the tcl plugin. If this is empty, it
# TCLLIBDIR = //zipfs:/lib/tcl/tcl_library/sqlite3.53.4
#
# sqlite asks tcl where its library lives and installs the plugin beside it. In
# tcl 8.6 that was a real directory. TCL 9 SHIPS ITS LIBRARY INSIDE THE
# EXECUTABLE, in a zipfs mount, so the answer is a path that exists only to tcl
# -- and sqlite used it as a filesystem path, creating a directory whose name
# contains a colon.
#
# This is the deferred "libtcl8.6 versus our tcl 9.0" item in its final form: not
# a version number to update but a change in what tcl considers a path.
#
# TCLLIBDIR is set to a real directory, with the version READ from tclsh so the
# next tcl release does not need this hook edited again. The section above then
# finds it, because usr/lib/tcl<version> is exactly the shape it looks for.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*make\b.*\bsqltclsh\b", l)]
if not hit:
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*make[ \t]*$", l)]
assert hit, "sqlite: no make line found in build()"
i = hit[0]
ind = re.match(r"^[ \t]*", lines[i]).group(0)
lines[i:i] = [
ind + "# tcl 9 answers with a zipfs path that only tcl can open, and sqlite",
ind + "# would install its plugin into a directory named `zipfs:`. Give it a",
ind + "# real one, versioned from tclsh rather than written in here.",
ind + '_tclver=$(echo "puts [info tclversion]" | tclsh)',
ind + '[ -n "$_tclver" ] || { echo "sqlite: tclsh gave no version" >&2; exit 1; }',
ind + 'sed -i "s|^TCLLIBDIR = .*|TCLLIBDIR = /usr/lib/tcl${_tclver}/sqlite${pkgver}|" Makefile',
ind + 'grep -q "^TCLLIBDIR = /usr/lib/tcl" Makefile || { echo "sqlite: TCLLIBDIR not redirected" >&2; exit 1; }',
]
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q "TCLLIBDIR = /usr/lib/tcl" PKGBUILD || {
echo "sqlite: the TCLLIBDIR redirection was not inserted" >&2; exit 1; }
echo "sqlite: tcl plugin directed to a real directory (tcl 9 uses zipfs)"

View file

@ -286,3 +286,95 @@ io.open("PKGBUILD", "w", encoding="utf-8").write(s)
ZZPY
grep -q -- "-Dapparmor=disabled" PKGBUILD || { echo "systemd: apparmor not disabled" >&2; exit 1; }
echo "systemd: AppArmor disabled"
# --- no man pages -------------------------------------------------------------
#
# compilation error: file ../systemd/man/custom-man.xsl line 12 element import
#
# An XSLT import that cannot resolve: systemd renders its man pages from DocBook,
# and the stylesheets its own custom-man.xsl imports are not installed. xsltproc
# exists -- our libxslt supplies it -- so this is the FOURTH package to stop on
# missing DocBook DATA rather than a missing tool, after pam, shadow and p11-kit.
#
# systemd has by far the largest man page set of any package here, which is the
# strongest argument in the port for shipping docbook-xsl eventually. Not now.
set -euo pipefail
python3 - <<'ZZPY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
old = "-Dman=enabled"
assert s.count(old) == 1, "systemd: expected one man option, got %d" % s.count(old)
s = s.replace(old, "-Dman=disabled", 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
ZZPY
grep -q -- "-Dman=disabled" PKGBUILD || { echo "systemd: man not disabled" >&2; exit 1; }
echo "systemd: man pages disabled (DocBook stylesheets absent, not xsltproc)"
# --- and every man page operation, in every sub-package -----------------------
#
# -Dman=disabled means NO man page exists, and package() touches them SIXTEEN
# times across five sub-package functions. This section was written three times,
# each catching more:
#
# 1st man3 only -> stopped on man8/*nss*
# 2nd mv and rm of share/man paths -> stopped on `ln -s ... installkernel.8.gz`
# 3rd mv, ln and rm -> stopped on
#
# install -D -m0644 -t "$pkgdir"/usr/share/man/man1 \
# build/man/init.1
#
# in package_systemd-sysvcompat(), whose SOURCE is under build/ and whose
# path contains no "man<digit>" component at all.
#
# So the pattern is a man path ANYWHERE on the line -- source or destination,
# build tree or pkgdir -- and the verbs are mv, ln, rm and install. Treated by
# kind:
#
# mv, ln, install neutralised. Nothing exists to move, link or install.
# rm made tolerant with -f. Their intent -- do not ship these --
# still holds where the DocBook stack exists, and one of them
# also removes a binary that must go.
# install -d LEFT ALONE. Creating an empty directory harms nothing, and
# other lines in the same functions install real files into
# the same trees.
#
# Written out because three rounds on one package is the cost of guessing the
# shape of "all of them" instead of listing them.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
MAN = re.compile(r"(share/man|build/man|\bman[1-8]\b|\bman[1-8]/)")
neutral = tolerant = 0
for i in range(len(lines) - 1, -1, -1):
l = lines[i]
if not MAN.search(l):
continue
# install -d only makes a directory; leave it.
if re.match(r"^[ \t]*install +-d", l):
continue
if re.match(r"^[ \t]*(mv|ln|install) ", l):
j = i
while lines[j].rstrip().endswith("\\"):
j += 1
ind = re.match(r"^[ \t]*", l).group(0)
lines[i:j + 1] = [ind + ": # no man pages are built: -Dman=disabled above."]
neutral += 1
elif re.match(r"^[ \t]*rm ", l) and not re.match(r"^[ \t]*rm -[a-z]*f", l):
lines[i] = re.sub(r"^([ \t]*)rm ", r"\1rm -f ", l)
tolerant += 1
assert neutral >= 5, "systemd: expected at least five man operations, got %d" % neutral
assert tolerant >= 1, "systemd: no man page removals found"
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
print(" %d neutralised, %d made tolerant" % (neutral, tolerant))
ZZPY
# Nothing may still act on a man path except `install -d` and a tolerant rm.
if grep -nE "^[[:space:]]*(mv|ln|install) .*(share/man|build/man|man[1-8])" PKGBUILD |
grep -v "install +-d" | grep -qE "install -d" ; then :; fi
grep -nE "^[[:space:]]*(mv|ln) .*(share/man|build/man|man[1-8])" PKGBUILD && {
echo "systemd: a man move or link survived" >&2; exit 1; }
grep -nE "^[[:space:]]*install +-[^d].*(share/man|build/man)" PKGBUILD && {
echo "systemd: a man install survived" >&2; exit 1; }
grep -nE "^[[:space:]]*rm [^-].*(share/man|man[1-8])" PKGBUILD && {
echo "systemd: an intolerant rm of a man path survived" >&2; exit 1; }
echo "systemd: all man page operations handled, in every sub-package"

66
patches/pkgbuild/tpm2-tss.sh Executable file
View file

@ -0,0 +1,66 @@
#!/usr/bin/env bash
# tpm2-tss: no unit tests, because their framework is not here.
#
# configure: error: Unit test enabled, but cmocka missing or version
#
# This is the second obstacle in this package, and the first one hid it: before
# autoconf-archive existed, configure died on
#
# configure.ac:139: error: undefined or overquoted macro: AC_MSG_WARN
#
# which named a macro that exists. An AX_* macro was missing, m4 quoting came
# apart, and autoconf blamed the next thing it recognised. With the archive
# installed, configure gets far enough to ask for cmocka.
#
# cmocka is a C unit-test framework, wanted only to build tests that this
# bootstrap does not run -- makepkg is invoked with --nocheck for the whole first
# pass. --disable-unit is tpm2-tss's own switch; the PKGBUILD passes --enable-unit
# in its options array.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*--enable-unit[ \t]*$", l)]
assert len(hit) == 1, "tpm2-tss: expected one --enable-unit, got %d" % len(hit)
lines[hit[0]] = lines[hit[0]].replace("--enable-unit", "--disable-unit")
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "--disable-unit" PKGBUILD || { echo "tpm2-tss: unit tests not disabled" >&2; exit 1; }
grep -qE "^[[:space:]]*--enable-unit[[:space:]]*$" PKGBUILD && {
echo "tpm2-tss: --enable-unit survived" >&2; exit 1; }
echo "tpm2-tss: unit tests disabled (no cmocka)"
# --- and no integration tests -------------------------------------------------
#
# configure: error: Missing required program 'ss': ensure it is installed
#
# The THIRD obstacle in this package, each hidden by the one before it: a missing
# AX_* macro, then cmocka, now `ss`. configure.ac has
#
# [ERROR_IF_NO_PROG([ss])]
#
# inside the integration-test branch. `ss` comes from iproute2, and adding
# iproute2 was the first idea -- it is in Arch's base group and belongs in this
# distribution eventually. It was MEASURED first: iproute2 depends on libbpf,
# iptables and linux-atm, none of which are here, and libbpf is the dependency
# systemd was already spared for the same reason.
#
# Three packages to satisfy a test-suite prerequisite, or one flag. The tests are
# not run: makepkg gets --nocheck for the whole first pass.
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
en = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*--enable-integration[ \t]*$", l)]
if en:
lines[en[0]] = lines[en[0]].replace("--enable-integration", "--disable-integration")
else:
# Put it beside the unit-test switch this hook already set.
u = [i for i, l in enumerate(lines) if re.match(r"^[ \t]*--disable-unit[ \t]*$", l)]
assert len(u) == 1, "tpm2-tss: expected the --disable-unit line from the section above"
ind = re.match(r"^[ \t]*", lines[u[0]]).group(0)
lines.insert(u[0] + 1, ind + "--disable-integration")
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "--disable-integration" PKGBUILD || {
echo "tpm2-tss: integration tests not disabled" >&2; exit 1; }
echo "tpm2-tss: integration tests disabled (ss would cost three packages)"

View file

@ -20,3 +20,44 @@ ZZPY
grep -q -- "autogen.sh --no-po4a" PKGBUILD || {
echo "xz: --no-po4a was not added" >&2; exit 1; }
echo "xz: translated man pages skipped"
# --- and no doxygen API reference ---------------------------------------------
#
# Two documentation toolchains in one package: po4a above for the translated man
# pages, doxygen for the liblzma API reference.
#
# ../../../doxygen/update-doxygen: 'doxygen' command not found
# make[4]: *** [Makefile:642: ../../../doc/api/index.html] Error 1
#
# THE FLAG WAS ALREADY THERE, AND LOST. The first version of this section
# inserted --disable-doxygen right after ./configure, and config.log recorded what
# actually happened:
#
# $ ./configure --disable-doc --disable-doxygen --prefix=/usr \
# --disable-rpath --enable-doxygen --enable-werror
#
# The PKGBUILD passes --enable-doxygen further along the same line, and autoconf
# takes the LAST occurrence. The port had already learned this from the other
# side -- the arch-meson wrapper APPENDS --auto-features auto precisely so it
# wins -- and this got it backwards.
#
# The existing option is flipped in place rather than a second one added. One
# statement per decision: two contradictory flags on one command line is how the
# next reader spends an hour in config.log.
#
# Note for later: --enable-werror is on that same line. It has not bitten yet.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
hit = [i for i, l in enumerate(lines) if re.search(r"--enable-doxygen\b", l)]
assert len(hit) == 1, "xz: expected one --enable-doxygen, got %d" % len(hit)
lines[hit[0]] = lines[hit[0]].replace("--enable-doxygen", "--disable-doxygen")
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines))
ZZPY
grep -q -- "--enable-doxygen" PKGBUILD && { echo "xz: --enable-doxygen survived" >&2; exit 1; }
grep -q -- "--disable-doxygen" PKGBUILD || { echo "xz: doxygen not disabled" >&2; exit 1; }
# One occurrence only: a line carrying both would be decided by position.
n=$(grep -c -- "--disable-doxygen" PKGBUILD)
[ "$n" = 1 ] || { echo "xz: expected 1 --disable-doxygen, found $n" >&2; exit 1; }
echo "xz: doxygen disabled by flipping the flag the PKGBUILD already passes"

View file

@ -403,7 +403,11 @@ build_package() {
local hook="$PATCH_DIR/$name.sh"
if [ -f "$hook" ]; then
git checkout -- PKGBUILD 2>/dev/null || true
bash "$hook" || return 1
# EL_STAGE tells the hook which stage is asking. Some hooks -- git,
# meson, libgcrypt, libarchive -- exist partly or wholly to work around
# something about the HOST, and that part must not run in the chroot
# where the problem does not exist.
EL_STAGE=1 bash "$hook" || return 1
fi
rm -f ./*.pkg.tar.*
# Explicit `|| return 1`. Relying on `set -e` here does NOT work: callers

View file

@ -145,6 +145,24 @@ SigLevel = Never
[core]
Server = file://$REPO1
EOF
# NOTHING MAY BE MOUNTED UNDER $ROOT WHEN THIS RUNS.
#
# `rm -rf` follows a bind mount and deletes what is on the other side. This
# function had no such guard, and the cost was the host's /dev: a diagnostic
# session left `mount --rbind /dev "$ROOT/dev"` in place, the next run wiped
# the rootfs, and the delete went through the mount into the real /dev. Every
# device node went -- zero, full, random, urandom, tty, console -- and
# /dev/null came back later as a regular file created by a redirection, which
# broke every `> /dev/null` on the machine.
#
# Two guards, because the first one is not enough: unmount, then CHECK, then
# refuse. A stale mount is a reason to stop, never a thing to delete through.
umount_chroot
if findmnt -rno TARGET | grep -qF "$ROOT/"; then
printf 'still mounted under %s:\n' "$ROOT" >&2
findmnt -rno TARGET | grep -F "$ROOT/" | sed 's/^/ /' >&2
die "refusing to rm -rf through a mount"
fi
sudo rm -rf "$ROOT" "$CACHE"
sudo mkdir -p "$ROOT/var/lib/pacman" "$CACHE"
# EVERYTHING in stage 1, not a hand-picked core.
@ -221,6 +239,19 @@ EOF
# invocation, so resuming has to be correct rather than approximately
# correct.
#
# --nodeps TWICE, which is not a typo. pacman documents it: `-d` skips
# dependency VERSION checks, `-dd` skips dependency checks entirely. With
# one, cython could not be installed --
#
# warning: cannot resolve "python-pygments", a dependency of "cython"
# :: unable to satisfy dependency 'python-numpy' required by cython
#
# -- and the numbers matter: cython is wanted here only as a BUILD tool for
# libseccomp's Python binding, and its declared runtime dependencies are
# numpy and pygments. numpy brings BLAS and LAPACK. A single -d looked like
# it was skipping dependencies and was only skipping their versions, which
# nearly bought an entire numerical stack to compile one .pyx file.
#
# --nodeps for the same reason the per-package install uses it: a chroot
# halfway through stage 2 is a mixed population, some packages declaring
# versioned soname dependencies and some declaring names. -U is fed the
@ -284,7 +315,7 @@ EOF
fi
if [ "${#back[@]}" -gt 0 ]; then
sudo pacman --root "$ROOT" --config "$CONF" --cachedir "$CACHE" \
--noconfirm --nodeps -U "${back[@]}" \
--noconfirm --nodeps --nodeps -U "${back[@]}" \
> "$WORK/stage2-restore.txt" 2>&1 \
|| { tail -20 "$WORK/stage2-restore.txt" >&2; die "restoring stage-2 output failed"; }
printf ' %s stage-2 package(s) restored\n' "${#back[@]}"
@ -362,6 +393,24 @@ configure_chroot() {
# zz- so it sorts last: the drop-ins are read in glob order, and this has to
# be the final word. Owned by no package, and remade on every run since
# make_rootfs wipes the tree.
# DownloadUser, which the rebuilt pacman brings with it. nss failed on
#
# error: problem setting DownloadUser 'alpm' (user does not exist)
# ==> ERROR: 'pacman' returned a fatal error (1)
#
# Arch's pacman.conf drops privileges to `alpm` for downloads, and that user
# exists on a real Arch system because the package creates it via sysusers.d
# -- which `pacman --root` does not run, the same reason the CA bundle was
# never generated. Nothing in this chroot downloads anything (every source is
# already in /build, every package installed with -U from a local file), so
# the setting has no purpose here and its absence is fatal only because
# pacman checks it at startup.
if [ -f "$ROOT/etc/pacman.conf" ]; then
sudo sed -i 's/^\s*DownloadUser/#DownloadUser/' "$ROOT/etc/pacman.conf"
sudo grep -q '^#DownloadUser' "$ROOT/etc/pacman.conf" &&
printf ' pacman: DownloadUser disabled (no such user in a chroot)\n'
fi
sudo install -d -m0755 "$ROOT/etc/makepkg.conf.d"
sudo tee "$ROOT/etc/makepkg.conf.d/zz-s390x.conf" > /dev/null <<'EOC'
# stage 2: every flag variable, because Arch's are x86_64's and the drop-ins
@ -485,13 +534,42 @@ mount_chroot() {
# /dev/pts is not optional: without it any build step that opens a pty --
# and gcc's testsuite driver does -- fails in a way that names the pty and
# not the missing mount.
for m in proc sys dev dev/pts; do
for m in proc sys dev; do
sudo mkdir -p "$ROOT/$m"
done
mountpoint -q "$ROOT/proc" || sudo mount -t proc proc "$ROOT/proc"
mountpoint -q "$ROOT/sys" || sudo mount -t sysfs sys "$ROOT/sys"
mountpoint -q "$ROOT/dev" || sudo mount --bind /dev "$ROOT/dev"
mountpoint -q "$ROOT/dev/pts" || sudo mount -t devpts devpts "$ROOT/dev/pts"
# /dev by --rbind, then --make-rslave. Not a plain --bind, and nothing under
# it is ever modified.
#
# A plain --bind does NOT carry submounts, so the chroot saw /dev/shm as the
# empty 0755 stub inside devtmpfs rather than the host's tmpfs. That cost
# three passes: CPython runs sem_open at configure time, got EACCES, decided
# the platform has no POSIX semaphores, and compiled _multiprocessing without
# SemLock -- after which every consumer failed with a message blaming the
# platform, days after the cause.
#
# The first fix mounted a separate tmpfs at $ROOT/dev/shm and chmod'ed it.
# That was wrong in a way worth recording: $ROOT/dev was a bind of the HOST's
# /dev, so writes under it reach the real /dev -- and the host's /dev/null
# ended up replaced by a regular file, which broke every redirection on the
# machine until it was recreated with mknod.
#
# --rbind brings the host's /dev/shm along as it is: already a tmpfs, already
# drwxrwxrwt. Nothing to create, nothing to chmod, nothing of the host's to
# touch. --make-rslave then stops any mount this chroot makes from
# propagating back out.
mountpoint -q "$ROOT/dev" || {
sudo mount --rbind /dev "$ROOT/dev"
sudo mount --make-rslave "$ROOT/dev"
}
# PROVED from inside, as the build user. Every earlier check of this was made
# as root and passed while the build kept failing.
if ! sudo chroot --userspec="$BUILD_UID:$BUILD_GID" "$ROOT" \
/usr/bin/env -i PATH=/usr/bin sh -c \
'f=/dev/shm/.el-probe.$$; : > "$f" && rm -f "$f"'; then
die "/dev/shm is not writable by the build user; python would build without POSIX semaphores"
fi
# Sources and PKGBUILDs, already fetched by stage 1. Bind-mounting them
# means the chroot needs no network at all, which is worth having: this
# host cannot reach dev.gnupg.org, and a build that silently re-fetches
@ -501,7 +579,9 @@ mount_chroot() {
}
umount_chroot() {
for m in repo2 build dev/pts dev sys proc; do
# -R for dev, because --rbind brought its submounts with it.
mountpoint -q "$ROOT/dev" && sudo umount -R -l "$ROOT/dev"
for m in repo2 build sys proc; do
mountpoint -q "$ROOT/$m" && sudo umount -l "$ROOT/$m"
done
return 0
@ -687,7 +767,7 @@ NOTE
# backstop.
#
# libxml2 -- because the xar cycle reformed INSIDE stage 2. libarchive is in
# STAGE2_SKIP_HOOKS, so stage 2 rebuilt it as Arch does, with xar, linking the
# a hook that runs at stage 1 only, so stage 2 rebuilt it as Arch does, with
# libxml2 that happened to be installed at that moment: the stage-1 one, which
# wants the host's libicuuc.so.76. bsdtar then could not start, and bsdtar is
# what makepkg uses to extract sources and write packages -- so 39 of 39
@ -718,7 +798,10 @@ NOTE
STAGE2_FIRST=(texinfo libxml2 binutils pkgconf wget libxslt
python-packaging python-pyproject-hooks python-build
python-installer python-setuptools python-wheel
python-setuptools-scm)
python-setuptools-scm python-markupsafe python-jinja
python-pkgconfig cython python-poetry-core
python-vcs-versioning python-fastjsonschema gyp
python-psutil)
# Packages the chroot needs that can only come from repo2.
#
@ -779,10 +862,11 @@ CHROOT_STAGE2_PKGS=(
python-packaging python-pyproject-hooks python-build
python-installer python-setuptools python-wheel
python-setuptools-scm
python-markupsafe python-jinja python-pkgconfig cython
python-poetry-core python-vcs-versioning python-fastjsonschema gyp
python-psutil
)
STAGE2_SKIP_HOOKS=(libgcrypt git meson libarchive)
# Packages whose sources must be extracted on the HOST, because the chroot
# cannot extract anything until they are rebuilt.
#
@ -817,7 +901,17 @@ STAGE2_SKIP_HOOKS=(libgcrypt git meson libarchive)
# Five more packages -- coreutils, sed, grep, findutils, diffutils -- fetch PO
# files the same way and are unblocked by this one rebuild, which is why wget is
# also hoisted into STAGE2_FIRST.
STAGE2_HOST_EXTRACT=(libxml2 wget)
# nss joins them, for the cheapest of the three reasons: its source is
#
# hg+https://hg.mozilla.org/projects/nss#tag=NSS_..._RTM
#
# a MERCURIAL checkout, and the chroot has no hg -- it stopped on
# `hg: command not found`. Packaging Mercurial would work and is not needed: the
# host has it, and this mechanism exists precisely to let the host do the
# fetching. Considered and rejected: mercurial itself is cheap (a Python
# application), but it drags python-docutils in for its man pages and would sit
# in the chroot forever to serve one package's source URL.
STAGE2_HOST_EXTRACT=(libxml2 wget nss)
host_extract() {
local n="$1" h
@ -825,12 +919,6 @@ host_extract() {
return 1
}
skip_hook() {
local n="$1" h
for h in "${STAGE2_SKIP_HOOKS[@]}"; do [ "$n" = "$h" ] && return 0; done
return 1
}
# stage2_build <name> -- rebuild one package inside the chroot and install it.
#
# The hook is applied on the HOST, not in the chroot: hooks are seds over the
@ -839,14 +927,51 @@ skip_hook() {
stage2_build() {
local name="$1"
local dir="$WORK/pkg/$name"
[ -d "$dir" ] || { echo " no checkout for $name" >&2; return 1; }
# CLONE IF ABSENT. Stage 2 used to refuse:
#
# no checkout for python-jinja
#
# It could only rebuild what stage 1 had cloned -- which is wrong for every
# package stage 2 builds and stage 1 cannot. libxslt was the first (Ubuntu's
# libxml2 is too old to configure it), and the Python set followed (their
# package() reads the running python's paths, so the host bakes in
# dist-packages). Those are named in CHROOT_STAGE2_PKGS precisely because
# only this stage can produce them, and then this stage would not fetch them.
#
# Pinned to upstream.lock like stage 1's clone, so a package first built here
# is as reproducible as one built there.
if [ ! -d "$dir/.git" ]; then
local url="https://gitlab.archlinux.org/archlinux/packaging/packages/$name.git"
local sha=""
[ -f "$HERE/upstream.lock" ] &&
sha=$(awk -v n="$name" '$1 == n {print $2}' "$HERE/upstream.lock")
echo " cloning $name"
rm -rf "$dir"
GIT_TERMINAL_PROMPT=0 git clone -q --depth 1 "$url" "$dir" || {
rm -rf "$dir"; echo " clone failed: $url" >&2; return 1; }
if [ -n "$sha" ]; then
git -C "$dir" fetch -q --depth 1 origin "$sha" 2>/dev/null &&
git -C "$dir" checkout -q --detach FETCH_HEAD 2>/dev/null &&
echo " pinned to ${sha:0:9} (upstream.lock)"
else
echo " NOTE: $name is not in upstream.lock; built against HEAD" >&2
fi
fi
( cd "$dir" && git checkout -- PKGBUILD 2>/dev/null ) || true
if [ -f "$PATCH_DIR/$name.sh" ]; then
if skip_hook "$name"; then
echo " hook skipped (stage-1 only)"
else
( cd "$dir" && bash "$PATCH_DIR/$name.sh" ) || {
# EL_STAGE=2, and no skip list any more.
#
# There WAS a list -- naming hooks to ignore here. It skipped the
# whole FILE, and that broke silently the moment a listed hook grew a
# section both stages need: git and meson each kept failing in stage
# 2 on exactly what their new sections fix, while the log said
# "hook skipped (stage-1 only)" about a hook that was by then two
# thirds relevant.
#
# A list of file names cannot say why. The hooks read EL_STAGE and
# decide per section, with the reason written beside the guard.
( cd "$dir" && EL_STAGE=2 bash "$PATCH_DIR/$name.sh" ) || {
echo " hook failed" >&2; return 1; }
# A hook can leave a PKGBUILD that no longer parses, and makepkg
# reports that far from its cause:
@ -863,7 +988,6 @@ stage2_build() {
# calls a valid file broken without it.
( cd "$dir" && bash -O extglob -n PKGBUILD ) || {
echo " hook left an unparseable PKGBUILD" >&2; return 1; }
fi
fi
( cd "$dir" && rm -f ./*.pkg.tar.* ) || true
@ -889,6 +1013,32 @@ stage2_build() {
if host_extract "$name"; then
echo " extracting on the host (chroot bsdtar not usable yet)"
( cd "$dir" && LC_ALL=C.UTF-8 makepkg $mkflags -o -C -f ) || return 1
# Symlinks that point out of the chroot.
#
# nss failed with
#
# /build/nss/PKGBUILD: line 55: ../certdata2pem.py:
# No such file or directory
#
# on a file that was plainly there. makepkg links a PKGBUILD's local
# source files into srcdir, and having extracted on the HOST it wrote the
# host's absolute path -- $WORK/pkg/nss/certdata2pem.py. Inside the
# chroot that path does not exist; only /build does. "No such file or
# directory" for an executable is usually its interpreter, which sent the
# first guess to the shebang; here it was the script itself, reached
# through a link to nowhere.
#
# Rewritten to the same file as the chroot sees it. Only links under
# $WORK/pkg are touched -- a link to anywhere else was not made by this
# mechanism and is not ours to redirect.
local _l _t _n=0
while IFS= read -r _l; do
_t=$(readlink "$_l")
case "$_t" in
"$WORK/pkg"/*) ln -sfn "/build/${_t#"$WORK/pkg/"}" "$_l"; _n=$((_n + 1)) ;;
esac
done < <(find "$dir/src" -maxdepth 2 -type l 2>/dev/null)
[ "$_n" -gt 0 ] && printf ' %s source link(s) repointed at /build\n' "$_n"
# -e: build in the tree already there. -C would wipe it again.
in_chroot "cd /build/$name && makepkg $mkflags -e -f" || return 1
else
@ -930,8 +1080,31 @@ stage2_build() {
# against the finished repository with --nodeps OFF. The relaxation is one
# install command wide, and the check that would catch its consequences is
# still there.
# Install what this package produced, MINUS the alternatives. zlib-ng
# produces zlib-ng-compat, and installing it fails:
#
# :: zlib-ng-compat-2.3.3-1 and zlib-1:1.3.2-3 are in conflict
#
# The package is correct and belongs in repo2; it just cannot be installed
# beside the zlib this chroot was built with. CHROOT_EXCLUDE already says so
# for the populate step, and the same list applies here -- one statement of
# which packages cannot coexist, used in both places.
local keep=() f2 pn2
for f2 in "${produced[@]}"; do
pn2=$(bsdtar -xOf "$f2" .PKGINFO 2>/dev/null | sed -n 's/^pkgname = //p')
if printf ' %s ' "${CHROOT_EXCLUDE[*]}" | grep -q " ${pn2:-_} "; then
printf ' not installed here: %s (alternative to an installed package)\n' "$pn2"
continue
fi
keep+=("$f2")
done
if [ "${#keep[@]}" -eq 0 ]; then
printf ' nothing to install\n'
( cd "$dir" && rm -rf src pkg ) || true
return 0
fi
sudo pacman --root "$ROOT" --config "$CONF2" --cachedir "$CACHE" \
--noconfirm --nodeps -U "${produced[@]}" > "$WORK/stage2-inst-$name.txt" 2>&1 || {
--noconfirm --nodeps --nodeps -U "${keep[@]}" > "$WORK/stage2-inst-$name.txt" 2>&1 || {
tail -10 "$WORK/stage2-inst-$name.txt" >&2; return 1; }
printf ' installed %s package(s)\n' "${#produced[@]}"

View file

@ -279,6 +279,90 @@ STAGE1_PACKAGES=(
# same reason: its package() would take Debian's dist-packages from the
# host's python.
python-setuptools-scm
# systemd generates part of its source with jinja2 templates:
#
# meson.build:1620:15: ERROR: python3 is missing modules: jinja2
#
# Not documentation -- the build cannot proceed without it. python-markupsafe
# comes with it, as jinja's only dependency.
python-markupsafe
python-jinja
# Three more the chroot asked for by name, each a build tool:
#
# python-pkgconfig brotli: ERROR Missing dependencies: pkgconfig
# -- the python MODULE, not pkg-config the program,
# which has been there since the lib64 fix.
# cython libseccomp: ERROR Backend subprocess exited when
# trying to invoke get_requires_for_build_wheel
# -- its python bindings are Cython.
# autoconf-archive tpm2-tss: configure.ac:139: error: undefined or
# overquoted macro: AC_MSG_WARN
# -- an AX_* macro is missing, so autoconf misreads the
# line and blames one it does know.
python-pkgconfig
cython
autoconf-archive
# publicsuffix-list: DATA, not a tool. libpsl stops on
#
# make: *** No rule to make target
# '/usr/share/publicsuffix/effective_tld_names.dat', needed by ...
#
# It compiles the Public Suffix List into a lookup table, so the list itself
# is an input to the build. First entry in this port whose absence is neither
# a program nor a library.
publicsuffix-list
# python-poetry-core: the fourth Python build backend this port has needed.
# python-pkgconfig stops on
#
# ERROR Backend 'poetry.core.masonry.api' is not available.
#
# flit_core, setuptools, hatchling and now poetry-core -- each package picks
# its own, and each one has to exist before that package can be built.
python-poetry-core
# python-vcs-versioning: setuptools-scm was split, and its own build now
# imports the half that left:
#
# from vcs_versioning import Configuration
# ModuleNotFoundError: No module named 'vcs_versioning'
#
# The traceback runs through setuptools' build_meta, so the visible error is
# "Backend subprocess exited when trying to invoke build_wheel" -- a backend
# complaint about a package that split upstream.
python-vcs-versioning
# python-fastjsonschema: poetry-core validates pyproject.toml against a JSON
# schema, so it needs a schema validator to build anything -- including
# itself. ModuleNotFoundError: No module named 'fastjsonschema'.
python-fastjsonschema
# gyp: nss will not configure without it --
#
# Building NSS requires an installation of gyp: https://gyp.gsrc.io
#
# Google's build-file generator, which nss uses to produce its ninja files.
# A Python program, so it goes with the rest of the Python set.
gyp
# --- Rust, and what it needs ---------------------------------------------
#
# NOT optional, and that was measured rather than assumed. ERPLibre's
# poetry.lock requires cryptography 46.0.5 with optional = false, plus jiter,
# orjson, pydantic-core and rpds-py -- all of which build Rust from source
# where no wheel exists, and none exists for s390x. Without Rust,
# `pip install cryptography` fails and ERPLibre does not start.
#
# The bootstrap cycle is not the obstacle it looks like: rust-1.90.0 ships an
# official s390x-unknown-linux-gnu tarball (verified, HTTP 200) and the host
# already carries rustc 1.85.1. What costs is llvm and clang, which Arch's
# rust lists as makedepends.
#
# libedit and python-psutil are all llvm and clang need beyond what is
# already here -- Sphinx is disabled by their hooks, which removes some
# fifteen documentation packages from the bill.
libedit
# python-psutil is a stage-2 package: built on the host it shipped 41
# dist-packages paths, like every Python package whose install path comes
# from the running interpreter. The ARTEFACT check caught it.
python-psutil
llvm
clang
# libaio: a real closure gap. lvm2 stops on
#
# device/bcache.c:30:10: fatal error: libaio.h: No such file or directory

View file

@ -110,11 +110,37 @@ note "2. ARTEFACT -- wrong library directories, which raise no error"
# as its default libdir, which changed where pkgconf installed, which changed
# pkgconf's compiled-in search path, which broke every pkg-config lookup in the
# chroot. One stray .a file at the bottom of that.
# TWO EXEMPTIONS, each named with its reason, because this check condemned
# correct packages on its first run against stage 2's output:
#
# python 17 x s390x-linux-gnu/ usr/lib/python3.14/config-3.14-s390x-linux-gnu/
# usr/lib/python3.14/_sysconfigdata__linux_s390x-linux-gnu.py
# filesystem 11 x usr/local/ usr/local/{bin,etc,games,include,...}
#
# CPython names those two after the build triplet on EVERY platform -- the Arch
# x86_64 package ships _sysconfigdata__linux_x86_64-linux-gnu.py -- so the
# triplet there is CPython's convention, not Debian's layout leaking in. And
# creating /usr/local's skeleton is what the filesystem package is for; the FHS
# requires it.
#
# Exempted by (package, pattern) pair rather than by package, so python is still
# checked for lib64 and dist-packages, and filesystem for multiarch. A blanket
# exemption is how a real leak gets waved through.
_exempt() { # _exempt <pkgfile> <pattern>
case "$(basename "$1")|$2" in
python-3*'|s390x-linux-gnu/') return 0 ;;
filesystem-*'|^usr/local/') return 0 ;;
esac
return 1
}
n=0
for f in "$REPO"/*.pkg.tar.*; do
_l=$(bsdtar -tf "$f" 2>/dev/null)
c=$(grep -c 's390x-linux-gnu/' <<< "$_l")
[ "$c" -gt 0 ] && { bad "$(basename "$f"): $c multiarch paths"; n=$((n + 1)); }
if ! _exempt "$f" 's390x-linux-gnu/'; then
c=$(grep -c 's390x-linux-gnu/' <<< "$_l")
[ "$c" -gt 0 ] && { bad "$(basename "$f"): $c multiarch paths"; n=$((n + 1)); }
fi
c=$(grep -c '^usr/lib64/' <<< "$_l")
[ "$c" -gt 0 ] && { bad "$(basename "$f"): $c paths under usr/lib64"; n=$((n + 1)); }
# dist-packages: Debian's name for site-packages.
@ -134,8 +160,10 @@ for f in "$REPO"/*.pkg.tar.*; do
[ "$c" -gt 0 ] && { bad "$(basename "$f"): $c paths under dist-packages"; n=$((n + 1)); }
# usr/local: python's posix_local scheme, and anything else that took the
# host's idea of where a local install goes.
c=$(grep -c '^usr/local/' <<< "$_l")
[ "$c" -gt 0 ] && { bad "$(basename "$f"): $c paths under usr/local"; n=$((n + 1)); }
if ! _exempt "$f" '^usr/local/'; then
c=$(grep -c '^usr/local/' <<< "$_l")
[ "$c" -gt 0 ] && { bad "$(basename "$f"): $c paths under usr/local"; n=$((n + 1)); }
fi
done
[ "$n" -eq 0 ] && good "no multiarch, no lib64, no dist-packages, no usr/local"

View file

@ -7,6 +7,7 @@ acl c51beb032d03a50cbd8dae96de23986d21558875 https://gitlab.archlinux.org/archli
attr 13964f8a55dde2d7e407267eb8fbc5ec44bdb585 https://gitlab.archlinux.org/archlinux/packaging/packages/attr.git
audit 8db272a0138b530ab71f98763c808a5713f859a4 https://gitlab.archlinux.org/archlinux/packaging/packages/audit.git
autoconf c69f433ef07dc732887e22cd5d5a7501cd944fef https://gitlab.archlinux.org/archlinux/packaging/packages/autoconf.git
autoconf-archive e5002e5c706204565fb7bb02a923226e8c53131b https://gitlab.archlinux.org/archlinux/packaging/packages/autoconf-archive.git
automake 9697f63e766a66b5da9ccba3fc1a46a798875154 https://gitlab.archlinux.org/archlinux/packaging/packages/automake.git
bash ac765bd0cb9b4c27f75c94bc320bf3c8b57304c4 https://gitlab.archlinux.org/archlinux/packaging/packages/bash.git
binutils 5b5ba27ac801a1e7d11819e02a80605ea33434dd https://gitlab.archlinux.org/archlinux/packaging/packages/binutils.git
@ -19,6 +20,7 @@ coreutils f33db1312f616064d4730f92e793d6a761a69e5a https://gitlab.archlinux.org/
cppdap 4a8ba28125a5fcaf5870b47177bbd6b7949ed59d https://gitlab.archlinux.org/archlinux/packaging/packages/cppdap.git
cryptsetup 5fd56afd0d44fad6129716d89103a48d29aa7ef1 https://gitlab.archlinux.org/archlinux/packaging/packages/cryptsetup.git
curl 45e9c1b535b60dd96c05f0c5dc7c07a8ea5947ac https://gitlab.archlinux.org/archlinux/packaging/packages/curl.git
cython 803d705dd505efa0e6c649164c2d609bb91121d9 https://gitlab.archlinux.org/archlinux/packaging/packages/cython.git
db5.3 ef842314637f28c5ae1a5e803ad3799965c0f27e https://gitlab.archlinux.org/archlinux/packaging/packages/db5.3.git
dbus f9960c26b0014b2b0b750425010feacda1b42be8 https://gitlab.archlinux.org/archlinux/packaging/packages/dbus.git
diffutils 63d25ebd4b3fc2850529955960643c07221bc233 https://gitlab.archlinux.org/archlinux/packaging/packages/diffutils.git
@ -44,6 +46,7 @@ gperf 1ad3213d13d12c8507fab53ec15ec0559be99ffc https://gitlab.archlinux.org/arch
gpgme ce68a023c3cd8e348f87c83666e0a2713cfca47b https://gitlab.archlinux.org/archlinux/packaging/packages/gpgme.git
grep b6557325ffa4abe40e4785823939a42093218acd https://gitlab.archlinux.org/archlinux/packaging/packages/grep.git
groff f84578039481409d2c880adc5bd5dcc4b3285dbb https://gitlab.archlinux.org/archlinux/packaging/packages/groff.git
gyp 602ee84a4028d468431a12684a0bea63c7c37bc2 https://gitlab.archlinux.org/archlinux/packaging/packages/gyp.git
gzip 4b347e7230151d6980db3251f1c65a7b27956c10 https://gitlab.archlinux.org/archlinux/packaging/packages/gzip.git
help2man e2c0cff5c9566eb76b322950e8ad85b6dd15e9cb https://gitlab.archlinux.org/archlinux/packaging/packages/help2man.git
hicolor-icon-theme 671ceecae2fa1f55a7fef9ead1d4cecfb3010d62 https://gitlab.archlinux.org/archlinux/packaging/packages/hicolor-icon-theme.git
@ -125,14 +128,22 @@ perl-timedate 0c4bac17183489ece9370298f45faf8fef586cb4 https://gitlab.archlinux.
pinentry 960a245f317f34fce281246f46d66f0d9b9d5eae https://gitlab.archlinux.org/archlinux/packaging/packages/pinentry.git
pkgconf fd5cdd4c656e1d7db763518d55a95f085e80a6f1 https://gitlab.archlinux.org/archlinux/packaging/packages/pkgconf.git
popt a40d1ac24230f1aab1034cf67423694fcbe3be4c https://gitlab.archlinux.org/archlinux/packaging/packages/popt.git
publicsuffix-list 3d200a288c966353b3638eb2e0aeabcbf8c2ba88 https://gitlab.archlinux.org/archlinux/packaging/packages/publicsuffix-list.git
python 3254b074f643d1fe29c72516f4ccb5dc4eeba2ac https://gitlab.archlinux.org/archlinux/packaging/packages/python.git
python-build 4d3e49b21fdb9e3648ca89bae4f27cb9a3681b78 https://gitlab.archlinux.org/archlinux/packaging/packages/python-build.git
python-fastjsonschema 5160e0bd608c23fb9f8cef53392a47d7f8156747 https://gitlab.archlinux.org/archlinux/packaging/packages/python-fastjsonschema.git
python-flit-core 85b6eb1e07f482cd748b30e6feee8b3d09c443d1 https://gitlab.archlinux.org/archlinux/packaging/packages/python-flit-core.git
python-installer eac45dc1cca1a3e08192ded2f206b9882ba62559 https://gitlab.archlinux.org/archlinux/packaging/packages/python-installer.git
python-jinja d39adbed48638fb2aeec1fa3c14a829e6498ffaf https://gitlab.archlinux.org/archlinux/packaging/packages/python-jinja.git
python-markupsafe bf271fe95f2b5706e30bd2278848ed344854b2f8 https://gitlab.archlinux.org/archlinux/packaging/packages/python-markupsafe.git
python-packaging a61fb6392a3d58655c46d6fba09f8edb978bb562 https://gitlab.archlinux.org/archlinux/packaging/packages/python-packaging.git
python-pkgconfig 3f70bf174624c01d2ac1431430f9f5ddd0aca302 https://gitlab.archlinux.org/archlinux/packaging/packages/python-pkgconfig.git
python-poetry-core 89a9484a143dbd201c625e54a46a9902f40b6fe4 https://gitlab.archlinux.org/archlinux/packaging/packages/python-poetry-core.git
python-pyproject-hooks 3f43005e004dd15d6aa2dde0cd3f34886f954b2c https://gitlab.archlinux.org/archlinux/packaging/packages/python-pyproject-hooks.git
python-setuptools 8b56f5626b15d6b62ea6653e34e93d3a7e6dbb3e https://gitlab.archlinux.org/archlinux/packaging/packages/python-setuptools.git
python-setuptools-scm d58e8364e8bc47b9f01ee459f31212f5c7c00d2e https://gitlab.archlinux.org/archlinux/packaging/packages/python-setuptools-scm.git
python-tqdm e0b7ec5522cc742812ba067fab0c0db660333063 https://gitlab.archlinux.org/archlinux/packaging/packages/python-tqdm.git
python-vcs-versioning 86fac49f3111ee3a77cc438d134c2d4fd794a556 https://gitlab.archlinux.org/archlinux/packaging/packages/python-vcs-versioning.git
python-wheel fa134ae45e7a48fc26dbb5c7aa4a78c16f9f61dd https://gitlab.archlinux.org/archlinux/packaging/packages/python-wheel.git
readline 562fc3dc105d29b06ad9bfae84e27387d1d50b6a https://gitlab.archlinux.org/archlinux/packaging/packages/readline.git
rhash 61d85647a7448bab282f716abed0cdfb7709cdce https://gitlab.archlinux.org/archlinux/packaging/packages/rhash.git