Un projet LaTeX multi-fichiers peut se répartir sur une douzaine de fichiers .tex sans que le compilateur sourcille : pdflatex main.tex les lit tous et produit un seul PDF. C’est l’éditeur qui perd le fil. Appuyer sur la touche de compilation avec le chapitre trois au premier plan renvoie ! LaTeX Error: Missing \begin{document}., parce que l’éditeur a docilement composé le fichier sous vos yeux. Le remède tient en une ligne en tête de chaque chapitre, % !TEX root = ../main.tex, et le plus curieux est que LaTeX ne lit jamais cette ligne. C’est un commentaire, adressé à l’éditeur et non au compilateur. Cette page traite de cette seconde couche d’un projet découpé : quels éditeurs lisent le commentaire magique et ce que les autres emploient à la place, comment SyncTeX retrouve le bon fichier de chapitre, où atterrissent réellement les fichiers de compilation, et ce qui casse lorsque le fichier ouvert n’est pas le fichier principal. Les commandes qui réalisent le découpage — \input, \include, \includeonly — forment une histoire à part, accessible au bas de la page.
% !TEX root : compiler le fichier principal avec un chapitre ouvert
Placer % !TEX root = ../main.tex en tête de chaque fichier qui n’est pas le fichier principal suffit : la touche de compilation fait alors ce qu’il faut, quel que soit le fichier au premier plan. Deux détails tirés de la documentation de TeXShop méritent d’être retenus, car l’un et l’autre piègent régulièrement. D’abord, la ligne doit figurer dans les vingt premières lignes du fichier ; enfouie sous un long en-tête de licence, elle passe inaperçue. Ensuite, le chemin est résolu relativement au fichier qui contient la ligne, et non à la racine du projet : un chapitre rangé dans chapters/ réclame ../main.tex et non main.tex. Un chemin absolu fonctionne aussi, au prix d’un projet devenu indéplaçable. Le fichier principal, lui, n’a besoin d’aucune ligne : il est déjà la racine.
thesis/
main.tex <- the root; needs no magic comment
chapters/
03-results.tex <- carries the line below% chapters/03-results.tex -- the first lines of the file
% !TEX root = ../main.tex % relative to THIS file, not to thesis/
% !TEX TS-program = xelatex % pin the engine for the whole project
% !TEX encoding = UTF-8 Unicode
\chapter{Results}Cette ligne a un prédécesseur, et la raison pour laquelle le successeur l’a emporté est instructive. TeXShop proposait autrefois une commande de menu nommée « Set Project Root… », qui consignait la réponse dans un fichier annexe placé à côté du chapitre : two.tex se voyait flanqué d’un two.texshop. Jetez ce fichier invisible et TeXShop se remettait aussitôt à composer le chapitre. La documentation de TeXShop décrit aujourd’hui cette commande comme retirée des menus parce que la méthode % !TEX root est plus robuste, et toute la raison tient dans ce mot. Une ligne à l’intérieur du fichier voyage avec le fichier. Elle survit à une copie, au renommage du dossier parent, à un clone Git et à un coauteur qui n’a jamais ouvert votre éditeur. Une configuration garée à côté d’un fichier finit toujours par perdre le fichier.
Pourquoi LaTeX lui-même ne lit jamais % !TEX root
Parce que % ouvre un commentaire, et que les commentaires sont écartés par l’analyseur de TeX avant toute autre chose. pdflatex, xelatex et lualatex ne voient absolument rien sur cette ligne. C’est un message d’un programme (l’éditeur) à un autre programme (la commande de compilation de l’éditeur), qui transite simplement par le fichier source. Deux conséquences pratiques en découlent. D’abord, rien ne vous avertira jamais que la ligne est fausse. Pointez-la vers un fichier inexistant et l’éditeur se rabat en silence sur sa propre supposition, généralement le fichier ouvert, et vous revoilà devant ! LaTeX Error: Missing \begin{document}. Ensuite, une compilation lancée depuis un terminal ou en CI — latexmk main.tex, un Makefile, une étape GitHub Actions — nomme le fichier principal en ligne de commande et ignore complètement le commentaire magique. Cette ligne est un confort d’édition interactive, non un élément de la définition du projet.
Il existe exactement un commentaire que TeX lit bel et bien, et il vaut mieux le connaître pour ne jamais confondre les deux. Si la toute première ligne du fichier d’entrée principal commence par %&, le moteur l’analyse lui-même pour choisir un format — %&pdflatex, %&latex —, comportement que la page de manuel de tex décrit comme piloté par l’option -parse-first-line et la variable de configuration parse_first_line. Celui-là descend du mécanisme de chargement de formats de TeX et réside dans le moteur. Tout ce qui s’écrit % !TEX ... réside dans l’éditeur. La ressemblance visuelle est une coïncidence : l’un et l’autre voulaient cacher des instructions là où LaTeX ne trébucherait pas dessus.
% main.tex -- first line only: the ENGINE parses this one
%&pdflatex
% chapters/03-results.tex -- the engine never sees this one
% !TEX root = ../main.texQuels éditeurs lisent % !TEX root, et que font les autres
TeXShop, TeXworks, TeXstudio et VS Code doté de l’extension LaTeX Workshop lisent tous cette ligne ; Emacs avec AUCTeX emploie une variable locale de fichier qui lui est propre, et Overleaf tire la réponse d’un réglage de projet plutôt que de la source. LaTeX Workshop mérite qu’on s’y attarde, car il documente toute sa procédure de décision : il regarde d’abord le commentaire magique dans l’éditeur actif, puis si le fichier actif contient lui-même \documentclass ou \begin{document}, puis parcourt les fichiers .tex à la racine de l’espace de travail pour en trouver un qui inclut le fichier actif, reconnaît ensuite le motif subfiles \documentclass[main.tex]{subfiles}, et se rabat enfin sur la liste de fichiers .fls laissée par la dernière compilation. Le commentaire magique l’emporte parce qu’il est consulté en premier — et si vous souhaitez qu’il n’en aille pas ainsi, le réglage se nomme latex-workshop.latex.build.enableMagicComments.
| Éditeur | Ce qu’il lit | Remarque |
|---|---|---|
TeXShop | % !TEX root | origine de la directive ; voisines % !TEX TS-program, encoding, spellcheck |
TeXworks | % !TEX root | reprend le même schéma de commentaires magiques |
TeXstudio | % !TeX root | détecte d’abord la racine automatiquement ; la ligne prime |
LaTeX Workshop | % !TEX root | pour VS Code ; première des cinq étapes, désactivable par latex-workshop.latex.build.enableMagicComments |
AUCTeX | TeX-master | pour Emacs ; variable locale de fichier, par convention à la fin du fichier |
Overleaf | un réglage de projet | choisi dans le menu du projet comme document principal ; rien dans la source |
Emacs est l’exception intéressante. AUCTeX pose la même question, mais range la réponse dans une variable locale de fichier, par convention dans un bloc à la fin du fichier. Comme chaque éditeur ne lit que sa propre convention, porter les deux ne coûte rien : le bloc AUCTeX n’est qu’un commentaire ordinaire pour tout autre éditeur, et % !TEX root n’est qu’un commentaire ordinaire pour Emacs. Dans les dépôts partagés, les fichiers de chapitre portent souvent les deux, ce qui est juste et coûte deux lignes. Overleaf, lui, reste tout à fait en dehors du débat : le document principal est une propriété du projet, réglée depuis le menu, si bien que rien dans la source ne peut se désynchroniser — et rien non plus n’accompagne le fichier lorsque l’on télécharge le projet pour l’ouvrir en local.
% chapters/03-results.tex -- both conventions in one file
% !TEX root = ../main.tex
\chapter{Results}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "../main"
%%% End:SyncTeX à travers les fichiers : pourquoi cliquer dans le PDF ouvre le bon chapitre
Parce que SyncTeX consigne, pour chaque boîte de chaque page, de quel fichier d’entrée et de quelle ligne elle provient. Un double-clic sur un paragraphe du chapitre trois dans le PDF ouvre chapters/03-results.tex, et non main.tex. On l’active avec -synctex=1, et l’on obtient un unique main.synctex.gz à la racine du projet, nommé d’après le fichier racine. Il n’existe pas de fichier synctex par chapitre : un index unique couvre tout le projet, et c’est justement pour cela qu’il peut désigner n’importe lequel de ses fichiers.
$ pdflatex -synctex=1 main.tex
$ synctex edit -o "2:100:200:main.pdf"
SyncTeX result begin
Output:main.pdf
Input:./chapters/ch2.tex
Line:2
SyncTeX result endEssayer une fois le client en ligne de commande rend le mécanisme concret. synctex edit prend une page et un point dans le PDF et renvoie un nom de fichier et un numéro de ligne ; synctex view fait le trajet inverse, d’une ligne source vers un emplacement sur la page. La Synchronize TeXnology qui l’anime revient, selon les termes de sa propre page de manuel, pour l’essentiel à Jérôme Laurens, et elle est aujourd’hui maintenue au sein de TeX Live. La documentation de TeXShop établit explicitement le lien avec la section précédente : c’est la ligne % !TEX root qui permet à un clic de recherche inverse d’ouvrir et d’activer la bonne fenêtre de chapitre au lieu de vous déposer dans le fichier principal. C’est précisément pour cela que les deux fonctions se configurent d’ordinaire ensemble.
Le piège survient dès qu’un chapitre est compilé seul. Une exécution du moteur dépose ses sous-produits dans le répertoire de travail d’où la compilation a été lancée, et non à côté du fichier d’entrée. Lancez pdflatex -synctex=1 chapters/03-results.tex depuis la racine du projet et 03-results.synctex.gz apparaît à la racine, juste à côté de main.synctex.gz. Deux index décrivent alors les mêmes lignes source, et l’un d’eux pointe vers un PDF d’un seul chapitre qui commence à la page 1. Selon celui que le visualiseur lit, votre clic n’aboutit pas au même endroit et la pagination cesse de concorder. Avant de revenir à la compilation du livre entier, supprimez le PDF et le fichier synctex laissés par la compilation du chapitre.
Où atterrissent les fichiers de compilation, et quoi mettre dans .gitignore
\include écrit un .aux par chapitre, et il l’écrit à côté du fichier de chapitre. Compilez un projet contenant chapters/01-intro.tex et vous trouverez chapters/01-intro.aux juste à côté. Tout le reste demeure à la racine, auprès du fichier principal : main.aux, main.log, main.toc, main.out, main.synctex.gz, et avec latexmk également main.fls et main.fdb_latexmk. Les fichiers engendrés ne sont donc pas rassemblés en un seul endroit : ils sont semés en fine couche sur tout l’arbre des sources.
thesis/
main.tex main.pdf
main.aux main.log main.toc main.out
main.synctex.gz main.fls main.fdb_latexmk
chapters/
01-intro.tex 01-intro.aux <- one .aux per \include, here
02-method.tex 02-method.auxPour Git, c’est moins gênant qu’il n’y paraît, car un motif de .gitignore sans barre oblique s’applique à toute profondeur : une simple ligne *.aux couvre déjà chapters/01-intro.aux. Ce qui ne le couvre pas, c’est un /*.aux ancré à la racine ; pas davantage l’habitude de nettoyer par un rm *.aux à la racine du projet. Et, plus surprenant, latexmk non plus : testé sous TeX Live 2024, latexmk -c et même latexmk -C suppriment les fichiers intermédiaires de la racine et laissent chapters/*.aux en place. Aussi, quand un .aux périmé est en cause — ce genre d’erreur qui désigne un chapitre auquel vous n’avez pas touché —, faites le ménage explicitement, par exemple avec find . -name "*.aux" -delete.
Il est un endroit où le découpage casse réellement un outil : -output-directory. Demandez une compilation hors de l’arborescence avec pdflatex -output-directory=build main.tex sur un projet qui utilise \include et l’exécution meurt. TeX tente d’ouvrir build/chapters/01-intro.aux, le sous-dossier n’existe pas, et vous obtenez ! I can't write on file suivi d’une erreur fatale et d’aucun PDF. TeX ne crée pas de répertoires. Deux remèdes : creuser vous-même au préalable les sous-dossiers en miroir, ou confier le travail à latexmk -outdir=build, qui les crée pour vous. C’est pourquoi les projets multi-fichiers compilant hors arborescence sont presque toujours pilotés par latexmk plutôt que directement par le moteur.
# fails: TeX will not create build/chapters/ by itself
pdflatex -output-directory=build main.tex
# ! I can't write on file ... chapters/01-intro.aux
# works: mirror the subdirectories first
mkdir -p build/chapters && pdflatex -output-directory=build main.tex
# works: latexmk creates them for you
latexmk -pdf -outdir=build main.texCe même fichier .fls est ce qui fait fonctionner la recompilation à l’enregistrement dans un projet découpé. Lancez la compilation avec -recorder — que latexmk ajoute pour vous — et le moteur consigne chaque fichier ouvert, si bien que main.fls porte une ligne INPUT chapters/01-intro.tex par chapitre. latexmk conserve la liste de dépendances qui en résulte dans main.fdb_latexmk et la surveille intégralement ; c’est pourquoi enregistrer le chapitre trois reconstruit le livre sans que vous ayez jamais déclaré que le chapitre trois en fait partie. La structure du découpage n’a pas à être énoncée deux fois : la suite des lignes \include est déjà la déclaration de dépendances.
Ce qui casse quand le fichier ouvert n’est pas le fichier principal
Trois symptômes, qui ne se ressemblent en rien. D’abord, un fichier de chapitre ordinaire compilé seul s’arrête aussitôt : ! Undefined control sequence. au premier \chapter, puis ! LaTeX Error: Missing \begin{document}., puis ! Emergency stop. et aucun PDF — issue inévitable pour un fichier dépourvu de \documentclass. Ensuite, un chapitre subfiles compilé seul est pire, car il réussit : on obtient un PDF d’un chapitre, plausible, qui commence à la page 1 et dont les renvois vers les autres chapitres s’impriment en ??. Enfin, une compilation lancée depuis le mauvais répertoire de travail échoue plutôt sur les images, car tout chemin relatif du projet se résout depuis l’endroit d’où la compilation a été lancée, et non depuis l’emplacement du fichier.
- La touche de compilation compose le mauvais fichier → mettre
% !TEX rootdans chaque fichier autre que le principal, dans les vingt premières lignes, avec un chemin relatif à ce fichier. ! LaTeX Error: Missing \begin{document}.→ un chapitre est compilé directement ; ce fichier n’a pas de préambule et n’a pas vocation à en avoir.- Les images disparaissent, ou la compilation s’arrête sur un fichier introuvable → la compilation ne part pas de la racine du projet ; les chemins relatifs se résolvent depuis le répertoire de travail.
- Un clic dans le PDF ouvre le fichier principal au lieu du chapitre → il manquait
-synctex=1à cette compilation, ou le visualiseur lit un.synctex.gzpérimé. - Des
.loget.pdfisolés apparaissent à la racine après un essai raté → une exécution du moteur écrit ses sous-produits dans le répertoire de travail, pas à côté du fichier d’entrée.
Au fond, deux habitudes suffisent à faire taire cette couche. La première : pas d’espaces dans les noms de fichiers ni de dossiers. Chaque outil évoqué ici finit par confier le chemin à un shell ou à un commentaire magique introduit par %, et l’espace est le refuge des bogues de guillemets. La seconde : toujours lancer la compilation depuis la racine du projet, à la main, depuis un Makefile ou en laissant faire l’éditeur. Le répertoire de travail est l’unique point de référence que partagent \includegraphics, \include et -output-directory ; s’il dérape, les trois dérapent ensemble. Ces deux points acquis, la couche multi-fichiers devient invisible — et c’est le seul état dans lequel elle fait son travail.