Title: Utiliser make pour compiler ses documents LaTeX Category: linux Tags: make, Makefile, LaTeX Date: 2023-08-30 12:10 cover: assets/backgrounds/machine_zorba.jpg status: draft Dans mon précédent [article]({filename}../fonctionnement_makefile/index.md), nous avons vu comment fonctionne *make* et son langague utilisé dans les `Makefile`. Comme promis, voici le premier example d'utilisation : la construction de document *PDF* à partir de fichiers sources LaTeX, de fichiers *SVG* et d'images matricielles. Nous allons avancer progressivement tout au long de cet article, ainsi nous commencerons avec un `Makefile` des plus basique pour l'améliorer au fur et à mesure. ## Avant de commencer Vous trouverez des fichiers d'exemples pour chacune des étapes [dans cette archive]({attach}./files/examples.tbz). Les corrections sont disponibles dans le répertoire `Makefile/`. Faites attention aux copiés/collés : les tabulations sont transformées en espace dans les blocs de code de cet article. Or *make* se sert des tabulations pour déterminer les actions relatives aux cibles. Lors de l'exécution de *make*, vous aurez l'erreur `Makefile:23: *** missing separator. Stop.`, il suffira de revoir votre indentation. ## Permier pas, simple mais pas efficace Notre base de départ est simple, elle permet de compiler un document appelé `document.tex` dans le répertoire courant. Voici le code de `Makefile`: ```make LC = lualatex LCFLAGS = --interaction=nonstopmode default: document.pdf document.pdf: document.tex $(LC) $(LCFLAGS) $< .PHONY: clean clean: @rm document.aux document.log documents.pdf ``` Nous utilisons ce qui se fait déjà pour la compilation de programme en `C`, à savoir placer le compitateur et ses paramètres dans des variables. Nous avons donc `$(LC)` pour *LaTeX Compiler* -- j'utilise LuaLatex -- et `$(LCFLAGS)` pour les paramètres. ## un répertoire pour les compiler tous À la premièrer compilation, on s'aperçoit vite que plusieurs fichiers apparaissent dans notre répertoire (fichiers `aux`, `log`, `toc` etc.) Il devient alors un peu confus, nous allons faire en sorte de placer tous ces fichiers (ainsi que le fichier PDF généré) dans un répertoire séparé. ```make LC = lualatex LCFLAGS = --interaction=nonstopmode --output-directory $(OUTPUT) OUTPUT = build default: $(OUTPUT)/document.pdf $(OUTPUT)/document.pdf: document.tex $(LC) $(LCFLAGS) $< .PHONY: clean clean: @rm -rf $(OUTPUT) ``` Nous définissons la variables `OUTPUT` qui contient le répertoire dans lequel placer tous les fichiers générés par `lualatex`. L'options qui va bien est ajoutée à `LCFLAGS`. `OUTPUT` est aussi utilisée pour la cible `clean`, notre nettoyage n'en est que plus simple!. Tout ces fichiers sont maintenant dans le répertoire `build/`. ## Tous ne s'appellent pas document Tous nos documents de s'appellent pas *document*, leurs noms dépendent souvent du contexte. Rendons donc ce `Makefile` plus générique grâce aux variables et macros ```make LC = lualatex LCFLAGS = --interaction=nonstopmode --output-directory $(OUTPUT) OUTPUT = build DOCUMENTS = $(addprefix $(OUTPUT)/, $(patsubst %.tex,%.pdf,$(wildcard *.tex))) default: $(DOCUMENTS) $(OUTPUT)/%.pdf: %.tex @mkdir -p $(OUTPUT) $(LC) $(LCFLAGS) $< .PHONY: clean clean: @rm -rf $(OUTPUT) ``` notre macro `DOCUMENTS` est un imbrication de 3 fonction : 1. `wildcard ` pour récupérer une liste de fichier dans le répersoire courant en fonction d'un motif; 2. `patsubst ,,` pour substituer une partie de la chaine dans un chemin, `%` fait ici office de caractère joker; 3. `addprefix , ` pour ajouter un préfixe à chacun des éléments d'une chaine de caracères. Pour *make*, les éléments d'une chaine de caractère sond délimités par des espaces. Elle permet d'obtenir le nom d'une cible en fonction des fichiers `tex` contenu dans notre répertoire : `document.tex` deviendra alors `build/document.pdf`. `$(DOCUMENTS)` est maintenant une dépendance de notre cible `default`. Et chacun de ses élémets appelle la cible `$(OUTPUT)/%.pdf: %.tex` qui dépend de `%.tex` : la source LaTeX, `%` étant remplacé par le nom donné dans la cible : `build/document.pdf` donnerait `document.tex` comme dépendance. Cette solution nous permet maintenant d'appeler notre source LaTeX comme bon nous semble. Mieux encore nous nouvons **avoir plusieurs fichiers** à la racine de notre répertoire, il seront tous compilés indépendament. C'est très pratique lorsque vous avez un rapport et la présentation dans un même dépôt par exemple. ## Les images matricielles Il n'est pas rare qu'un document contienne des **images au format matriciel** : des photos au format JPEG ou des captures d'écran au format PNG par exemple. Il est alors nécessaire de prendre en compte la modification de ces images pour la compilation de nos documents. ```make LC = lualatex LCFLAGS = --interaction=nonstopmode --output-directory $(OUTPUT) OUTPUT = build IMAGES_DIR = images/bitmap DOCUMENTS = $(addprefix $(OUTPUT)/, $(patsubst %.tex,%.pdf,$(wildcard *.tex))) IMAGES = $(wildcard $(IMAGES_DIR)/*.*) default: $(DOCUMENTS) $(OUTPUT)/%.pdf: %.tex $(IMAGES) @mkdir -p $(OUTPUT) $(LC) $(LCFLAGS) $< .PHONY: clean clean: @rm -rf $(OUTPUT) ``` Nous avons maintenant une variable `IMAGES_DIR` qui nous permet de spécifier le répertoire dans lequel sont stockées les images -- nous verrons plus tard que d'autres sous-dossiers d'*images* apparaîtront. La liste des images est récupérée grace à la macro `IMAGES` qui utilise la fonction `wildcard` pour récupérer **tous les fichiers de ce répertoire**. Le résultat de cette macro est donné en dépendance de notre cible de contruction des documents `$(OUTPUT)/%.pdf: %.tex`. **Ainsi si une image est modifiée ou ajoutée, alors la compilation du fichier PDF sera relancée**. Notre technique a cependant deux défauts. D'abord si une image est ajoutée mais n'est pas utilisée le document sera quand même compilé alors que ce n'est pas nécessaire. Enfin si une image utilisée **seulement** dans le document *B* est modifiée, *A* et *C* **seraient aussi compilés**. Ce dernier problème pourrait être résolu, mais au prix d'une complexification de notre processus de compilation. ## Les images SVG Personnellement j'utilise beaucoup *Inkscape* pour produire diverses images. Mais le format SVG n'est pas utilisable tel quel en LaTeX[^n_svgtex]. Il est nécesssaire de passer par une étape intermédiaire pour le transformer en PDF. ```make LC = lualatex LCFLAGS = --interaction=nonstopmode --output-directory $(OUTPUT) SC = inkscape SCFLAGS = --export-type=pdf --export-pdf-version=1.4 OUTPUT = build IMAGES_DIR = images/bitmap SVG_DIR = images/svg SVG_EXPORTED_DIR = images/generated DOCUMENTS = $(addprefix $(OUTPUT)/, $(patsubst %.tex,%.pdf,$(wildcard *.tex))) IMAGES = $(wildcard $(IMAGES_DIR)/*.*) SVG = $(wildcard $(SVG_DIR)/*.svg) SVG_EXPORTED = $(subst $(SVG_DIR), $(SVG_EXPORTED_DIR), $(patsubst %.svg,%.pdf,$(SVG))) default: $(DOCUMENTS) $(OUTPUT)/%.pdf: %.tex $(IMAGES) $(SVG_EXPORTED) @mkdir -p $(OUTPUT) $(LC) $(LCFLAGS) $< $(SVG_EXPORTED_DIR)/%.pdf : $(SVG_DIR)/%.svg $(SC) $(SCFLAGS) -o $@ $< .PHONY: clean clean: @rm -rf $(OUTPUT ``` Le principe est ici simple : nous allons utiliser *Inkscape* pour exporter les fichiers au format PDF. Pourquoi exporter en PDF? C'est un format bien supporté par les moteurs *LaTeX*. Il permet aussi de conserver au maximum le format vectoriel[^n_svgpdf]. Quatres variables font leur apparition : * `SC` pour *SVG compiler*, nous utilisons *Inkscape*; * `SCFLAGS` qui contient les paramètres de la commande `Inkcape`; * `SVG_DIR` qui contient le chemin vers les fichiers SVG; * `SVG_EXPORTED_DIR` qui contient le chemin vers les fichiers PDF exportés depuis *Inkscape*. Nous avons aussi deux macros * `SVG` liste les fichiers SVG, cette macro utilise la fonction `wildcard` interne à *make* qui permet de récupérer une liste de fichiers en fonction d'un motif; * `SVG_EXPORTED` transforme la liste de fichiers SVG en liste de fichiers PDF. Deux fonctions imbriquées sont nécessaires :`subst` qui permet de remplacer un motif dans des chaines et `patsubst` que nous avons vu précédemment. Cette macro est donnée en dépendance de la cible de compilation des documents. Ensuite la cible qui nous permet de convertir les fichiers : `$(SVG_EXPORTED_DIR)/%.pdf: $(SVG_DIR)/%.svg` et la commande associée. [^n_svgtex]: Ce n'est pas tout à fait vrai, il est possible d'utiliser le package LaTeX *svg* et sa commande `\includesvg` mais les contraintes sont nombreuses. [^n_svgpdf]: Sauf dans quelques cas spécifiques comme l'utilisation de certains filtres, mais les éléments concernés seront convertis en matriciels dans le PDF rendu. ## Les cibles accessoires Nous avons vu jusqu'ici les cibles principales, nous allons maintenant ajouter deux cibles qui pourront nous faciliter la vie. ### Afficher des informations Notre `Makefile` est maintenant conséquent, il contient quelques **macros qu'il est parfois utiles d'afficher**. Cette affichage nous permettra par exemple de vérifier les images matricielles prises en compte ou encore les fichiers SVG qui seront exportées. ```make LC = lualatex LCFLAGS = --interaction=nonstopmode --output-directory $(OUTPUT) SC = inkscape SCFLAGS = --export-type=pdf --export-pdf-version=1.4 OUTPUT = build IMAGES_DIR = images/bitmap SVG_DIR = images/svg SVG_EXPORTED_DIR = images/generated DOCUMENTS = $(addprefix $(OUTPUT)/, $(patsubst %.tex,%.pdf,$(wildcard *.tex))) IMAGES = $(wildcard $(IMAGES_DIR)/*.*) SVG = $(wildcard $(SVG_DIR)/*.svg) SVG_EXPORTED = $(subst $(SVG_DIR),$(SVG_EXPORTED_DIR),$(patsubst %.svg,%.pdf,$(SVG))) default: $(DOCUMENTS) $(OUTPUT)/%.pdf: %.tex $(IMAGES) $(SVG_EXPORTED) @mkdir -p $(OUTPUT) $(LC) $(LCFLAGS) $< $(SVG_EXPORTED_DIR)/%.pdf : $(SVG_DIR)/%.svg $(SC) $(SCFLAGS) -o $@ $< .PHONY: clean clean: @rm -rf $(OUTPUT) .PHONY: info info: @echo "document.............'$(DOCUMENTS)'" @echo "bitmap images........'$(IMAGES)'" @echo "SVG images...........'$(SVG)'" @echo "exported SVG images..'$(SVG_EXPORTED)'" ``` Nous n'utilisons pas la commande relatives au messages (`info`, `warning` et `error`) que nous avons vu dans le précédent article, sinon un message `make: Nothing to be done for 'info'.` apparaît après les informations. ### Lancer l'application de visualisation des PDF Lancer la compilation d'un document et l'afficher ensuite dans notre visionneur de document permet souvent de gagner du temps. ```make LC = lualatex LCFLAGS = --interaction=nonstopmode --output-directory $(OUTPUT) SC = inkscape SCFLAGS = --export-type=pdf --export-pdf-version=1.4 VIEWER = zathura VIEWER_FLAGS = --fork OUTPUT = build IMAGES_DIR = images/bitmap SVG_DIR = images/svg SVG_EXPORTED_DIR = images/generated DOCUMENTS = $(addprefix $(OUTPUT)/, $(patsubst %.tex,%.pdf,$(wildcard *.tex))) IMAGES = $(wildcard $(IMAGES_DIR)/*.*) SVG = $(wildcard $(SVG_DIR)/*.svg) SVG_EXPORTED = $(subst $(SVG_DIR),$(SVG_EXPORTED_DIR),$(patsubst %.svg,%.pdf,$(SVG))) default: $(DOCUMENTS) $(OUTPUT)/%.pdf: %.tex $(IMAGES) $(SVG_EXPORTED) @mkdir -p $(OUTPUT) $(LC) $(LCFLAGS) $< $(SVG_EXPORTED_DIR)/%.pdf : $(SVG_DIR)/%.svg $(SC) $(SCFLAGS) -o $@ $< .PHONY: clean clean: @rm -rf $(OUTPUT) .PHONY: info info: @echo "document.............'$(DOCUMENTS)'" @echo "bitmap images........'$(IMAGES)'" @echo "SVG images...........'$(SVG)'" @echo "exported SVG images..'$(SVG_EXPORTED)'" .PHONY: view view: default @$(VIEWER) $(VIEWER_FLAGS) $(DOCUMENTS) ``` Deux nouvelles variables font leur apparition : * `VIEWER` qui contient le nom du programme utilisé comme visionneur, [zathura][l_zathura] dans l'exemple. * `VIEWER_FLAGS` qui contient les options de notre visionneur Nous avons aussi une nouvelle cible `view`, comme elle ne réalise aucune création de fichier nous ladéclarons comme cible `.PHONY`. La seule dépendance ce cette cible est `default`. Ainsi lors de l'appel de `view`, **notre document sera recompilé si nécessaire**. [l_zathura]:https://pwmt.org/projects/zathura/ ## En conclusion Partant d'un `Makefile` standard, nous l'avons amélioré au fur et à mesure en utilisant les fonctionnalités proposé par *make*. Bien entendu cet exemple est valable pour les documents simple, il sera alors nécessaire de l'adapter pour prendre en compte les **compilations multi-passes** pour la gestion des bibiographies par exemple. De mon côté j'ai profité de l'écriture de cet article pour améliorer mon processus de compilation de mes documents LaTeX. Au final, chaque dépôt de code contenant des documents prend la forme suivante : ``` ├── images │   ├── bitmap │ │ └── ... │   ├── generated │   └── svg │ └── ... ├── Makefile ├── build ├── README.md └── document.tex ``` *[PDF]: Portable Document Format *[SVG]: Scalable Vector Graphics