Rendre Francium portable

Auteurice : Arthur Pons

Temps de lecture : ~8 minutes


A Katzele on écrit souvent du shell. On (je du moins) aime bien dire que ce que l’on écrit est POSIX ou presque et que ça fonctionne donc sur tous les dérivés d’Unix1. De fait, une majorité d’entre nous utilise des systèmes Debian ou basés sur Debian (Ubuntu) et nous testons rarement nos créations sur autre chose. Même si l’on essaye d’énoncer clairement les dépendances d’un projet, la portabilité vers d’autres systèmes ne prend qu’assez peu de notre temps pour quatre raisons :

  1. La faible demande d’adaptation vers d’autres distributions
  2. Le manque de temps
  3. Le manque de matériel (nous n’avons pas facilement accès à un mac)
  4. La croyance que puisque l’on fait du shell simple alors ce sera portable de toute façon

En profitant d’une rare exception à la première raison, je propose de tester les deux dernières raisons en portant Francium sur MacOs version 10.x et supérieure.

Merci à mon frère qui m’a prêté son mac pour faire des tests.

La demande

Timothée Goguely, designer et dev web basé à Strasbourg, souhaite tester Francium pour la création d’un site pour l’un de ses clients soucieux de faire très simple. Timothée utilise un mac, le client utilise un mac, il est donc opportun de tester Francium sur un mac.

Les tests

Construire le projet (make)

Première chose à faire, cloner le projet. Mac vient avec git (en tout cas je n’ai pas eu à l’installer sur les deux macs que j’ai utilisé), il suffit donc de faire :

git clone git://katzele.netlib.re/francium

En théorie, si tout fonctionne bien il suffit ensuite d’installer lowdown, de créer le dossier root dans lequel on génère le site et de lancer make. Pas de souci à l’installation de lowdown via le gestionnaire de paquet communautaire homebrew. Pas de souci pour créer le dossier mais en lançant make on constate que seules les copies du favicon et de la feuille de style css se font. Pas d’exécution de l’unique page de démo du projet. Il se trouve que le make fourni par défaut par Apple sur les Mac (du moins ceux que j’ai testé) est GNU Make version 3.82. Elle ne supporte manifestement pas la syntaxe suivante, présente dans notre makefile :

pages = ${sources:src/%.md=root/%.html}

Or cette syntaxe “traduit” le chemin du fichier source vers le chemin du fichier de destination. Si cette ligne ne fonctionne pas, le makefile ne cherchera jamais à créer le fichier de destination et n’appellera jamais la règle correspondante. Pour y remédier il faut installer GNU Make via brew en faisant brew install make. Brew package une version plus récente de GNU Make (4.1.1) qui supporte cette syntaxe. Après l’installation, brew nous informe que l’on peut appeler cette version de GNU Make en lançant la commande gmake mais qu’une opération sur le PATH peut rendre cette version là disponible en tapant make.

GNU “make” has been installed as “gmake”. If you need to use it as “make”, you can add a “gnubin” directory to your PATH from your bashrc like:

 PATH="$HOMEBREW_PREFIX/opt/make/libexec/gnubin:$PATH"

J’en conviens, cette solution est quelque peu confuse puisqu’elle a pour résultat d’avoir deux version différentes de GNU Make installées sur son ordi, l’une appelée make et l’autre gmake mais ça fait l’affaire.

Lançons maintenant gmake. On constate que la génération de la page html essaye bien d’avoir lieu mais qu’un message d’erreur (parmi d’autres) indique que -D n’est pas une option pour la commande install. Il se trouve que d’après le standard POSIX3, install n’est pas POSIX. Cela ne veut pas dire qu’il ne faut jamais l’utiliser ni qu’il ne se trouvera sur aucun *nix mais que l’on en a pas la garantie et surtout que l’on ne peut pas prédire son comportement et ses options. La preuve, la version disponible sur MacOs ne comporte pas l’option -D qui existe pourtant dans l’install des GNU core utils. Ne trouvant pas d’équivalent commun aux deux versions j’ai préféré le remplacer par :

mkdir -p $(dirname $@)
$< > $@

Qui créé d’abord le dossier de destination à l’aide d’mkdir et dirname qui sont tous les deux POSIX puis exécute le script en redirigeant la sortie vers le fichier de destination.

Si vous exécutiez gmake vous verriez une nouvelle erreur informant qu’il manque une quelque chose à mkdir pour bien fonctionner. En effet la capture de commande $(dirname $@) ne fonctionnera pas tel quel dans le makefile. Il faut faire appel à la fonction shell4 pour exécuter dirname avant que le makefile n’exécute le mkdir. Cela donne donc

mkdir -p $(shell dirname $@)
$< > $@

Avec cette modification nous avons un makefile parfaitement fonctionnel sur MacOs à condition d’avoir mis jour make.

Exécuter les scripts

Si vous suivez l’article vous aurez remarqué qu’il y a d’autres erreurs que nous avons jusque là ignoré. Le “préprocesseur” des pages et le fichier common associé, les fichiers page et common ont été écrit pour Dash, le shell POSIX de Debian par défaut. Il est disponible sur d’autres systèmes.

Les shebang utilisés sur ces deux scripts sont #! /bin/sh. Sous Debian /bin/sh est un lien symbolique vers dash mais ce n’est pas le cas sous tous les systèmes. En l’occurence sur MacOs /bin/sh est un lien symbolique vers le shell de login par défaut du système, c’est à dire zsh. Il fallait donc être plus explicite. J’ai initialement changé les shebang en #! /bin/dash, ce qui fonctionne sous MacOs, mais pour des raisons de meilleure portabilité je l’ai ensuite changé en #! /usr/bin/env dash au cas-où dash soit installé ailleurs qu’à /bin/dash5.

Ce changement corrige une partie des problèmes, lorsque zsh ou bash n’interprètent pas les scripts comme dash mais certaines erreurs subsistent du type :

src/index.md: ligne 2 : fg: pas de contrôle de tâche
src/index.md: ligne 3 : fg: pas de contrôle de tâche
src/index.md: ligne 4 : fg: pas de contrôle de tâche
src/index.md: ligne 5 : fg: pas de contrôle de tâche
src/index.md: ligne 9: Cette : commande introuvable
src/index.md: ligne 13: bin : commande introuvable

Ces erreurs pointent vers le fait que les alias présents dans common ne sont pas instanciés correctement. Si l’on vérifie quel processus se lance lorsque l’on exécute “à la main” la page en faisant src/index.md, on constate que le script est lancé avec le shell par défaut du système (bash src/index.md par exemple). Or, le shebang de src/index.md est #! page. Ce que l’on souhaite c’est qu’en exécutant une page, elle soit passée en argument au script page qui lui même sera exécutée par dash et fera tourner le code dans common. Pour une raison ou pour une autre le shebang est ignoré, page et common non exécutés, le nécessaire n’est pas créé et l’on obtient des erreurs.

Avec Timothée nous avons cherché un moment pourquoi nous constations ce comportement. Il se trouve que MacOs (et peut-être *BSD, on ne sait pas encore), ne supporte pas l’utilisation d’un script contenant lui même un shebang678. Le shebang doit pointer sur un binaire. Un contournement est d’utiliser /usr/bin/env et de lui donner en argument notre interpréteur perso9 :

#! /usr/bin/env ./page

Je n’aime pas trop cette solution que je trouve bien moins sympathique pour l’utilisateurice que d’écrire #! page ou #! article mais c’est le prix de la portabilité. Cela n’empêche évidemment pas d’utiliser la forme courte sur les systèmes Linux.

Avec cette dernière modification on devrait pouvoir faire

gmake

et constater la bonne génération de root.index.html.

Conclusion

J’avais tort en disant “On fait du shell POSIX simple, ça fonctionne partout !”. Je n’aurais pas eu tort si j’avais dit “On fait du shell à peu près POSIX et simple, ça devrait pas être très compliqué à faire fonctionner ailleurs”.

J’en retire qu’il faut être encore plus clair sur les dépendances. Par exemple ne pas dire “make” mais “GNU Make testé en version >4.3”, ne pas dire “interpréteur shell POSIX” mais “Dash”.

La totalité de ce travail, y compris la rédaction de cet article, aura pris quelque part entre 6h et 8h, ce que je trouve raisonnable. La quantité de code modifié est petite, l’esprit du projet n’a pas changé. Je pense que l’on a fait la démonstration qu’à défaut d’avoir été portable directement en faisant des choses simples et en essayant de garder, ne serait-ce qu’un peu, la portabilité en tête on se facilite la tâche pour le jour où il faudra réellement le faire.


  1. Linux, *BSD, MacOs notamment 

  2. Vieille de 21 ans ! 

  3. https://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html 

  4. https://www.gnu.org/software/make/manual/html_node/Shell-Function.html 

  5. Im me semble qu’env peut se retrouver ailleurs que /usr/bin/env mais bon. Je pense pas que l’on puisse faire beaucoup mieux. 

  6. https://stackoverflow.com/questions/9988125/shebang-pointing-to-script-also-having-shebang-is-effectively-ignored 

  7. https://en.wikipedia.org/wiki/Shebang_(Unix) 

  8. https://www.in-ulm.de/~mascheck/various/shebang/#interpreter-script 

  9. https://theinfocentric.com/bash-shebang/