自动构建

“Rerun to get cross-references right.” LaTeX 是少数几种「只跑一遍还得不到正确结果」的排版系统之一。交叉引用、目录和引文在第一遍编译时只是被 写进文件,因此像 latexmk 这样的 自动构建 工具会替你反复运行,直到输出稳定下来。本页先从「为什么需要编译好几遍」讲起,再依次走过 latexmk -pdf、每次保存都重新构建的 -pvc、清理用的 -c-C、配置文件 latexmkrc,以及 ararallmkmake 这几个替代方案。

为什么 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 不再变化的时刻,也就是到达了 不动点。判断文档是否排好,看的不是页面外观,而是这些辅助文件是否一致。

text
% 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 请求的键在第一遍写进 .auxbibtexbiber 读取该文件生成 .bbl,第二遍把 .bbl 读进来,第三遍才能修正因编号移位而错乱的引用——著名的 latex → bibtex → latex → latex 口诀就是这么来的。若还有索引,同一条链中间还要插入 makeindex。只要靠手动运行,就得每一次重新判断该退回到哪一步。

latexmk 的基本用法:一条命令跑完整个循环

你只需要敲一行:latexmk -pdf document.tex。接下来 latexmk 会盯着 .aux 的变化,按需要运行任意多次 pdflatex,在途中以正确的顺序调用 bibtex/bibermakeindex,直到警告消失才停下。这个工具的来历有点特别:它最早是 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 都自带它,通常无需另行安装。

terminal
$ 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

选项作用使用场合
-pdfpdflatex 生成 PDF以西文为主的常规文档
-lualatexlualatex 生成 PDF(等同于 -pdflua -dvi- -ps-OpenType 字体,或用 Lua 做扩展
-xelatexxelatex 生成 .xdv,最后调用 xdvipdfmx需要直接使用系统字体时
-pdfdvi先生成 .dvi 再转换为 PDFupLaTeX + 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 文件、阻止写入新版本,因此不适合连续预览。

terminal
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 顺利通过,这就是「文档真的可以只凭源文件构建出来」的证明。

terminal
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 代码,# 之后到行尾是注释;多数情况下只需写几行变量赋值。协作时,把这个文件提交进仓库、当作「本文档就这么构建」的约定,是最省口舌的做法。

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 之外的选择:ararallmkmake

分水岭只有一个问题:由谁来决定步骤。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 运行——它的设计把「在任何环境下结果完全一致」放在首位。

latex
% arara directives: the document itself states the workflow
% arara: pdflatex
% arara: biber
% arara: pdflatex
% arara: pdflatex
\documentclass{article}
toml
# 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 的循环无能为力Makefilemake;多数环境本就具备

写作中、协作时、提交前分别该用哪条命令

取舍可以归结为三个时刻。写作过程中用 -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 能让日志更易读。