Title: Utiliser make pour compiler ses documents LaTeX
Category: linux
Tags: make, Makefile, LaTeX
Date: 2023-09-05 8:30
cover: assets/backgrounds/machine_zorba.jpg
status: published

Dans mon [précédent article]({filename}../fonctionnement_makefile/index.md),
nous avons parlé du fonctionnement de *make* et des `Makefile`. Comme promis,
voici le premier exemple d'utilisation : la construction de documents *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.tar.xz). Les corrections sont disponibles dans
le répertoire `Makefile/`.

Faites attention aux copiés/collés : les tabulations sont transformées en
espaces 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 compilateur et ses paramètres dans des variables. Nous avons
donc `$(LC)` pour *LaTeX Compiler* -- j'utilise LuaLatex -- et `$(LCFLAGS)`
pour les paramètres.

La cible `default` sera lancée automatiquement si aucune autre est spécifiée
dans la ligne de commande. Ainsi la compilation se lance avec un simple `make`.

## 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.) comme
ceci:

```shell
$ ls -l
-rw-r--r-- 1  user group    186 Aug 31 13:14 document.aux
-rw-r--r-- 1  user group  19650 Aug 31 13:14 document.log
-rw-r--r-- 1  user group  41023 Aug 31 13:14 document.pdf
-rw-r--r-- 1  user group   1203 Aug 29 23:27 document.tex
-rw-r--r-- 1  user group    191 Aug 29 23:27 Makefile
```

Notre répertoire 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
sous-répertoire.

```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 variable `OUTPUT` qui contient le répertoire dans lequel
placer tous les fichiers générés par `lualatex`. L'option qui va bien est
ajoutée à `LCFLAGS`. `OUTPUT` est aussi utilisée pour la cible `clean`, notre
nettoyage n'en est que plus simple!

Tous ces fichiers sont maintenant dans le répertoire `build/`:

```shell
$ tree
.
├── build
│   ├── document.aux
│   ├── document.log
│   └── document.pdf
├── document.tex
└── Makefile

2 directories, 5 files
```

## Mais où est document?

Tous nos documents ne 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 une imbrication de 3 fonctions :

 1. `wildcard <motif>` pour récupérer une liste de fichiers dans le répertoire
    courant en fonction d'un motif;
 2. `patsubst <motif>,<remplacement>,<chaine>`  pour substituer une partie de la
    chaîne dans un chemin, `%` fait ici office de caractère joker;
 3. `addprefix <prefix>, <chaine>` pour ajouter un préfixe à chacun des éléments
    d'une chaîne de caractères. N'oubliez pas, pour *make*, les éléments d'une
    chaîne de caractères sont délimités par des espaces.

Elle permet d'obtenir le nom d'une cible en fonction des fichiers `tex` contenus
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 appelleront la cible `$(OUTPUT)/%.pdf: %.tex` qui se charge de
contruite un fichier PDF en fonction de sa source LaTeX.

Cette solution nous permet d'appeler notre source LaTeX comme bon nous semble.
Mieux encore nous pouvons **avoir plusieurs fichiers** à la racine de notre
répertoire, ils seront tous compilés indépendamment. 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 matricielles** : des
photos JPEG ou des captures d'écran 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 grâce à 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 permettant la
construction 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 nous ajoutons une image sans l'utiliser dans notre document et
nous relançons la compilation alors notre document sera tout de même
recompilé sans que se soit nécessaire. *make* ne fait pas d'analyse sytaxique,
il n'est pas **capable d'analyser le fichier source pour determiner s'il doit 
être compilé ou non**.

Ensuite prenons l'exemple d'un dossier contenant trois fichiers LaTeX *A*, *B*,
et *C*. Si une image utilisée **seulement** dans le document *B* est modifiée,
*A* et *C* **seront tout de même recompilés**.

Dans l'exemple de code fourni (dans le répertoire `4_images`), vous trouverex un
script (`change_image.sh`) qui se charge de changer l'image du document
`presentation.tex`. Vous pouvez tester la compilation avant et après la
modification du `Makefile` et observer les actions effectuées.

Ce scrits se lance sans paramètres :

```shell
$ ./change_image.sh
Flip images ...
Done!
```

## Les images SVG

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 et 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 simple : nous utilisons *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 `Inkscape`;
 * `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*.

Et deux deux macros :

 * `SVG` liste les fichiers SVG via la fonction `wildcard`;
 * `SVG_EXPORTED` transforme la liste contenu dans`SVG` en liste de fichiers
   PDF. Deux fonctions imbriquées sont nécessaires :`subst` qui permet de
   remplacer un motif dans des chaînes 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`.

[^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, ajoutons maintenant 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 utile d'afficher**. Cet affichage nous permettra par exemple de
vérifier les images matricielles prises en compte ou encore les fichiers SVG qui
seront exportés.

```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 les commandes relatives aux 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.

Voici le résultat de cette cible:

```shell
$ make info
document.............'build/presentation.pdf'
bitmap images........'images/bitmap/ferret.jpg'
SVG images...........'images/svg/souris.svg'
exported SVG images..'images/generated/souris.pdf'
```

### Lancer l'application de visualisation des PDF

Lancer la compilation d'un document et l'afficher ensuite dans notre visionneur
de documents permet souvent de gagner du temps. Partons du principe que nous
utilisons [Zathura][l_zathura] comme visionneur de documents.

```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
 * `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 fichiers nous la déclarons comme cible `.PHONY`. La seule dépendance
de 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ées par *make*. Bien entendu cet exemple est
valable pour les documents simples, il sera alors nécessaire de l'adapter pour
prendre en compte les **compilations multi-passes** pour la gestion des
bibliographies par exemple.

De mon côté, j'utilise énormément LaTeX pour les courriers officiels, les rendu
des différents TD pour l'Université, mes présentations. J'ai profité de
l'écriture de cet article pour améliorer mon processus de compilation de tous
mes documents. 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
*[JPEG]:Join Photographic Experts Group
*[PNG]: Portable Network Graphic
