Sur l'installation TeX Live 2024 qui a servi à rédiger cette page, kpsewhich -expand-path='$TEXINPUTS' affiche 8 798 répertoires, soit environ un demi-million de caractères de chemin. Et pourtant \usepackage{amsmath} se résout instantanément. La raison est simple : LaTeX ne va presque jamais regarder dans ces répertoires. Cette page démonte les deux moitiés de ce tour de force à l'aide de véritables sorties de commandes : la TDS (TeX Directory Structure), la carte de tous les fichiers texmf, et kpathsea, le moteur de recherche qui la parcourt. Quel arbre survit à une mise à niveau et lequel est jeté ? Et pourquoi un fichier déposé dans TEXMFHOME est-il le seul cas qui se passe de mktexlsr ?
La TDS : pourquoi un package est éparpillé dans neuf répertoires
La TDS classe les fichiers par type, non par package : un package ne se trouve donc jamais en un seul endroit. Recensez amsfonts, le package qui fournit amssymb, sur cette installation TeX Live 2024 : il occupe neuf répertoires sous texmf-dist. Les macros sont dans tex/latex/amsfonts/, les sources commentées .dtx dans source/latex/amsfonts/, les manuels dans doc/fonts/amsfonts/, et les polices elles-mêmes se répartissent encore par format entre fonts/tfm/, fonts/type1/, fonts/afm/, fonts/map/ et fonts/source/. La version plain TeX dispose de son propre tex/plain/amsfonts/.
$ find /usr/local/texlive/2024/texmf-dist -maxdepth 4 -type d -path '*amsfonts*' | sort
/usr/local/texlive/2024/texmf-dist/doc/fonts/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/afm/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/map/dvips/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/source/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/tfm/public/amsfonts
/usr/local/texlive/2024/texmf-dist/fonts/type1/public/amsfonts
/usr/local/texlive/2024/texmf-dist/source/latex/amsfonts
/usr/local/texlive/2024/texmf-dist/tex/latex/amsfonts
/usr/local/texlive/2024/texmf-dist/tex/plain/amsfontsPourquoi une telle organisation ? La réponse est la portabilité. TeX tourne sous macOS, Unix et Windows, et le CTAN (Comprehensive TeX Archive Network) rassemble des milliers de packages. Si chaque distributeur disposait les fichiers à sa façon, ceux qui publient des packages comme les outils qui les cherchent trébucheraient à chaque fois. La TDS, établie par le TeX Users Group (TUG) dans les années 1990, a rendu un jeu de règles universel : les macros sous tex/, les polices sous fonts/<type>/<fournisseur>/<fonte>/. L'emplacement de n'importe quel fichier se déduit donc des seules règles, sur tout système et toute distribution. Sous tex/ s'ajoute un niveau, tex/<format>/<package>/, où <format> vaut latex, plain, generic, etc.
| Répertoire | Contenu | Taille mesurée (texmf-dist, TeX Live 2024) |
|---|---|---|
doc/ | Manuels des packages — ce qu'ouvre texdoc | 3,7 Go, dont 10 099 PDF |
fonts/ | Toutes les polices, par format : tfm, vf, type1, opentype, enc, map | 2,9 Go |
tex/ | Macros, classes, styles (.tex .sty .cls), p. ex. tex/latex/... | 594 Mo |
source/ | Sources commentées .dtx et scripts d'extraction .ins — l'implémentation, lisible | 426 Mo |
scripts/ | Scripts exécutables indépendants du système (le corps de mktexlsr, latexmk, …) | 133 Mo |
bibtex/ | Bases bibliographiques bib/ et styles bst/ | 26 Mo |
web2c/ | Configuration des moteurs ; siège de texmf.cnf et de la liste fmtutil.cnf | 248 Ko |
Si une ligne de ce tableau surprend, c'est celle-ci : la documentation pèse plus lourd que le logiciel. Sur les 7,9 Go de texmf-dist, doc/ en occupe 3,7 tandis que les macros de tex/ n'en font que 594 Mo. C'est pour cela que l'installateur de TeX Live propose de ne pas installer la documentation, et que les images Docker se déclinent avec et sans -doc. Connaître cette disposition sert aussi : quand un package se comporte de façon inexplicable, on va lire directement source/latex/<package>/*.dtx, et le manuel qu'ouvre texdoc est un vrai fichier posé dans doc/.
Quel arbre survit à une mise à niveau
Seul texmf-dist est remplacé en bloc. TeX Live crée un répertoire par année — /usr/local/texlive/2024 — et y place la distribution proprement dite, texmf-dist. L'année suivante, un 2025 apparaît à côté et texmf-dist est échangé contre une copie neuve. Ajouter ses propres fichiers dans la distribution est donc suicidaire ; en contrepartie, tout ce qui se trouve hors du répertoire de l'année reste intact. Que TEXMFLOCAL soit à /usr/local/texlive/texmf-local, en dehors de 2024, n'a rien d'un hasard : c'est précisément la conception retenue. TEXMFHOME est encore plus à l'écart, dans le répertoire personnel.
# Never guess these paths - ask. Values below: TeX Live 2024 on macOS.
$ kpsewhich -var-value=TEXMFROOT
/usr/local/texlive/2024
$ kpsewhich -var-value=TEXMFLOCAL # note: OUTSIDE the year directory
/usr/local/texlive/texmf-local
$ kpsewhich -var-value=TEXMFHOME # ~/texmf on Linux, ~/Library/texmf on macOS
/Users/you/Library/texmf
$ kpsewhich -var-value=TEXMFVAR
/Users/you/Library/texlive/2024/texmf-var| Variable | Rôle | Ce qu'une mise à niveau lui fait |
|---|---|---|
TEXMFDIST | La distribution elle-même ; des milliers de packages. Ne jamais modifier à la main | Remplacée en bloc. Tout ajout disparaît |
TEXMFLOCAL | Ajouts valables pour toute la machine, partagés par tous | Conservé, car situé hors du répertoire de l'année |
TEXMFHOME | L'arbre personnel ; vos classes et le style d'une revue vont ici | Conservé ; il est dans le répertoire personnel, intouché |
TEXMFVAR | Cache généré automatiquement : formats, font maps, caches LuaTeX | Reconstruit chaque année ; le supprimer force sa régénération |
TEXMFCONFIG | Dépôt de configuration par utilisateur, écrit par updmap et fmtutil | Conservé, mais rangé sous un répertoire annuel |
TEXMFSYSVAR | Équivalent système de VAR / CONFIG, écrit par les commandes en -sys | TEXMFSYSCONFIG se comporte pareil ; les deux sont dans le répertoire de l'année |
TEXMFROOT | Racine de toute l'installation, /usr/local/texlive/2024 | Une nouvelle année, c'est un tout autre répertoire |
Quand un fichier du même nom existe dans plusieurs arbres, lequel l'emporte ? Cela se règle par une seule variable, TEXMF, dont la valeur n'est rien d'autre que la priorité de recherche écrite dans l'ordre. Sur ce TeX Live 2024 elle se lit comme ci-dessous : le plus à gauche gagne, donc d'abord votre configuration et vos caches, puis l'arbre personnel TEXMFHOME, ensuite le TEXMFLOCAL de la machine, et la distribution TEXMFDIST en dernier. Autrement dit, déposer mystyle.sty dans TEXMFHOME masque le fichier homonyme de la distribution — non par écrasement, mais par l'ordre naturel personnel → site → distribution. Les marques !! devant certaines entrées font l'objet de la section suivante.
$ kpsewhich -var-value=TEXMF
{{}/Users/you/Library/texlive/2024/texmf-config,
/Users/you/Library/texlive/2024/texmf-var,
/Users/you/Library/texmf,
!!/usr/local/texlive/texmf-local,
!!/usr/local/texlive/2024/texmf-config,
!!/usr/local/texlive/2024/texmf-var,
!!/usr/local/texlive/2024/texmf-dist}
# Note which entries carry "!!" - and which do not.Peut-on supprimer texmf-var ?
Tout ce qu'il contient est engendré : en principe, rien ne se perd en le supprimant. Avant d'entonner « supprime-le et ça se répare », il vaut pourtant la peine de regarder ce qui s'y trouve. Sur ce TeX Live 2024, le texmf-var système pèse 259 Mo, dont 233 Mo pour web2c/ qui abrite 53 fichiers .fmt — pdflatex.fmt à lui seul fait 7,8 Mo. Un fichier de format est une image mémoire en conserve, qui évite de relire latex.ltx et les classes à chaque exécution. Le texmf-var utilisateur est plus gros encore, 293 Mo, dont 257 Mo de luatex-cache/, résultat de l'analyse des polices par LuaTeX. Le psfonts.map écrit par updmap s'y trouve aussi.
$ du -sh /usr/local/texlive/2024/texmf-var/*
4.0K ls-R
36K tex
26M fonts
233M web2c # 53 .fmt files; pdflatex.fmt alone is 7.8 MB
$ du -sh "$(kpsewhich -var-value=TEXMFVAR)"/*
32K fonts
2.1M texdoc
12M web2c
22M luatexja
257M luatex-cache # LuaTeX font analysis, rebuilt on demandIl en découle une règle pratique. Quand un fichier de format périmé fait dérailler quelque chose, on le reconstruit avec fmtutil-sys --all plutôt que de supprimer le répertoire. Effacer l'arbre entier vise des cas plus étroits : un cache de polices LuaTeX corrompu qui fait sortir à luaotfload des erreurs incompréhensibles, par exemple. Le prix n'est qu'une première compilation lente ensuite ; mais ne confondez pas texmf-var et texmf-config : emporter le second, c'est perdre aussi les réglages d'updmap. Les commandes de régénération elles-mêmes relèvent de la page sur la gestion des packages et des polices.
Comment kpathsea trouve réellement un fichier
La recherche est assurée par une bibliothèque partagée nommée kpathsea (kpath search). Ni pdftex, ni xetex, ni luatex, ni dvipdfmx, ni bibtex ne cherchent par eux-mêmes : tous demandent à kpathsea « où est amsmath.sty ? ». Ce que kpathsea reçoit est une seule chaîne contenant des règles. Trois symboles méritent d'être retenus : $VAR développe une variable, un // final signifie « tout ce qui est en dessous, récursivement », et un !! initial signifie « ne pas parcourir le disque — consulter uniquement la base de noms de fichiers décrite à la section suivante ». Affichez TEXINPUTS, le chemin de recherche des sources LaTeX, et les trois apparaissent d'un coup.
$ kpsewhich -progname=pdflatex -var-value=TEXINPUTS
.:{...the TEXMF list...}/tex/{latex,generic,}//
# The same query, run as a different program:
$ kpsewhich -progname=pdflatex-dev -var-value=TEXINPUTS
.:{...the TEXMF list...}/tex/{latex-dev,latex,generic,}//
# How many real directories does that string stand for?
$ kpsewhich -progname=pdflatex -expand-path='$TEXINPUTS' | tr : '\n' | wc -l
8798Voici comment le lire : d'abord . (le répertoire du manuscrit), sinon parcourir récursivement la branche tex/ de chaque arbre texmf dans l'ordre latex → generic → tout le reste. Qu'un fichier voisin du manuscrit l'emporte correspond exactement à l'intuition — et c'est aussi là que se niche le piège de cette section. L'autre point remarquable est que le premier élément de {latex,generic,} change avec le nom du programme en cours. Appelé sous le nom pdflatex-dev, il devient {latex-dev,latex,generic,} : l'arbre de développement est consulté en premier. kpathsea répond différemment selon qui demande. Et les 8 798 rapportés par -expand-path sont aussi un avertissement : sans l'index ls-R, c'est le nombre de répertoires qu'il faudrait ouvrir à chaque recherche.
ls-R et TEXMFDBS : pourquoi seul TEXMFHOME se passe de mktexlsr
La réponse tient en une ligne : TEXMFDBS, la liste des arbres pourvus d'un index, ne contient pas TEXMFHOME. Ouvrir à chaque fois les 8 798 répertoires de la section précédente est exclu ; kpathsea place donc à la racine de chaque arbre une base de noms de fichiers appelée ls-R et la consulte à la place. Quels arbres possèdent un index, c'est TEXMFDBS qui le dit, et sur ce TeX Live 2024 il en énumère exactement quatre — précisément ceux qui portaient !! dans TEXMF. TEXMFHOME n'y figure pas. Voilà pourquoi TEXMFHOME est parcouru sur le disque à chaque fois, et pourquoi un fichier qu'on y dépose est trouvé sur-le-champ.
$ kpsewhich -var-value=TEXMFDBS
{!!/usr/local/texlive/texmf-local,
!!/usr/local/texlive/2024/texmf-config,
!!/usr/local/texlive/2024/texmf-var,
!!/usr/local/texlive/2024/texmf-dist}
# TEXMFHOME is absent from this list.
# The experiment: the SAME file, the SAME TDS layout, two different trees.
$ mkdir -p /tmp/t/tex/latex/demo && touch /tmp/t/tex/latex/demo/demo.sty
$ TEXMFHOME=/tmp/t kpsewhich -progname=pdflatex demo.sty
/tmp/t/tex/latex/demo/demo.sty # found - no ls-R, no mktexlsr
$ TEXMFLOCAL=/tmp/t kpsewhich -progname=pdflatex demo.sty
$ echo $?
1 # NOT found: "!!" means index-onlyLe fichier ls-R lui-même est un texte sans apprêt. Sa première ligne est toujours % ls-R -- filename database for kpathsea; do not change this line., puis chaque répertoire est listé avec son contenu. Le texmf-dist/ls-R de cette machine fait 5,2 Mo et 276 953 lignes ; il indexe 228 764 fichiers répartis dans 16 063 répertoires. La commande qui le reconstruit est mktexlsr, et texhash est un lien symbolique vers elle — le même programme sous un second nom. La règle pratique tombe d'elle-même : placez un fichier à la main dans TEXMFLOCAL ou un arbre système et il faut mktexlsr ; placez-le dans TEXMFHOME et ce n'est pas nécessaire. L'expérience ci-dessus en est toute la raison. Le détail des commandes revient à la page sur la gestion des packages et des polices.
kpsewhich --all : débusquer le fichier masqué par une copie plus ancienne
kpsewhich --all NOM affiche toutes les correspondances, dans l'ordre de recherche. Le kpsewhich nu ne renvoie que la première — le fichier qui sera réellement lu — ; pour voir la deuxième et les suivantes, il faut --all. L'accident classique, « il existe deux fichiers de ce nom et c'est le plus ancien qui l'emporte », devient visible en une commande. Même sur un TeX Live 2024 tout neuf, amsmath.sty existe bel et bien en double : la version stable dans tex/latex/amsmath/ et celle de développement dans tex/latex-dev/amsmath/.
$ kpsewhich --all amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex/amsmath/amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex-dev/amsmath/amsmath.sty
# Same two files, opposite order - because the program name changed the path.
$ kpsewhich --all -progname=pdflatex-dev amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex-dev/amsmath/amsmath.sty
/usr/local/texlive/2024/texmf-dist/tex/latex/amsmath/amsmath.styCes deux exécutions constituent une expérience reproductible sans rien modifier, et elles confirment la règle : le premier résultat l'emporte. En pratique, pourtant, l'endroit où cela mord est presque toujours à côté du manuscrit. Comme TEXINPUTS commence par ., un vieux amsmath.sty ou article.cls égaré dans le dossier du projet il y a des années sera lu avant la copie à jour de la distribution. Pire, la panne prend la forme la plus désagréable qui soit : le document se compile sur votre machine et pas sur celle d'un coauteur. Devant une erreur inexplicable — ! LaTeX Error: Command \... already defined. et consorts — ou un désaccord entre deux machines, tapez d'abord kpsewhich --all. C'est le chemin le plus court vers la réponse.
Utiliser kpsewhich : -var-value contre -expand-path
-var-value montre ce que dit la configuration ; -expand-path montre ce qui se trouve réellement sur le disque. C'est cet écart qui les rend utiles au diagnostic. Affichez TEXMF des deux façons sur ce TeX Live 2024 : -var-value énumère sept arbres, marques !! comprises, alors que -expand-path n'en rend que cinq. Les deux disparus — ~/Library/texlive/2024/texmf-config et ~/Library/texmf — n'ont tout simplement pas encore été créés. Si un arbre figure dans la configuration mais pas dans le développement, c'est que le répertoire n'existe pas. Quand un fichier que vous croyez avoir déposé dans TEXMFHOME reste introuvable, c'est le premier soupçon.
| Commande | Ce qu'elle répond | Quand y recourir |
|---|---|---|
kpsewhich NAME | La première correspondance — le fichier réellement lu | Commencer par là : vérifier que c'est bien ce fichier |
kpsewhich --all NAME | Toutes les correspondances, dans l'ordre de recherche | Pour voir si une copie plus ancienne la masque |
kpsewhich -var-value=TEXMFHOME | La valeur donnée par la configuration, marques !! comprises | Pour confirmer sans deviner où un arbre doit se trouver |
kpsewhich -expand-path=$TEXMF | Le développement restreint aux répertoires qui existent vraiment | Pour repérer l'écart entre configuration et réalité |
kpsewhich -show-path=tex | La liste ordonnée des répertoires pour ce type de fichier | Pour comprendre pourquoi l'ordre est celui-là |
texmf.cnf : d'où viennent les valeurs des variables
Toutes les variables rencontrées jusqu'ici — TEXMF, TEXINPUTS, l'emplacement de chaque arbre — sont consignées dans un fichier de configuration nommé texmf.cnf. Avant toute chose, kpathsea le lit et y prend ses paramètres de fonctionnement : chemins de recherche, position des arbres, limites de mémoire, etc. Le point intéressant est qu'il peut exister plus d'un texmf.cnf. kpathsea les lit dans l'ordre le long d'un chemin dédié, TEXMFCNF, et retient pour chaque variable la première définition rencontrée — les fichiers lus ensuite n'écrasent pas les précédents. Sur cette machine, il y en a deux empilés.
$ kpsewhich -all texmf.cnf
/usr/local/texlive/2024/texmf.cnf # TeX Live's thin override, read first
/usr/local/texlive/2024/texmf-dist/web2c/texmf.cnf # hundreds of lines of defaultsLe texmf.cnf mince du dessus — le fichier de différences qu'écrit TeX Live — est lu en premier, le gros fichier de valeurs par défaut ensuite. Pour changer une valeur durablement, la convention est donc de ne pas modifier le fichier de la distribution mais d'écrire seulement les lignes voulues à un emplacement plus prioritaire. TEXMFLOCAL/web2c/texmf.cnf est cet emplacement. Ainsi les réglages survivent à une montée de version, et quelques lignes suffisent à voir ce qui a été changé. En résumé : texmf.cnf fixe où sont les arbres et à quoi ressemblent les chemins de recherche, puis kpathsea trouve le fichier dans cet ordre, le plus souvent via l'index ls-R. Ces deux couches constituent tout le mécanisme derrière une simple ligne \usepackage{...}.
PATH trouve le programme, kpathsea trouve les fichiers
Ce sont deux mécanismes tout à fait distincts, et les confondre fait dérailler le diagnostic. kpathsea cherche les fichiers que TeX lit — .sty, .cls, polices — mais avant cela le shell doit trouver l'exécutable lui-même, pdflatex. C'est l'affaire du système d'exploitation : il parcourt dans l'ordre les répertoires listés dans la variable d'environnement PATH. TeX Live rassemble ses exécutables dans un unique répertoire bin par système et architecture, et sous macOS MacTeX fournit un lien stable indépendant de l'année, /Library/TeX/texbin. Donc pdflatex: command not found n'est pas un problème kpathsea : c'est presque à coup sûr un problème de PATH. À l'inverse, ! LaTeX Error: File 'foo.sty' not found. n'a rien à voir avec PATH. La marche à suivre pour le configurer relève de la page sur l'installation de bureau.
$ which pdflatex
/Library/TeX/texbin/pdflatex
$ readlink /Library/TeX/texbin
Distributions/Programs/texbinOù placer son propre fichier .sty
Le personnel dans TEXMFHOME, le partagé au laboratoire dans TEXMFLOCAL, et dans les deux cas on respecte la disposition TDS. Toute la règle est là. Ce qu'il ne faut pas faire, c'est deviner l'emplacement : la valeur par défaut de TEXMFHOME dépend du système — ~/texmf sous Linux, mais ~/Library/texmf pour MacTeX sous macOS. On commence donc toujours par kpsewhich -var-value=TEXMFHOME. À l'inverse, un fichier qui n'appartient qu'à une soumission — un myconf.cls de conférence, un journal.sty de revue — peut rester à côté du manuscrit, puisque TEXINPUTS regarde . en premier. Mais poser un nom générique comme article.cls à côté du manuscrit, c'est fabriquer soi-même l'accident de masquage de la section précédente.
# Ask for the tree, never hard-code it: this is ~/texmf on Linux,
# ~/Library/texmf on macOS, %USERPROFILE%\texmf on Windows.
HOME_TREE="$(kpsewhich -var-value=TEXMFHOME)"
mkdir -p "$HOME_TREE/tex/latex/thesisstyle"
cp thesisstyle.sty "$HOME_TREE/tex/latex/thesisstyle/"
# Confirm which copy TeX will pick up. No mktexlsr needed for TEXMFHOME.
kpsewhich thesisstyle.sty
kpsewhich --all thesisstyle.sty # and check nothing else shadows itDès que kpsewhich renvoie le chemin attendu, le manuscrit n'a plus besoin que de \usepackage{thesisstyle}. S'il ne renvoie rien, soupçonnez trois choses dans l'ordre. (1) Le fichier est-il sous tex/latex/<package>/ ? TEXINPUTS ne regarde jamais ailleurs que sous tex/. (2) La casse du nom de fichier correspond-elle ? (3) Si vous l'avez posé dans un arbre système, avez-vous lancé mktexlsr ? Vérifier dans cet ordre transforme le symptôme « TeX est cassé » en « où l'ai-je posé sur la carte de recherche ? » — une question qui, elle, a une réponse.