Journal BSD Make Pallàs Scripts 2.0

Posté par (page perso) . Licence CC by-sa
15
8
jan.
2014

Cher journal,

je t'écris pour te donner des nouvelles de BSD Make Pallàs Scripts[1], une famille de macros pour make que j'écris et utilise pour:

  • Préparer et publier des documents avec TeX et LaTeX.
  • Développer des macros TeX et LaTeX avec NOWEB.
  • Développer pour OCaml.
  • Préparer un site web statique avec ONSGMLS.
  • Maintenir les fichiers de configuration de systèmes FreeBSD.

Les macros sont publiées sous licence CeCILL-B, une licence de type BSD.

[1] https://bitbucket.org/michipili/bsdmakepscripts

Un atout des macros make sur d'autres systèmes analogues sont d'une part l'universalité de make qui ne vise pas de sous-système particulier, l'ubiquité de make et sa simple extensibilité:

  • make est universel car il propose simplement d'écrire des scripts shells dans une forme particulière, en imposant un flux de traitement adapté aux tâches de type traitement sur des fichiers, sans pour autant prescrire ce que peut-être traité.

  • make est partout. (Parceque.)

  • une macro make est facilement extensible, puisque pour ajouter la commande plop fizz en épilogue à l'installation d'un programme il suffit d'ajouter

after-install:
    plop fizz

Enfin, make peut-être utilisé pour construire de gros projets, comme le système FreeBSD.

Fonctions avancées

Les fonctions avancées sympas sont, pour les documents TeX:

  • Support de bibtex
  • Support des figures générées avec (une version moderne de) METAPOST.
  • Support de profils d'impression PostScript.

Cf: https://bitbucket.org/michipili/bsdmakepscripts/wiki/ProduceLaTeXDocuments

Pour les projets OCaml:

  • Support de la compilation bytecode ou native.
  • Génération de documentation avec OCamldoc.
  • Compilation pour le déboguage.
  • Compilation pour le profilage.
  • Génération de lexers et de parsers.
  • Compilation parallèle.
  • Support de la séparation des sources et des objets.
  • Prépraration des archives de distribution, signature avec GPG.

Cf: https://bitbucket.org/michipili/bsdmakepscripts/wiki/DevelopOCamlSoftware

Compatibilité

J'utilise ces macros dans les environnements suivants:

  • FreeBSD 9.0 et le programme make du système.
  • Mac OS X 10.5 et le programme bsdmake du système.
  • Mac OS X 10.4 et le programme bsdmake du système.
  • Debian 7.0 et le package bmake.

La compatibilité avec ces systèmes est un but du projet. Probablement, les macros peuvent être utilisées sur d'autres systèmes comme NetBSD, OpenBSD et peut-être même INTERIX avec bmake.

Bon développement!

  • # omake

    Posté par . Évalué à 2.

    Est-ce que tu t'es penché sur omake avant de choisir make pour tes macro ?

    Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: omake

      Posté par (page perso) . Évalué à 5.

      Ma première réponse est simplement que j'ai commencé à écrire ces macros en 2000, époque à laquelle omake n'existait probablement pas.

      J'ai commencé à travailler avec GNU Make, je peux donc par contre détailler les raisons qui m'ont motivé à changer.

      La documentation de GNU Make est essentiellement réduite à son manuel de référence. Ce manuel est très complet (à l'époque il faisait plus de 200 pages) mais c'est un manuel de référence: le but de ce document est de décrire très précisément tous les aspects du logiciel, mais pas du tout d'expliquer comment on résout des problèmes avec le logiciel.

      Ces dernières explications sont habituellement contenues dans des introductions, des tutoriels, ou des fiches de recette, comme on peut les appeler. À l'époque je n'avais pas trouvé de tutoriel satisfaisant.

      Finalement, la troisième source d'information sont les exemples. J'avais trouvé très difficile de trouver des exemples intéressants, le gros des Makefile étant généré par automake et pas du tout typique de ce qu'on peut écrire à la main.

      Du côté de BSD Make, que j'avais à disposition puisque j'ai commencé à utiliser FreeBSD à cette époque, la situation est bien différente puisque:

      • Il existe un tutoriel de référence écrit par Adam de Boor, et faisant partie de la documentation standard du système. C'est un document d'excellente qualité qui commence par le b-a ba et montre progressivement comment utiliser variables et macros pour raccourcir et assouplir ses Makefile en introduisant de nouvelles abstractions. C'est aussi un document court et accessible, dont on fait le tour en moins d'une demi-journée.

      • La page de manuel make(1) tient lieu de manuel de référence, elle faisait et fait toujours dans les 8 pages: le logiciel est certe plus simple que la version GNU, mais on trouve rapidement toute l'information! Alors qu'avec le manuel du programme de GNU, j'étais toujours à la recherche de la description de ceci ou de cela, et du passage pas dans l'index deux pages avant…

      • Il existe une abondante littérature de Makefile dans laquelle puiser des idées: la compilation du système FreeBSD est gérée par make, j'avais donc sous la main beaucoup d'exemples à étudier. Et cela prouve au passage qu'en s'y prenant bien—c'est à dire, en faisant comme dans les exemples—on peut organiser des tâches extrêmement complexes avec make.

      Alors oui, le make de FreeBSD est très certainement bien plus primitif que celui de GNU, mais les trois points ci-dessus font bien plus que contrebalancer cette différence technologique. Après tout, l'apparition de C++ n'empêche pas que chaque jour des programmes nouveaux soient écrits en C, une technologie pourtant inférieure sur le papier (que sait faire C que ne sait pas faire C++?). Les raisons à cela sont très certainement (en partie) celles de ma liste: C est à la fois plus simple que C++, bien éprouvé, et les exemples ne manquent pas.

      J'ai passé un peu de temps à regarder omake — que j'ai redécouvert, puisque je me souviens l'avoir vu il y a quelques années — c'est un logiciel très prometteur qui semble très puissant. La documentation est cependant un problème: comme manuel de référence elle semble très complète, mais un examen rapide me laisse penser qu'il n'y a pas d'exemple simple, un exemple simple sans macro, sans fonction sans rien, juste un

      module.obj: module.c module.h
          cc -c module.c

      De plus je n'ai pas vraiment réussi à comprendre — dans le temps imparti! — le statut du shell dans omake. Dans make c'est très clair, si je sais faire quelque chose dans mon shell, je n'ai qu'à le recopier à la fin de mon Makefile en ajoutant une ligne spécifiant les dépendances: c'est à la fois facile à retenir car cela ne fait intervenir que des concepts de base du programme — c'est quelque chose qu'on sait faire même si on s'est arrêté au premier exemple du tutoriel d'Adam de Boore! — et facile à mettre en œuvre car cela s'appuie directement sur le shell.

  • # Problématique

    Posté par (page perso) . Évalué à 3.

    J'ai lu la documentation en diagonale et ce qui m’embête c'est que j'ai l'impression que c'est (pour la partie latex du moins) une n-ième version de rubber/latexmk/yourbuildscript.

    Ce que j’attends d'un outil de build pour latex c'est une gestion correcte des dépendances générées. Rubber ne sait faire cela que pour des cas simples (i.e.: générer un .poulet à partir d'un .tortue), latexmk à le même problème, mais sait déléguer à make, mais cette délégation se fait en plusieurs passes et généralement c'est le carnage (i.e.: tu appelles make, qui appelle latexmk pour générer la liste des dépendances, qui appele make, qui génère celles-ci et rappel latexmk… 9 fois sur 10 j'ai un truc recompilé pour rien ou pas recompilé, quand je ne parle même pas d'un echec de latexmk qui perd les pédales).

    J'ai craqué, j'ai fais une tambouille maison dégueulasse et utilisable seulement par moi et cela me déprime, et chaque fois que je vois quelqu'un proposer un outil de build pour LaTeX, j'espere que on va me sortir de ce calvaire.

    Et vous, vous gérez comment vos dépendances générées ? Attention, je ne parle pas de cas simple du genre convertir des .svg en .pdf, mais plutôt de cas du genre vous voulez afficher une image qui est le résultat du rendu d'une scène avec blender, puis passé dans 4 passes d'image magick, passé dans FFMPEG pour ressortir des images de diagnostic, et bien évidement tout cela doit être paramétré dans le .tex car je peux vouloir changer un paramètre de rendu.

    • [^] # Re: Problématique

      Posté par (page perso) . Évalué à 4. Dernière modification le 08/01/14 à 19:44.

      Je ne connais ni rubber ni latexmk donc je ne peux pas trop comparer à ce que j'ai écrit.

      Écrire des macros pour make c'est la même chose que de la programmation en shell à ceci près que la logique du traitement n'est pas exprimée par des procédure mais par des recettes et des dépendances. On explique seulement à make les étapes élémentaires de la construction et make se débrouille pour constuire un plan d'action.

      Pour ta question précise, j'ai une réponse précise, en trois étapes:

      1. On spécifie clairement le problème.
      2. On résout le problème dans le shell.
      3. On intègre le point 2 dans le workflow de make.

      Voilà la spécification que je propose: je suppose que tu as une macro plopfizz qui prend en argument les paramètres de ton image et fait deux choses. Premièrement elle écrit ces paramètres dans un fichier id.plopfizz ou id caractérise l'appel. Deuxièmement elle regarde si l'image id.png résultat du traitement existe déjà pour éventuellement l'inclure dans le document. Ce n'est certainement pas exactement ta situation, mais probablement assez similaire.

      On prépare une routine shell, disons process-plopfizz.sh qui lit un fichier id-plopfizz et prépare l'image id.png. La prépration d'un document ressemble donc à

      $ pdflatex galley.tex
      $ sh ./process-plopfizz.sh -o id1.png id1.plopfizz
      $ sh ./process-plopfizz.sh -o id2.png id2.plopfizz
      $ pdflatex galley.tex

      Maintenant on peut intégrer dans le workflow de make. Pour cela, comme make se fie aux timestamp d'un fichier pour savoir s'il a été modifié, il faut être un peu sioux et améliorer la macro TeX plopfizz pour qu'elle lise le fichier id.plopfizz s'il existe et n'en écrive un nouveau que si les paramètres ont changé.

      À la fin, le fichier Makefile ressemblerait à

      DOC= galley.tex
      .include "latex.doc.mk"
      
      ${COOKIEPREFIX}galley.pdf.aux: id1.png id2.png
      
      id1.png: id1.plopfizz
           sh ./process-plopfizz.sh -o id1.png id1.plopfizz
      
      id2.png: id2.plopfizz
           sh ./process-plopfizz.sh -o id2.png id2.plopfizz
      
      CLEANFILES+= id1.png id2.png
      DISTCLEANFILES+= id1.plopfizz id2.plopfizz
      

      La ligne

      ${COOKIEPREFIX}galley.pdf.aux: id1.png id2.png

      est l'incantation vaudou qui insère notre solution dans le workflow de make. Les déclarations suivantes sont des recettes make standard et les deux dernières lignes ajoutent les produits aux listes de fichiers à effacer, avec make cleanmake distclean. La forme make distclean efface moins de fichiers que make clean. Ici les images sont conservées mais pas les fichiers intermédiaires plopfizz, ce qui facilite la préparation d'une archive qu'on envoie à son éditeur par exemple.

      On voit bien que la préparation des images pourrait être améliorée en introduisant des règles génériques ou des variables, mais j'ai pensé qu'on pouvait dans un premier temps se contenter d'une solution “bébête” qui n'aie pas l'air artificiellement compliquée.

      Bien-sûr c'est garanti sans test, mais c'est la route à suivre.

      P.S.: Si tu prépares un use case ou bien une use story un peu plus précis, je peux te donner plus de détails! L'idéal serait quelque chose que je puisse incorporer au source dans le dossier test.

      • [^] # Re: Problématique

        Posté par (page perso) . Évalué à 3.

        Sommaire

        Merci pour ce message.

        Ta solution est intéressante, sauf qu'elle force à décrire dans le makefile la liste de toutes tes dépendances. Ma (courte) thèse contient actuellement plusieurs dizaines de dépendances générées et je n'ai pas envie de devoir les décrire une par une dans le makefile sachant que je le fais déjà dans le .tex.

        Use case simple

        J'ai besoin de convertir les .poulet en .tortue.

        dans latex:

        \includegraphics{hibou.tortue}
        

        Là latexmk (ou rubber) est capable d'appeler make pour toutes les dépendances, ainsi :

        $ make hibou.tortue
        

        Il y a une règle relativement simple qui dit que :

        %.tortue: %.hibou
            tortufy $*
        

        Use case avancé

        J'ai un programme qui prend en paramètre un nom de svg (source.svg) et la liste de calques à exporter (calque1 et calque2) et qui génère un .pdf.

        Dans mon latex:

        \includegraphics{source__calque1_calque2_layers.pdf}

        Le soucis c'est que on ne peut plus faire de règle makefile simple du genre :

        %1__%2_layers.pdf: %1.svg
            export_mon_svg --input $1.svg --layers $2 --output $1__$2_layers.pdf
        

        avec %1 et %2 des trucs génériques qui seront par la suite associés à $1 et $2

        Ce cas reste encore un peu simple, car finalement il suffit d'écrire pour chaque fichier svg du répertoire la règle :

        fichier__%_layers.pdf: fichier.svg
             export_mon_svg --input fichier.svg --layers $2 --output fichier__$2_layers.pdf
        

        et on peut faire cela avec une macro make:

        define LAYER_template
        $(1)__%__layer.pdf: $(1).svg
            python2 scripts/layer.py $(1).svg $$* $(1)__$$*__layer.pdf
        
        endef
        
        SVG_LAYER_NAMES = $(shell hg manifest | grep .svg | sed 's/\.svg//')
        
        $(foreach p, $(SVG_LAYER_NAMES), \
          $(eval $(call LAYER_template,$(p))) \
        )
        

        (ici il me prend tous les .svg que mercurial connait, mais bon)

        Ça commence à faire mal ici (en gros, je suis le seul humain de tout ceux qui bossent sur ce projet (i.e, heureusement que c'est ma thèse) qui veut encore lire le makefile).

        Cas pathologique (90% de mes use cases)

        Je veux un fichier de sortie qui dépend d'un nombre de fichiers d'entrés indéterminé et de N paramètres. Dans mon latex je fais :

        \includegraphics{fichier1_fichier2_fichier3__param1_param2__uberrule.png}
        

        et dans mon makefile, et bien je ne sais pas ce que je fais. J'avais adopté une nouvelle solution, je sous-traitais à un script :

        %__uber.png: 
            mon_script $*
        

        qui génère la liste de dépendances qui sera évalué par make à la prochaine passe pour gérer correctement mon build.

        Et là je meurs.

        Solution actuelle

        Alors finalement je fais autrement. J'ai une fonction

        \includegraphisCommand{}
        

        Qui prend une commande.

        LaTeX ouvre un pipe et écrit cette commande et lit dans un pipe le nom du fichier à inclure.

        Un démon (en python, mais on s'en cogne) lit dans le pipe la commande à exécuter, et écrit le nom temporaire de sortie après exécution (en gérant bien évidement un cache pour ne pas reconstruire tout à chaque fois).

        Exemple :

        \includegraphicsCommand{svg('input.svg', ['calque1', 'calque2'])}
        

        Et dans mon code python, j'ai défini une fonction :

        def svg(input, layers):
             output_name = nom_unique('svg', input, layers) # genere un nom unique pour ce fichier de sorti, basé sur le nom et les arguments de la méthode
             if must_rebuild([output_name], [input]): # fonction générique qui regarde si il faut rebuilder
                   # action pour builder
        
              return output_name
        

        Ainsi:

        a) chaque action de build est en fait une simple fonction python dans un fichier, fonction paramétrée comme bon me semble et appelée naturellement depuis mon .tex. Mon pire exemple c'est :

        \includegraphicsCommand{calcul_vecteurs_mouvements(resize((800, 600), rendu_video_blender(scene.blend, start_frame=0, end_frame=12, resolution=(800, 600), samplesPerPixels=12, materials=('red', 'blue'))))}
        

        b) Je m’embête plus à trouver des noms pour mes fichiers générés, j'ai une fonction de hash à la con qui fait cela, met tout dans /tmp et voila. En plus c'est sympa, le nom dépend des paramètres de la commande et pas de l'endroit ou elle est appelée dans le .tex. Ainsi si je décide de tester en changeant un paramètre, cela va compiler pendant 2 heures, et si je revient sur la version précédente, cela va reprendre l'ancien fichier déjà présent dans le cache.

        c) Toutes les dépendances sont mise à jour lors de la passe sur le .tex et dirigées par le contenu du .tex. Ainsi tout est bien synchronisé.

        d) Je peux intégrer des "plugins" dans mon outil de rendu. Par exemple, à tout moment, je peux dire que les résultats images qui sortent sont downscalés, ainsi mon pdf se construit plus vite (cela permet d'avoir le bon visuel dans le pdf, à la qualité près, sans prendre 30 minutes parce que toutes mes images hautes résolutions tuent les performances)

        Ma plus grosse limitation actuellement, hormis le coté code non robuste et pas du tout vendable, c'est que cela ne gère pas la construction en parallèle des dépendances.

        Donc pour conclure, je cherche un outil plus puissant pour générer mes dépendances. Actuellement j'ai une solution foireuse qui fonctionne, mais pas très sérieuse.

        • [^] # Re: Problématique

          Posté par (page perso) . Évalué à 3.

          tl;dr: Je ne veux pas à avoir à lister mes dépendances autre part que dans le .tex et je veux pouvoir lister facilement celles-ci, même quand elle proviennent d'un processus de construction très complexe ayant plusieurs fichiers en dépendance et plusieurs paramètres.

        • [^] # Re: Problématique

          Posté par (page perso) . Évalué à 2.

          Ta solution est beaucoup trop compliquée!

          Pour tirer parti correctement de make tu as besoin de préciser correctement tes dépendances et de tenir compte des timestamps. Comme tu sais programmer des dæmons en Python, je suppose que des programmes plus simples sont également à ta portée.

          Voici ce que je propose:

          Pour commencer, on définit une commande TeX MyFancyGraphic qu'on utilise comme suit:

          \begin{MyFancyGraphic}{filename}
          calcul_vecteurs_mouvements(resize((800, 600), rendu_video_blender(scene.blend, start_frame=0, end_frame=12, resolution=(800, 600), samplesPerPixels=12, materials=('red', 'blue'))))
          \end{MyFancyGraphic}

          Dont l'effet est d'inclure l'image filename et de poubelliser (ignorer) le reste.

          Ensuite on écrit un programme makedepend_blender — en OCaml, mais on s'en cogne ;) — qui prépare les dépendances. Il lit tes fichiers TeX et écrit un fichier .depend_blender contenant des lignes du type

          # DRAFT vaut no quand on prépare la version finale du document et yes
          #    autrement.
          DISTCLEANFILES+= filename.in
          REALCLEANFILES+= filename.png
          filename.png:   filename.in
              sh ./call_blender.sh -D ${DRAFT} -o filename.png filename.in

          (Si les fichiers image ont d'autres dépendances, il faut trouver une convention pour les ajouter dans MyFancyGraphic et demander à makedepend_blender d'écrire leurs noms à droite de filename.in.)

          De plus makdepend_blender écrit aussi le contenu de l'appel à la commande dans le fichier filename.in — mais ne réécrit pas le fichier si le contenu n'a pas changé, ce qui évite de changer inutilement le timestamp.

          Finalement, côté Makefile cela donne:

          DOC=    these
          SRCS=   macros.tex
          SRCS+=  chaptitre3.tex
          
          .include "latex.doc.mk"
          
          .if exists(.depend_blender)
          .include .depend_blender
          .endif
          
          depend: .depend_blender
          
          .depend_blender: ${SRCS}
              makedepend_blender ${SRCS} > ${.TARGET}
          
          REALCLEANFILES+= .depend_blender

          Et voilà. :)

          • [^] # Re: Problématique

            Posté par (page perso) . Évalué à 2.

            Pour commencer, on définit une commande TeX MyFancyGraphic
            […]
            Dont l'effet est d'inclure l'image filename et de poubelliser (ignorer) le reste.

            Donc il faut inventer un nom, c'est à mon sens une limitation.

            Ensuite on écrit un programme makedepend_blender — en OCaml, mais on s'en cogne ;) — qui prépare les dépendances. Il lit tes fichiers TeX et écrit un fichier .depend_blender contenant des lignes du type

            Donc il faut que le programme parse le document racine à la recherche d'inclusions vers d'autres documents. C'est faisable, mais c'est dur à rendre robuste (si par exemple quelqu'un défini une macro qui appelle \input).

            (Si les fichiers image ont d'autres dépendances, il faut trouver une convention pour les ajouter dans MyFancyGraphic et demander à makedepend_blender d'écrire leurs noms à droite de filename.in.)

            Donc tu dois spécifier les dépendances à la main, alors que certaines commandes pourraient les gérer d'elles mêmes.

            Par contre ce que tu propose me donne une idée pour simplifier mon implémentation. Je garde la commande "MyFancyGraphic", sans le "filename", et la seule chose que fait latex c'est générer un nom de fichier unique basé sur la commande et d'inclure ce nom de fichier unique. Après mon script de build parse le fichier .tex (et ses sous-fichiers) et génère les dépendances en utilisant le même mécanisme, et c'est gagné.

            • [^] # Re: Problématique

              Posté par (page perso) . Évalué à 2.

              Donc il faut inventer un nom, c'est à mon sens une limitation.

              Pas forcément c'est just un exemple. Ce qui compte c'est d'avoir une idée claire de ce qu'on veut pour pouvoir l'implémenter — c'est pour ça que j'ai choisi un exemple. Mon but est plutôt d'expliquer la partie make.

              Donc il faut que le programme parse le document racine à la recherche d'inclusions vers d'autres documents.

              Il y a mille façons de régler ça. Moi, ça ne me dérange pas d'écrire la liste de mes sources TeX à la main dans le Makefile et c'est facile. Une solution automatique pourait par exemple parser le log du job TeX — si c'est trop difficile on peut redéfinir la primitive input pour qu'elle écrive le nom du fichier de façon facile à reconnaître.

              Donc tu dois spécifier les dépendances à la main, alors que certaines commandes pourraient les gérer d'elles mêmes.

              Encore une fois c'est juste un exemple, et mon but est plutôt d'expliquer la partie make. Si makedepend_blender peut calculer une partie des dépendance, pas de raison de s'en priver.

              Ceci dit, écrire les dépendances à la main est en général peut contraignant — si on écrit la liste une seule fois à un seul endroit — car il s'agit d'une liste stable et cela permet d'avoir cette liste sous les yeux pour déboguer.

              • [^] # Re: Problématique

                Posté par (page perso) . Évalué à 2.

                Il faut que je prenne quelque temps pour réfléchir à l'approche que tu proposes, mais c'est en effet intéressant, merci.

                Ceci dit, écrire les dépendances à la main est en général peut contraignant — si on écrit la liste une seule fois à un seul endroit — car il s'agit d'une liste stable et cela permet d'avoir cette liste sous les yeux pour déboguer.

                C'est un point sur lequel je ne suis pas d'accord, la liste de dépendance c'est implicitement le document .tex qui la donne et je n'ai pas envie de me répéter, sachant que je change souvent celles-ci.

                • [^] # Re: Problématique

                  Posté par (page perso) . Évalué à 2.

                  sachant que je change souvent celles-ci

                  Donc dans ton cas la liste n'est pas stable! Une solution automatique de type makedepend est effectivement à envisager.

                  Si tu as des problèmes avec BSD Make, n'hésite pas! :D

                  Et bonne chance pour ta thèse, après c'est que des bons souvenirs!

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.