Lire les erreurs et déboguer

Les messages d'erreur de LaTeX se lisent mal parce que ce ne sont pas des traces de pile. Les deux lignes empilées sous ! Undefined control sequence n'expliquent pas quelle commande est fautive : elles photographient la tête de lecture de TeX à l'instant où elle s'est arrêtée — la ligne du haut est ce qui a déjà été lu, celle du bas ce qui ne l'était pas encore, et la coupure entre les deux est le lieu de l'accident. Une fois ce point acquis, le même mécanisme explique pourquoi le numéro de ligne l.NN ment parfois, s'il faut taper h ou x à l'invite ?, et pourquoi le fichier .log contient davantage que ce que le terminal a jamais affiché. Cette page traite de l'anatomie d'une erreur TeX, de -file-line-error, des quatre modes -interaction, de la lecture du journal et de la recherche par dichotomie du coupable.

Anatomie d'une erreur TeX : la ligne ! et les deux lignes empilées

La ligne ! dit ce qui s'est produit ; les deux lignes empilées à partir de l.NN disent où TeX s'est arrêté — et le coupable se trouve presque toujours à l'extrémité droite de la ligne supérieure. TeX coupe la ligne d'entrée en une partie lue et une partie non lue, empile les deux moitiés et signale la coupure par une indentation. Dans l'exemple ci-dessous, la moitié supérieure se termine par \textbnf, c'est-à-dire la commande qui a explosé à l'instant même de sa lecture ; {bold} text. n'avait pas encore été touché et reste donc en bas. Cette coupure est bien plus fiable que le numéro de ligne : le numéro indique où TeX a remarqué, la coupure indique où TeX se trouvait.

terminal
! Undefined control sequence.
l.3 This is \textbnf
                    {bold} text.
? 

Des lignes supplémentaires apparaissent parfois au-dessus de l.NN : c'est le contexte de l'erreur. Une ligne contenant ->, par exemple \mynorm #1->\lVert, signifie que la panne s'est produite dans l'expansion de cette macro. <inserted text> est un token que TeX a fourni lui-même pour se rattraper, <to be read again> un token consommé puis remis en file, et <read *> signale une attente de saisie au terminal. Quand une ligne dépasse la largeur du terminal, son début est élidé par ... : un affichage tel que l.9 ...: $\frac{1}{2}$ and a stray \undefinedmacro commence donc en réalité plus à gauche.

Ligne de contexteSignification
l.NNla ligne d'entrée en cours de lecture ; la coupure marque l'arrêt
\mac #1->survenu dans l'expansion de \mac ; la définition est ailleurs
<inserted text>un token fourni par TeX pour se rattraper, souvent un $
<recently read>le token qui vient d'être consommé, généralement la cause elle-même
<to be read again>un token consommé puis remis en file ; il sera relu ensuite
<argument>survenu dans un argument : examiner l'argument, pas l'appel
<read *>en attente d'une saisie au terminal ; un mode non interactif abandonne aussitôt

Pourquoi l.NN dépasse parfois d'exactement une ligne

Une erreur déclenchée par \usepackage est généralement signalée une ligne trop loin, à cause de l'argument de date optionnel que \usepackage autorise à la fin. Puisque \usepackage[opt]{pkg}[2021/02/14] est licite, TeX doit regarder au-delà de l'accolade fermante pour voir si un [ suit ; cette anticipation saute les espaces et les fins de ligne, si bien que la ligne suivante est déjà lue au moment où l'erreur se déclenche. Mesuré sur TeX Live 2024 : avec \usepackage[latin1]{inputenc} en ligne 3, le conflit d'options est signalé en l.4 ; ajouter un [2021/02/14] explicite en fin de cette même ligne ramène le rapport à l.3. Si une erreur liée à un package désigne une ligne vide ou \begin{document}, il faut donc regarder une ligne plus haut.

terminal
% \usepackage[latin1]{inputenc} sits on line 3 -- reported at line 4
./oc1.tex:4: LaTeX Error: Option clash for package inputenc.
l.4 \begin
          {document}

% same source, but the optional date argument is written out
% -- the look-ahead ends on line 3, and so does the report
./ocA.tex:3: LaTeX Error: Option clash for package inputenc.
l.3 \usepackage[latin1]{inputenc}[2021/02/14]

Le même écart entre « où TeX a remarqué » et « où se trouve la faute » s'ouvre avec une } non fermée, sauf qu'il peut alors s'étendre sur des dizaines de lignes, TeX ne renonçant qu'à la fin d'un paragraphe ou à \end{document}. Les cas particuliers — mode mathématique perdu, commandes non définies, accolades manquantes — ont chacun leur page. Seule la règle générale compte ici : plus la ligne signalée paraît innocente, plus la faute est en amont.

-file-line-error : le format vers lequel l'éditeur sait sauter

Avec -file-line-error, le ! initial est remplacé par ./file.tex:3:, ce qui réunit le nom du fichier et le numéro de ligne sur une seule ligne : un éditeur ou un analyseur de journal d'intégration continue peut y sauter directement. Le format par défaut a un vrai manque : l.3 ne donne qu'un nombre, et le nom du fichier doit être déduit d'une parenthèse ouvrante comme (./chapters/intro.tex imprimée bien plus haut. Dans un document découpé en chapitres par \input, c'est cette déduction qui coûte du temps. -file-line-error la supprime, et les deux lignes empilées l.NN restent affichées : rien n'est perdu.

terminal
$ pdflatex main.tex
(./chapters/intro.tex
! Undefined control sequence.
l.2 Here is \nosuchcmd
                       in a chapter.

$ pdflatex -file-line-error main.tex
(./chapters/intro.tex
./chapters/intro.tex:2: Undefined control sequence.
l.2 Here is \nosuchcmd
                       in a chapter.

Dans bien des installations, ce format est déjà celui par défaut : latexmk l'active en interne, et des interfaces comme TeXworks ou LaTeX Workshop pour VS Code l'ajoutent d'elles-mêmes. Lors d'un appel manuel, il faut passer -file-line-error, ou -no-file-line-error pour le désactiver explicitement. Effet secondaire utile : lorsque l'erreur vient d'un package, le chemin affiché est celui de son propre fichier — une ligne /usr/local/texlive/…/foo.sty:120: signifie que c'est foo qui proteste, et non ce qui a été écrit à la main.

L'invite ? : h, i, x, q, r, s et Entrée

Neuf réponses sont possibles à une invite ?, et taper ? fait imprimer la liste par TeX lui-même. C'est le comportement du mode par défaut errorstopmode, dans lequel TeX demande littéralement ce qu'il doit faire. Trois réponses assurent l'essentiel du trafic : Entrée (ignorer cette erreur et poursuivre), h (afficher le paragraphe d'aide propre à TeX pour ce message) et x (abandonner aussitôt, sans produire de PDF). Si un long document risque de contenir d'autres erreurs, le plus rapide est de taper r ou s, de laisser la compilation aller à son terme, puis de lire le .log.

terminal
? ?
Type <return> to proceed, S to scroll future error messages,
R to run without stopping, Q to run quietly,
I to insert something, E to edit your file,
1 or ... or 9 to ignore the next 1 to 9 tokens of input,
H for help, X to quit.
?
RéponseCe que fait TeX
Returnoublier cette erreur et poursuivre ; la composition continue et un PDF est produit
himprimer le paragraphe d'aide de ce message ; le .log le contient déjà
iinsérer du texte à cet endroit — i\textbf corrige une faute pour cette exécution seulement
xabandonner immédiatement ; affiche No pages of output. et n'écrit aucun PDF
qaffiche OK, entering \batchmode puis termine la compilation en silence
raffiche OK, entering \nonstopmode... et va jusqu'au bout sans s'arrêter
saffiche OK, entering \scrollmode... ; ne s'arrête pas, mais lit encore le terminal
eouvrir à cette ligne l'éditeur désigné par la variable d'environnement TEXEDIT
1 … 9ignorer les 1 à 9 tokens suivants et continuer ; la ligne est réaffichée à la nouvelle coupure

Les quatre modes -interaction et quand employer chacun

pdflatex --help énumère quatre valeurs — batchmode, nonstopmode, scrollmode, errorstopmode — et la valeur par défaut est errorstopmode. Pour une compilation pilotée par un script, -interaction=nonstopmode ; pour un job d'intégration continue qui ne doit pas saturer le terminal, -interaction=batchmode. Deux axes seulement séparent les quatre : s'arrêter ou non, écrire sur le terminal ou non. La paire la plus mal comprise est scrollmode face à nonstopmode. Mesuré : un document appelant \typein lit bel et bien la réponse au terminal sous scrollmode, et meurt avec ! Emergency stop. sous nonstopmode. La ligne de partage ne passe pas par les erreurs mais par la saisie au terminal.

ModeS'arrête ? Écrit sur le terminal ?
errorstopmodepar défaut ; s'arrête à chaque erreur avec une invite ? — pour le travail manuel
scrollmodene s'arrête pas aux erreurs mais lit encore le terminal ; utile pour survoler une compilation entière
nonstopmodene lit jamais le terminal ; si une saisie est demandée, se termine par ! Emergency stop.
batchmodenonstopmode plus sortie terminal supprimée ; le .log est tout de même écrit en entier

Dire que batchmode n'imprime rien est presque exact. En mesurant le même document fautif sur TeX Live 2024, le terminal reçoit 1212 octets sous nonstopmode et 144 octets sous batchmode — ne subsistent que la bannière pdfTeX et entering extended mode, imprimées avant que le mode d'interaction ne prenne effet. Le .log, lui, pèse 4144 octets dans les deux cas, octet pour octet identique, et le PDF est produit dans les deux. batchmode ne jette donc pas d'information : il la garde simplement hors du terminal. D'où la recette habituelle en intégration continue : compiler en mode batch, décider du succès à partir du code de retour traité à la section suivante, et archiver le .log pour le détail. Ces quatre noms sont aussi des primitives TeX, si bien qu'écrire \nonstopmode en tête de fichier produit le même effet.

-halt-on-error et le code de retour

-halt-on-error abandonne la compilation à la première erreur. Vérifié sur TeX Live 2024 : juste après le premier ! Undefined control sequence, il imprime ! ==> Fatal error occurred, no output PDF file produced! et s'arrête, sans laisser de PDF. Le même document sous simple -interaction=nonstopmode signale les quatre erreurs et écrit malgré tout un PDF ; l'option est donc celle qu'il faut quand un document cassé ne doit pas ressembler à une compilation réussie. Le code de retour a aussi été mesuré : 1 dès qu'une erreur s'est produite, 0 sinon. Cela ne dépend pas du mode — nonstopmode et batchmode se comportent pareil — et les avertissements ne le modifient jamais. Une chaîne pdflatex && … dans un Makefile ou un job d'intégration continue ne s'interrompt donc que sur des erreurs.

shell
# interactive: stop at the first error and look at it
pdflatex -file-line-error -halt-on-error document.tex

# CI: quiet terminal, full log on disk, exit status decides
pdflatex -interaction=batchmode -halt-on-error -file-line-error document.tex
echo $?      # 1 if any error occurred, 0 if none

Lire le .log : il en contient plus que le terminal n'en a montré

Le .log conserve le paragraphe d'aide que le terminal n'a jamais imprimé : lorsqu'un message reste obscur, inutile de le reproduire pour taper h, il suffit d'ouvrir le journal. Mesure sur une compilation : 938 octets ont atteint le terminal, le .log en contenait 3199, et l'écart est fait pour l'essentiel de ce texte d'aide. L'effet est maximal pour un conflit d'options : le terminal n'affiche que ! LaTeX Error: Option clash for package inputenc., tandis que le journal détaille avec quelles options le package a d'abord été chargé et lesquelles viennent d'être demandées. Ces quatre lignes séparent la conjecture de la certitude.

log
% only the first line of this reaches the terminal
./oc1.tex:4: LaTeX Error: Option clash for package inputenc.
...
The package inputenc has already been loaded with options:
  [utf8]
There has now been an attempt to load it with options
  [latin1]
Adding the global options:
  utf8,latin1
to your \documentclass declaration may fix this.

Connaître la forme d'ensemble du journal paie également. La première ligne nomme le moteur, sa version, la date et l'heure de la compilation ; la suivante montre l'appel sous la forme **document.tex ; au-delà, tout n'est que parenthèses imbriquées — ( ouvre un fichier, ) le referme, si bien que l'imbrication répond à la question « quel fichier a fait entrer ce package ». [1], [2] marquent les pages sorties, et la fin donne le bilan mémoire après Here is how much of TeX's memory you used: puis Output written on document.pdf (1 page, 12817 bytes).. Pour éviter tout cela, on fait passer la compilation par texfot, livré avec TeX Live, qui réduit la sortie aux erreurs, aux avertissements et à la ligne de bilan finale.

terminal
$ texfot pdflatex -interaction=nonstopmode two.tex
texfot: invoking: pdflatex -interaction=nonstopmode two.tex
This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024)
! Undefined control sequence.
l.3 First \foo
! Missing $ inserted.
<inserted text>
l.4 Second \bar
Output written on two.pdf (1 page, 23091 bytes).

\listfiles et le bloc *File List* : compter ce qui a réellement été chargé

Un unique \listfiles placé n'importe où dans le préambule ajoute à la fin du .log un tableau *File List* qui nomme chaque fichier chargé avec sa date, sa version et une description d'une ligne. Compté sur TeX Live 2024 : un article nu charge 3 fichiers (article.cls, size10.clo, l3backend-pdftex.def). Une seule ligne hyperref porte le total à 33 — hyperref à lui seul en entraîne 30 de plus. Avec tikz, on arrive à 34. C'est le premier geste dès qu'un package jamais demandé se retrouve mêlé à un conflit. C'est aussi ce qu'il faut coller en posant une question ou en signalant une anomalie : le tableau rend évidente toute divergence entre deux installations.

log
% \listfiles goes anywhere in the preamble; this lands at the end of the .log
 *File List*
 article.cls    2023/05/17 v1.4n Standard LaTeX document class
  size10.clo    2023/05/17 v1.4n Standard LaTeX file (size option)
 amsmath.sty    2023/05/13 v2.17o AMS math features
hyperref.sty    2024-01-20 v7.01h Hypertext links for LaTeX
   iftex.sty    2022/02/03 v1.0f TeX engine tests
 ***********

Pour une vue plus fine, on ajoute -recorder. Chaque fichier ouvert pendant la compilation est inscrit dans un fichier .fls sous forme de ligne INPUT — un document dont le seul package est tikz en produit 140. Là où \listfiles répond « quels packages ont été chargés », .fls répond « quels fichiers ont été touchés », jusqu'aux .tfm de polices et aux fichiers de configuration. Le premier convient pour traquer un conflit de packages, le second pour traquer où kpathsea est réellement allé chercher.

\show, \showthe, \typeout : imprimer ce que TeX croit

\show\foo imprime la définition de \foo, et \showthe\textwidth la valeur d'une longueur ou d'un compteur. La sortie se retrouve dans le .log sous la forme > \LaTeX=macro: ou > 345.0pt. — le > initial est le repère, et 345,0 pt se trouve être la \textwidth par défaut d'article. Quand on ne sait plus comment une commande est actuellement définie, \show vaut mieux que deviner, et cela tranche le plus souvent la question de savoir si c'est la classe ou un package qui a redéfini. Pour émettre ses propres messages, il y a \typeout{…} et \message{…} ; à la mesure, \typeout place son texte sur une ligne à part tandis que \message l'accole à la ligne courante. Le premier se lit mieux pour un débogage façon printf, le second sert à marquer un endroit à côté d'un numéro de page.

latex
\show\LaTeX            % > \LaTeX=macro:  ... (definition follows)
\showthe\textwidth     % > 345.0pt.       (article default)
\typeout{reached the theorem}   % own line in log and terminal
\message{mark}                  % appended to the current line
\tracingall            % dump every step to the log -- extremely verbose

Le dernier recours est \tracingall, qui écrit dans le journal chaque pas de TeX — expansions de macros, changements de mode, essais de coupure de ligne. Sur un document de quelques pages, cela atteint des dizaines de mégaoctets ; il faut donc l'activer juste avant l'endroit fautif et revenir à \tracingnone juste après, ou l'associer au package trace, qui met la sortie en forme lisible. \tracingall répond à « dans quel ordre cela s'est-il produit », pas à « quelle macro est en cause » — et une fois l'ordre établi, un seul \show règle en général le reste.

Dichotomiser le document : remonter \end{document}

Quand le message ne suffit pas, couper le document en deux est le chemin le plus court : un \end{document} supplémentaire au milieu du corps, et tout ce qui suit est ignoré. Vérifié sur TeX Live 2024 — ce qui suit \end{document}, fût-ce une commande cassée, n'est jamais lu. Il n'est donc même pas nécessaire de supprimer l'original : il suffit de faire glisser la ligne ajoutée vers le haut ou vers le bas pour resserrer l'étau des deux côtés. Dix déplacements ramènent un document de mille lignes à une seule. Si le préambule est suspect, on commente la moitié des lignes \usepackage avec % ; si les chapitres sont séparés par \include, on emploie plutôt \includeonly{chapter3}.

document.tex
\begin{document}
\input{chapters/intro}
\input{chapters/method}

\end{document}   % <- added: bisect here, everything below is ignored

\input{chapters/results}
\input{chapters/discussion}
\end{document}

Une fois arrivé à la moitié, il faut continuer à tailler jusqu'au plus petit exemple qui échoue encore. Retirer les lignes \usepackage une à une, jeter le texte paragraphe par paragraphe, remplacer les figures par example-image (livré avec graphicx) et les longs passages par lipsum : il reste en général une douzaine de lignes. À cette taille, la cause saute normalement aux yeux ; et si ce n'est pas le cas, ces quelques lignes sont précisément ce qu'on colle dans une question. Tailler, c'est déjà diagnostiquer — l'art de bien demander, et où demander, relève de la page consacrée à la communauté.