FreeBSD : Installation de Nethack 5.0.0

Code source.

https://github.com/HackTechDev/Nethack54FreeBSD

Captures d'écran.

nethack501.png

nethack502.png

nethack503.png

Analyse de compilation de NetHack 5.0.0 pour FreeBSD 15.0

Ce document résume la tentative de compilation du dépôt avec le fichier de hints sys/unix/hints/linux.500, réalisée sur la machine locale. Il complète le fichier README officiel du projet (à ne pas confondre avec celui-ci) et les instructions de build déjà consignées dans CLAUDE.md.

Contexte

La machine hôte n'est pas Linux mais FreeBSD (gcc (FreeBSD Ports Collection) 14.2.0, noyau FreeBSD 15.0). Le fichier de hints linux.500 a tout de même été utilisé tel quel, à la demande explicite, plutôt qu'un hints file FreeBSD dédié (qui n'existe pas dans cette distribution). Cela a permis de mettre en évidence plusieurs incompatibilités entre les hypothèses du système de build (pensé pour Linux/macOS) et l'environnement réel.

Étapes suivies

cd sys/unix
sh setup.sh hints/linux.500      # génère Makefile, dat/Makefile, doc/Makefile,
cd ../..                          # src/Makefile, util/Makefile
gmake fetch-lua
gmake WANT_WIN_ALL=1 QT_SELECT=5 MOCPATH=/usr/local/bin/moc-qt5 \
      LFLAGS=-L/usr/local/lib all

Le binaire src/nethack a fini par être produit avec succès (tty + curses + X11 + Qt), et ./nethack --version rapporte correctement 5.0.0 avec le hash du commit courant.

gmake install a ensuite été lancé avec les mêmes variables :

gmake WANT_WIN_ALL=1 QT_SELECT=5 MOCPATH=/usr/local/bin/moc-qt5 \
      LFLAGS=-L/usr/local/lib install

Problèmes rencontrés et corrections

  1. make système incompatible avec les Makefiles générés. /usr/bin/make sur FreeBSD est BSD make, qui échoue sur la syntaxe GNU Make (ifdef, ifeq, endif) utilisée massivement par les Makefiles générés par setup.sh. Il faut utiliser gmake (GNU Make 4.4.1, installé séparément via les ports/pkg), disponible dans PATH. → Correction : préfixer toutes les commandes make par gmake.

  2. Port Qt : binaire moc introuvable au chemin attendu. Le Makefile calcule le chemin du compilateur de méta-objets Qt via MOCPATH ?= $(QTDIR)/bin/$(MOC), avec MOC ?= moc. Sur cette machine, moc n'existe pas sous ce nom ; le paquet Qt5 de FreeBSD l'installe sous /usr/local/bin/moc-qt5 (ou /usr/local/lib/qt5/bin/moc). → Correction : passer MOCPATH=/usr/local/bin/moc-qt5 explicitement sur la ligne de commande make.

  3. Édition de liens : bibliothèques X11 introuvables malgré leur présence. Le bloc ifdef WANT_WIN_X11 du Makefile généré contient en dur LFLAGS += -L/opt/X11/lib — un chemin typique d'une installation XQuartz sur macOS, indépendant du hints file choisi. Sur FreeBSD, les bibliothèques libX11, libXaw7, libXmu, libXext, libXt, libXpm existent bel et bien, mais dans /usr/local/lib, absent du chemin de recherche du linker. → Correction : passer LFLAGS=-L/usr/local/lib sur la ligne de commande. Comme il s'agit d'une variable positionnée en ligne de commande, les affectations += ultérieures dans le Makefile s'ajoutent à cette valeur au lieu de l'écraser (comportement standard de GNU Make), donc le chemin /opt/X11/lib codé en dur reste présent mais n'empêche plus la résolution des bibliothèques.

  4. make install : sed -i en syntaxe GNU, incompatible avec le sed BSD. L'étape post-install qui désactive GDBPATH/PANICTRACE_GDB dans le sysconf installé (utile quand gdb n'est pas présent) est codée en dur dans le Makefile généré : sed -i -e 's;...;' -e 's;...;' $(INSTDIR)/sysconf. Cette syntaxe suppose GNU sed (-i sans argument obligatoire). Sur FreeBSD, sed -i exige un argument de suffixe de sauvegarde juste après (-i '' pour ne pas garder de backup) ; sed BSD interprète alors le -e suivant comme ce suffixe, ce qui casse toute la commande (sed: -e: No such file or directory). Comme c'est la dernière commande de la séquence POSTINSTALL (chaînée par ;), son échec fait échouer toute la cible install, alors que la copie du binaire et des données dans nethackdir a déjà réussi avant ce point. → Correction : faire résoudre sed vers GNU sed (gsed, déjà présent via les paquets FreeBSD) pour cette commande, sans modifier le dépôt ni le système, via un lien symbolique local ajouté en tête de PATH :

    mkdir -p /tmp/sedshim && ln -sf /usr/local/bin/gsed /tmp/sedshim/sed
    PATH=/tmp/sedshim:$PATH gmake WANT_WIN_ALL=1 QT_SELECT=5 \
        MOCPATH=/usr/local/bin/moc-qt5 LFLAGS=-L/usr/local/lib install

    Notes : bdftopcf (conversion de police pour le port X11) est également absent sur cette machine (pkg install bdftopcf le fournirait), mais son échec n'est pas bloquant — ce n'est pas la dernière commande de la chaîne POSTINSTALL, et il ne concerne que le rendu de police du port X11.

  5. Bug amont (indépendant de FreeBSD) : -w Qt avec un espace casse le parsing des arguments. Dans src/earlyarg.c, early_options() traite chaque flag reconnu en appelant consume_arg()/consume_two_args() pour le retirer du tableau argv transmis au reste du programme — sauf le cas 'w' (windowtype) :

    case 'w': /* windowtype: ... */
       arg = lopt(arg, (ArgValRequired | ArgNamOneLetter | ArgErrComplain),
                  "-windowtype", origarg, &argc, &argv);
       if (gc.cmdline_windowsys)
           free((genericptr_t) gc.cmdline_windowsys);
       gc.cmdline_windowsys = arg ? dupstr(arg) : NULL;
       break;   /* pas de consume_arg()/consume_two_args() ici */

    Quand la valeur est donnée en token séparé (-w Qt ou --windowtype Qt), lopt() consomme bien "Qt" dans ses copies locales de argc/argv, mais ce résultat n'est jamais répercuté sur *argc_p/*argv_p (les pointeurs de l'appelant). "Qt" reste donc dans le tableau d'arguments et atterrit plus loin dans sys/unix/unixmain.c, où tout argument positionnel restant après le traitement des options est interprété comme l'ancien paramètre historique nethack N (nombre max de joueurs, pré-SYSCF) :

    if (argc > 1) {
       int mxplyrs = atoi(argv[1]);          /* atoi("Qt") == 0 */
       boolean mx_ok = (mxplyrs > 0);
    #ifdef SYSCF
       config_error_add("%s%s%s",
                        mx_ok ? "MAXPLAYERS are set in sysconf file"
                              : "Expected MAXPLAYERS, found \"",
                        mx_ok ? "" : argv[1], mx_ok ? "" : "\"");

    D'où le message observé au lancement :

    * Expected MAXPLAYERS, found "Qt".
    
    1 error on command line.

    suivi en général d'un second message sans rapport (« Exec to uncompress ... failed », cf. point 6) si un fichier de sauvegarde .gz existe déjà dans save/.

    Le message apparaît en pratique à la fermeture du jeu, pas au lancement. config_error_done() écrit ce texte via pline()/ raw_print() tout au début du programme, avant l'initialisation de Qt/curses — mais cette écriture atterrit dans le buffer standard de stdout, qui n'est pas vidé (flush) immédiatement. Le message reste donc invisible tant que le processus tourne (masqué par la fenêtre Qt ou l'écran alternatif de curses) et ne s'affiche dans le terminal qu'au moment où le processus se termine normalement et vide ses buffers — c'est-à-dire quand on quitte le jeu. Vérifié expérimentalement : en tuant le processus brutalement (SIGTERM via timeout) avant une sortie normale, le message n'apparaît jamais dans la capture, alors qu'il est bien écrit en interne dès l'appel à -w Qt. → Correction : ne pas séparer le flag et sa valeur par un espace — -wQt ou --windowtype=Qt fonctionnent parfaitement (vérifié, y compris à la fermeture normale du jeu). Ce bug n'est pas spécifique à FreeBSD ; il se reproduirait à l'identique sur Linux ou macOS.

  6. Chemin gzip codé en dur pour Linux (/bin/gzip), absent sur FreeBSD. sys/unix/hints/linux.500 définit NHCFLAGS+=-DCOMPRESS=\"/bin/gzip\" -DCOMPRESS_EXTENSION=\".gz\" — un chemin qui suppose le FHS Linux. Sur FreeBSD, gzip fait partie du système de base mais vit dans /usr/bin/gzip ; /bin/gzip n'existe pas. COMPRESS étant un #define figé à la compilation (utilisé par nh_uncompress()/docompress() dans src/files.c via fork()+exec() avec le chemin littéral, pas une recherche PATH), le symptôme n'apparaît qu'au moment où le jeu tente réellement de (dé)compresser un fichier de sauvegarde (save/<uid><user>.gz) :

    No such file or directory
    Exec to uncompress save/1001util01 failed.

    Contrairement aux points 1 à 4, un simple override de variable sur la ligne de commande gmake ne suffit pas ici : le hints file ajoute son propre -DCOMPRESS=... via += après tout override qu'on passerait en NHCFLAGS/CFLAGS, et pour une macro redéfinie plusieurs fois sur la même ligne de commande du compilateur, c'est la dernière définition qui l'emporte — la nôtre serait donc écrasée. → Correction appliquée dans le dépôt, à la demande explicite (seule modification de fichier source de cette analyse ; toutes les précédentes n'étaient que des overrides de ligne de commande) :

    - NHCFLAGS+=-DCOMPRESS=\"/bin/gzip\" -DCOMPRESS_EXTENSION=\".gz\"
    + NHCFLAGS+=-DCOMPRESS=\"/usr/bin/gzip\" -DCOMPRESS_EXTENSION=\".gz\"

    (sys/unix/hints/linux.500, ligne 168). Après modification du hints file, il faut regénérer les Makefiles puis forcer la recompilation des objets concernés, car make ne détecte pas qu'un #define a changé de valeur (seules les dates de fichier sont suivies, pas le contenu des flags) :

    cd sys/unix && sh setup.sh hints/linux.500 && cd ../..
    touch src/files.c src/mdlib.c util/sfctool.c   # seuls fichiers référençant COMPRESS
    gmake WANT_WIN_ALL=1 QT_SELECT=5 MOCPATH=/usr/local/bin/moc-qt5 \
         LFLAGS=-L/usr/local/lib all
    PATH=/tmp/sedshim:$PATH gmake WANT_WIN_ALL=1 QT_SELECT=5 \
        MOCPATH=/usr/local/bin/moc-qt5 LFLAGS=-L/usr/local/lib install

    Vérifié en recréant manuellement un save/1001util01.gz factice et en relançant le binaire : décompression silencieuse et sans erreur.

Hormis le correctif du point 6 (modification volontaire et validée du hints file), aucune autre modification n'a été apportée aux fichiers du dépôt : les corrections des points 1 à 4 ont toutes été appliquées via des variables et un PATH passés sur la ligne de commande gmake.

Lancer le jeu avec le support Qt

Le build ayant été fait avec WANT_WIN_ALL=1, le binaire installé embarque déjà les interfaces tty, curses, X11 et Qt. Le port graphique n'est cependant pas actif par défaut : le binaire a été compilé avec -DDEFAULT_WINDOW_SYS=\"tty\".

Pour sélectionner Qt au lancement, coller la valeur au flag (voir bug n°5 ci-dessous pour la raison) :

/home/util01/nh/install/games/nethack -wQt
# ou
/home/util01/nh/install/games/nethack --windowtype=Qt

Le script wrapper nethack installé se contente de propager tous les arguments ("$@") au binaire réel dans nethackdir, donc l'option fonctionne aussi bien via le script que via l'exécutable direct.

Un lancement de test de 3 secondes avec cette option ne produit aucune erreur et se termine proprement (le DISPLAY de la session, :0.0, est valide). Cela confirme que l'exécutable tente bien de démarrer le port Qt sans échouer immédiatement, mais l'affichage effectif de la fenêtre n'a pas été vérifié visuellement depuis cet environnement.

Pour rendre Qt permanent sans repasser -w Qt à chaque lancement, ajouter dans le fichier de configuration runtime (~/.nethackrc ou NETHACKOPTIONS) :

OPTIONS=windowtype:Qt

L'option de ligne de commande a priorité sur cette entrée, qui elle-même a priorité sur le défaut de compilation (tty).

Pré-requis : Qt étant une interface graphique, un serveur X accessible (DISPLAY valide) est nécessaire — contrairement à tty ou curses qui tournent directement dans un terminal.

Portée de ces observations

Les correctifs 1 à 4 sont spécifiques à cet hôte (FreeBSD avec make BSD, sed BSD, moc fourni par le paquet Qt5 des ports, bibliothèques X11 dans /usr/local/lib) et ne reflètent pas un défaut du hints file linux.500 lui-même sur une vraie machine Linux. Ils ne sont donc pas répercutés dans les instructions générales de CLAUDE.md, qui restent focalisées sur le cas Linux/macOS standard décrit par le projet.

Le correctif 6 (/bin/gzip/usr/bin/gzip) est, lui, spécifique à FreeBSD mais désormais corrigé dans le dépôt (modification volontaire du hints file, pas un simple override de commande) : un build linux.500 sur une vraie machine Linux n'en a pas besoin, mais la modification n'y nuit pas non plus puisque /bin/gzip reste la valeur par défaut la plus courante sur Linux — seul FreeBSD diverge.

Le bug 5 (-w Qt avec espace) est en revanche un défaut réel de NetHack 5.0.0, indépendant de la plateforme : il se reproduirait identiquement sur Linux ou macOS. Il n'a pas été corrigé dans le dépôt (ce serait un patch de src/earlyarg.c, hors du périmètre « configuration de build » de cette analyse) ; le contournement (-wQt au lieu de -w Qt) suffit en pratique.