“Rerun to get cross-references right.” LaTeX 是少数几种「只跑一遍还得不到正确结果」的排版系统之一。交叉引用、目录和引文在第一遍编译时只是被 写进文件,因此像 latexmk 这样的 自动构建 工具会替你反复运行,直到输出稳定下来。本页先从「为什么需要编译好几遍」讲起,再依次走过 latexmk -pdf、每次保存都重新构建的 -pvc、清理用的 -c 与 -C、配置文件 latexmkrc,以及 arara、llmk 和 make 这几个替代方案。
为什么 LaTeX 需要编译好几遍
答案很简单:LaTeX 只把文档从头到尾读一遍。在第 1 页排出目录时,它还不知道第 7 节会落在哪一页。于是 LaTeX 把一路上得到的信息——每个标签的节号与页码、目录行、引用键——写进 .aux、.toc、.lof、.lot 这些辅助文件,并在 下一次运行开始时把它们读回来。也就是说,输出永远是用 上一遍 运行查明的值排出来的。这正是第一遍生成的 PDF 目录为空、引用处显示 ?? 的原因。
这里藏着一个巧妙的机制:LaTeX 并不去数「还要跑几遍」。到 \end{document} 时,它把本次算出的每个标签值 逐一与上一遍从 .aux 读入的值比对,只要有一个不一致,就打印 LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.。反过来说,这条警告消失的那一刻,就是 .aux 不再变化的时刻,也就是到达了 不动点。判断文档是否排好,看的不是页面外观,而是这些辅助文件是否一致。
% doc.aux -- what one run leaves behind for the next one to read
\@writefile{toc}{\contentsline {section}{\numberline {1}One}{1}{}}
\newlabel{sec:one}{{1}{1}{}{}{}}
% doc.log -- the first run, before the .aux settles
LaTeX Warning: Reference `sec:two' on page 1 undefined on input line 5.
LaTeX Warning: There were undefined references.
LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.加上参考文献,这趟往返还会更长。\cite 请求的键在第一遍写进 .aux,bibtex 或 biber 读取该文件生成 .bbl,第二遍把 .bbl 读进来,第三遍才能修正因编号移位而错乱的引用——著名的 latex → bibtex → latex → latex 口诀就是这么来的。若还有索引,同一条链中间还要插入 makeindex。只要靠手动运行,就得每一次重新判断该退回到哪一步。
latexmk 的基本用法:一条命令跑完整个循环
你只需要敲一行:latexmk -pdf document.tex。接下来 latexmk 会盯着 .aux 的变化,按需要运行任意多次 pdflatex,在途中以正确的顺序调用 bibtex/biber 和 makeindex,直到警告消失才停下。这个工具的来历有点特别:它最早是 David J. Musliner 写的一个叫 go 的小脚本,Evan McLean 把它改造成了 latexmk,此后一直由宾夕法尼亚州立大学的物理学家 John Collins 用 Perl 维护——在 TeX Live 2024 上,latexmk -v 会回答「Latexmk, John Collins, 31 Jan. 2024. Version 4.83.」。TeX Live 和 MiKTeX 都自带它,通常无需另行安装。
$ latexmk -pdf doc.tex
Latexmk: applying rule 'pdflatex'...
Run number 1 of rule 'pdflatex'
Running 'pdflatex -recorder "doc.tex"'
Latexmk: References changed.
Latexmk: applying rule 'pdflatex'...
Run number 2 of rule 'pdflatex'
Running 'pdflatex -recorder "doc.tex"'
Latexmk: All targets (doc.pdf) are up-to-date输出里的 -recorder 是 latexmk 自己加上的。带上这个选项,TeX 引擎会把该次运行读过和写过的文件列表输出到 .fls;latexmk 把它和日志对照,推断出依赖关系,并把每个文件的状态存进名为 .fdb_latexmk 的数据库。关键在判定标准:latexmk 比较的是 文件内容的校验和,而不是修改时间。手册把理由说得很直白——在一次 LaTeX 运行中写出的文件,总是比运行前读入的那个更晚,因此单看时间戳,它永远显得「已经过期」。手册指出,这种循环依赖是 LaTeX 固有的问题,而 latexmk 正是为克服它而编写的。此外还有一道保险:如果跑满 $max_repeat 次(默认 5 次)仍未收敛,latexmk 会认定陷入无限循环并中止。
-pdf、-lualatex、-xelatex 的区别:选择引擎
-pdf 选择 pdflatex,-lualatex 选择 lualatex,-xelatex 选择 xelatex。若不加任何选项,latexmk 仍沿用最早版本的行为生成 .dvi,所以只要想要 PDF,就必须指定其中之一。这里有个值得知道的细节:即便指定了 -xelatex,latexmk 也不会让 xelatex 直接写出 PDF。它先生成中间格式 .xdv,在其上跑完全部重复运行,最后才调用一次 xdvipdfmx。当文档贴了很大的 .png 时,生成 PDF 这一步很慢,这样就不必每一遍都重新嵌入图像。-lualatex 是 -pdflua -dvi- -ps- 的简写,-xelatex 是 -pdfxe -dvi- -ps- 的简写。若走 DVI 路线(例如日语的 upLaTeX + dvipdfmx),则选 -pdfdvi。
| 选项 | 作用 | 使用场合 |
|---|---|---|
-pdf | 用 pdflatex 生成 PDF | 以西文为主的常规文档 |
-lualatex | 用 lualatex 生成 PDF(等同于 -pdflua -dvi- -ps-) | OpenType 字体,或用 Lua 做扩展 |
-xelatex | 用 xelatex 生成 .xdv,最后调用 xdvipdfmx | 需要直接使用系统字体时 |
-pdfdvi | 先生成 .dvi 再转换为 PDF | upLaTeX + dvipdfmx 之类经由 DVI 的路线 |
-pvc | 监视源文件,一有变化就重新构建 | 写作过程中,希望每次保存都看到结果 |
-pvctimeout | 在长时间没有变化后结束 -pvc(默认 30 分钟) | 不希望无人看管地一直挂着时 |
-c | 删除可再生成的中间文件,保留 PDF | 想整理工作目录时 |
-C | 在 -c 之外连 .dvi/.ps/.pdf 也删除 | 验证干净构建、准备发布 |
-gg | 先做相当于 -C 的清理,再执行常规构建 | 一条命令完成从零重建 |
-f | 即使出错也继续处理 | 想一次看完全部日志时 |
-silent | 抑制引擎输出(与 -quiet 相同) | 想让 CI 日志更易读时 |
-r | 额外读取指定的配置文件 | 临时想用另一条路径构建时 |
每次保存都重新构建——latexmk -pvc
-pvc 是 preview continuously 的缩写:latexmk 会带着一个查看器常驻,只要任何一个源文件发生变化,就把整个循环重跑一遍。它监视的不只是主 .tex。由 .fls 得出的依赖清单直接成为监视列表,所以用 \input/\include 引入的章节文件、贴进来的图像和 .bib 都在其中。用起来就像文档版的开发服务器。它也有几处脾气:-pvc 一次只能用于一个文件,且与 -p、-pv 不兼容。这个模式还会自动关掉强制模式 -f,若确实两者都要,必须按 -pvc -f 的顺序书写。默认情况下它不会自行退出;加上 -pvctimeout 才会在长时间没有变化之后结束,等待时长默认为 30 分钟,可用 -pvctimeoutmins= 修改,再用 -pvctimeout- 关回去。查看器的选择也有讲究:手册明确提醒,Windows 上的 acroread 会锁定 PDF 文件、阻止写入新版本,因此不适合连续预览。
latexmk -pdf -pvc doc.tex # watch the sources, rebuild on every save
latexmk -pdf -pvc -pvctimeout doc.tex # same, but give up after 30 idle minutes
latexmk -lualatex -pvc doc.tex # the same loop, driven by lualatex编辑器里那个「保存即构建」的按钮,底下通常就是 latexmk。VS Code 的 LaTeX Workshop、TeXstudio、TeXShop、Emacs 的 AUCTeX、Overleaf——名字各不相同,跑的却要么是同一条命令,要么是同一思路的内置实现。所以在终端里记住 -pvc,就等于给自己留了一条退路:编辑器出问题时退回裸命令,就能分清该怪文档还是该怪配置。如果只有编辑器的构建失败而 latexmk 能通过,那么该怀疑的是编辑器设置,不是文档。
latexmk -c 与 -C 的区别:清理生成的文件
区别只有一点:PDF 是留还是删。-c 删除那些可以重新生成的文件——.aux、.log、.toc、.fls、.fdb_latexmk 等等——但保留 .dvi、.ps 和 .pdf。-C 连输出本身也一并删掉。若想先清理再重新构建、一步到位,就用 -gg。这在实践中很要紧,因为过期的 .aux 会掩盖事故。调换了几节的顺序、删掉了一个 \label,你机器上的 PDF 照样看着像模像样,因为旧值还留在那儿;而刚刚 clone 仓库的合著者或 CI 得到的却是坏掉的构建。提交前先 latexmk -C 再让 latexmk -pdf 顺利通过,这就是「文档真的可以只凭源文件构建出来」的证明。
latexmk -c # remove aux, log, toc, fls, fdb_latexmk ... keep the PDF
latexmk -C # remove all of that plus the dvi / ps / pdf output
latexmk -gg -pdf doc.tex # clean first, then build again from scratch把构建写进 latexmkrc 配置文件
在文档旁边放一个名为 latexmkrc 或 .latexmkrc 的文件,那么在该目录里只要敲 latexmk,所有人走的就是同一条路径。latexmk 启动时按以下顺序读取:系统级配置 → 用户的 $HOME/.latexmkrc(或 $XDG_CONFIG_HOME/latexmk/latexmkrc)→ 当前目录下的 latexmkrc 或 .latexmkrc → 用 -r 指定的文件。后读的覆盖先读的,因此项目配置会盖过个人偏好。文件内容是 Perl 代码,# 之后到行尾是注释;多数情况下只需写几行变量赋值。协作时,把这个文件提交进仓库、当作「本文档就这么构建」的约定,是最省口舌的做法。
# latexmkrc -- lives next to the document and is committed with it
$pdf_mode = 4; # 4 = build the PDF with lualatex
$max_repeat = 7; # allow a couple of extra passes on a long document
# Alternative route: upLaTeX -> DVI -> dvipdfmx
# $latex = 'uplatex -interaction=nonstopmode -halt-on-error %O %S';
# $dvipdf = 'dvipdfmx %O -o %D %S';
# $pdf_mode = 3; # 3 = make the PDF from the DVI file
# Extra extensions that -c and -C should remove as well.
$clean_ext = 'synctex.gz run.xml bcf';latexmk 之外的选择:arara、llmk、make
分水岭只有一个问题:由谁来决定步骤。latexmk 从日志和依赖关系中 推断 步骤。arara 则完全不作推断。它读取写在文档里的指令——形如 % arara: pdflatex 的一行注释——并严格照写下的内容、按写下的顺序执行。正如它在 CTAN 上的条目所说,arara 从源代码中的元数据决定自己的动作,而不是依赖日志分析这类间接线索。它由 Island of TeX 围绕 Paulo Roberto Massa Cereda 开发,运行需要 Java。llmk(在 TeX Live 中的宏包名是 light-latex-make,作者为 Takuto Asakura)更进一步走向声明式:流程写在 llmk.toml 或源文件中的 TOML 字段里,只依赖 texlua 运行——它的设计把「在任何环境下结果完全一致」放在首位。
% arara directives: the document itself states the workflow
% arara: pdflatex
% arara: biber
% arara: pdflatex
% arara: pdflatex
\documentclass{article}# llmk.toml -- next to the document; "source" is required in this file
source = "doc.tex"
latex = "lualatex"
bibtex = "biber"
sequence = ["latex", "bibtex", "latex", "latex"]那么裸用 make 呢?写个 Makefile 当然也能驱动 LaTeX,但 make 的判定依据是 修改时间。.aux 每次运行都会被重写,只看时间戳的话,它永远排在被读入的那一份之后——也就永远处于「已过期」的状态。latexmk 的手册正是就这一点写道:这种循环依赖是 LaTeX 固有的,而 latexmk 就是为克服它而编写的。若仍要用 make,可行的写法是保留一份 .aux 副本来比对差异,或者干脆在 Makefile 的目标里调用 latexmk。实际上,许多项目的 Makefile 最后都归结为一行:latexmk -pdf $<。
| 工具 | 如何决定步骤 | 配置写在哪里 | 依赖 |
|---|---|---|---|
latexmk | 依据日志、.fls 与内容校验和推断 | latexmkrc / .latexmkrc(Perl) | Perl;TeX Live 与 MiKTeX 自带 |
arara | 严格照文档中写下的指令执行 | 文档开头的 % arara: 注释 | Java |
llmk | 按 TOML 中声明的 sequence 执行 | llmk.toml 或源文件中的 TOML 字段 | 仅需 texlua |
make | 按修改时间新旧判断;对 .aux 的循环无能为力 | Makefile | make;多数环境本就具备 |
写作中、协作时、提交前分别该用哪条命令
取舍可以归结为三个时刻。写作过程中用 -pvc 盯着,每次保存都看一眼结果;把文档交给别人之前,先跑一遍裸的 latexmk;临提交时,用 latexmk -C 清空再重新构建。尤其把最后一步养成习惯,就能避免那种典型的事故——到了截稿前才发现文档只在自己机器上编译得过。而只要把设置固定在 latexmkrc 里并提交到仓库,CI 服务器和每位合著者都会走同一条路径,「在我这儿是好的」这种争论也就无从谈起。
- 写作时 →
latexmk -pdf -pvc doc.tex:每次保存自动重新构建;不想让它无人看管地一直挂着,可加-pvctimeout。 - 固定引擎 → 在
latexmkrc里写好$pdf_mode等设置,并提交到仓库让所有人共用。 - 交给合著者之前 → 先跑一遍裸的
latexmk -pdf,确认没有残留LaTeX Warning: Label(s) may have changed.。 - 提交或发布前夕 → 用
latexmk -C全部清除后做一次干净构建;latexmk -gg -pdf doc.tex可一步到位。 - 在服务器或 CI 上构建 → 见 CI 页面;加上
-silent能让日志更易读。