宏包冲突

! LaTeX Error: Option clash for package inputenc 通常被解释成「同一个宏包用不同选项加载了两次」,但这并不准确。latex.ltx 实际执行的是一次子集检查:第二次 \usepackage 所要求的选项,只要全都已在第一次加载的选项之中就放行,只要出现一个新名字就冲突。因此 \usepackage[a,b]{X} 之后写 \usepackage[a]{X} 没问题,反过来就会出错。更麻烦的是,有些宏包会完全跳过这项检查,xcolor 就是最典型的一个。本页讲清这条规则的真面目、\PassOptionsToPackage 必须写在哪个位置、为什么 hyperref 要放在靠后的位置,以及在 TeX Live 2024 上确实仍然无法共存的宏包组合。

触发冲突的是「新增的选项」,而不是「不同的选项」

第二次 \usepackage 只要索要了第一次没有的任何一个选项,立刻就会冲突。反之,只要它要的是第一次那组选项的子集——顺序不同、数量更少、甚至一个都不要——就什么也不会发生。 原因在 latex.ltx 里:\@onefilewithoptions@clashchk 调用 \@if@ptions,其内部的 \@if@pti@ns 逐个取出所请求的选项,用 \in@ 到已记录的列表里查找。只要有一个查不到,就进入第二个分支,也就是 \@latex@error{Option clash for …}。下面这张表是在 TeX Live 2024 上实跑七种组合的结果,规则一目了然。

第一次 → 第二次的选项结果(TeX Live 2024 实测)
[alpha] → [beta]冲突——beta 是第一次加载中没有的新名字
[alpha] → [alpha]没事——重复同一个选项什么也不会发生
[alpha] → []没事——空集合永远是子集,所以不带选项的重复加载总是安全的
[] → [alpha]冲突——这正是「被文档类或别的宏包抢先加载」的典型情形
[alpha,beta] → [alpha]没事——要得更少总是被允许的
[alpha] → [alpha,beta]冲突——要得更多就会失败;正确做法是在第一次加载时把选项写全
[beta,alpha] → [alpha,beta]没事——顺序无关,比较的是集合

错误那一行本身很简短,但 .log 里会具体写明该宏包最初是用哪些选项加载的、现在又请求了什么。这四行就是诊断的全部内容,所以别对着终端发愁,打开日志即可。另一件值得知道的事是报出的行号会偏移。由于 \usepackage 末尾可以带一个可选的日期参数,它必须越过右花括号去看有没有 [,而这次前瞻会伸进下一行。这就是为什么第 3 行的 \usepackage 引发的冲突会被报成 l.4 \begin{document}

log
% terminal shows only the first line; the rest is in the .log
./oc1.tex:4: LaTeX Error: Option clash for package inputenc.
l.4 \begin
          {document}
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.

为什么 xcolor 不会冲突:绕开这项检查的三种做法

在 TeX Live 2024 上,写完 \usepackage[dvipsnames]{xcolor} 再写 \usepackage[table]{xcolor},一点错误都不会有——不是因为 xcolor 格外宽容,而是因为那项子集检查根本没有被调用 对于已经加载过的宏包,latex.ltx 里的 \@onefilewithoptions 会先看有没有一个叫 opt@handler@<宏包>.sty 的宏。没有,就交给 \@onefilewithoptions@clashchk,也就是上面那项子集检查;有,内核就整个甩手,让那个处理器去处理新请求的选项。而 TeX Live 2024 的 xcolor.sty 已经迁移到新方式:用 \DeclareKeys 声明选项、用 \ProcessKeyOptions 处理,而正是 \ProcessKeyOptions 注册了 opt@[email protected]。用 \show 一看,它的内容只有一行:\ProcessKeyOptions [xcolor]

latex
% no error on TeX Live 2024: xcolor uses \DeclareKeys + \ProcessKeyOptions
\usepackage[dvipsnames]{xcolor}
\usepackage[table]{xcolor}

% still an error: inputenc uses the classic \DeclareOption mechanism
\usepackage[utf8]{inputenc}
\usepackage[latin1]{inputenc}

% check for yourself which mechanism a package uses
\makeatletter
\expandafter\show\csname opt@[email protected]\endcsname
% -> \opt@[email protected]=\protected\long macro: ->\ProcessKeyOptions [xcolor].
\makeatother

改用键值式选项是最规矩的绕法,但还有另外两种。fontenc.sty 在自己文件的末尾把 [email protected][email protected] 双双设回 \relax——而 \@ifl@aded 正是靠查看 ver@… 来判断的,于是 fontenc 把「自己曾被加载」这个事实本身抹掉了,下一句 \usepackage[T2A]{fontenc} 就算作首次加载。另一种是 caption,它在 caption3.sty直接替换掉内核的 \@onefilewithoptions:当 caption 家族的宏包被再次加载时,新选项会被导向相当于 \captionsetup 的处理,然后再以空选项重新加载该宏包。紧挨着这段代码的作者注释说,他在 2018 年和 2020 年两次向 LaTeX 团队请求提供正式接口,都被回绝了,并把自己的这段替换称作「肮脏的 hack」。一个安安静静不报冲突的宏包,背后多半就是这三种之一。

宏包(用新选项重新加载)TeX Live 2024 上的结果与原因
xcolor不冲突:它用 \DeclareKeys\ProcessKeyOptions,检查被绕开
fontenc不冲突:它在自己加载的末尾清掉 [email protected],于是看起来又是未加载状态
caption不冲突:caption3.sty 替换了内核的 \@onefilewithoptions
inputenc会冲突:它仍然使用经典的 \DeclareOption 机制
geometry会冲突:想追加设置请改用 \geometry{…},不要再次加载
hyperref会冲突:想追加设置请改用 \hypersetup{…},不要再次加载
babel会冲突:把所有语言写进同一句 \usepackage,不要加载两次
amsmath会冲突:fleqnleqno 这类选项按惯例应写给文档类

\PassOptionsToPackage 该写在哪里:第一次加载之前,别无他处

\PassOptionsToPackage{opt}{X} 只有写在 X 首次加载之前才有意义,最保险的位置是文件第一行,\documentclass 之上。 这条命令做的只是把 opt 追加到名为 [email protected] 的选项列表里——而这一个动作同时带来两个效果:X 加载的那一刻就拿到了 opt,之后再出现的 \usepackage[opt]{X} 也能通过子集检查。也就是说,「让选项生效」和「消除冲突」不是两件事,而是同一行的一体两面。它之所以能写在 \documentclass 之前,是因为 \PassOptionsToPackage 在格式层面就已定义,不像 \usepackage 那样以读入文档类为前提。

document.tex
% correct: the very first line, above \documentclass
\PassOptionsToPackage{table}{xcolor}
\documentclass{article}
\usepackage{tikz}          % pulls xcolor in -- with table already attached
\usepackage[table]{xcolor} % no clash, and \rowcolor works

% WRONG: after xcolor is already loaded. No error is raised, and the
% option is silently never executed.
\documentclass{article}
\usepackage{tikz}
\PassOptionsToPackage{table}{xcolor}
\usepackage[table]{xcolor}

这条命令有一种非常安静的失败方式。把 \PassOptionsToPackage 写在 X 已经加载之后,完全不会报错——但那个选项永远不会被执行。 用带探针的测试宏包实测:放在加载之前,选项的代码确实运行了;放在加载之后,冲突消失,代码却没有运行。因为错误没了就以为问题解决了,是这个工具最危险的用法。 错误文本本身还藏着第二个陷阱:它建议把选项加到 \documentclass 的全局选项里。照字面做并不管用——加上 \documentclass[beta]{article} 却仍保留 \usepackage[alpha]{X}\usepackage[beta]{X},冲突照旧重现。只有把两处局部选项都删掉,写成 \documentclass[alpha,beta]{article} 加两句光秃秃的 \usepackage{X},才真正通过。

查出是谁加载了你从没要过的宏包

在导言区加一行 \listfiles.log 末尾就会列出实际加载过的所有文件;至于「是谁把它拉进来的」,答案在日志的括号嵌套里。 ( 打开一个文件、) 关闭它,所以如果打开 xcolor.sty 的那个 ( 落在 pgfcore.sty 的括号内部,把它拉进来的就是 pgf。实测:光秃秃的 article 只读 3 个文件,加一行 tikz 就变成 34 个,xcolor 就在其中;hyperref 单独就带来 30 个。「我根本没写 xcolor,怎么会有 option clash」——答案几乎总在这张清单里。 想追得更细,加上 -recorder,所有打开过的文件都会记进 .fls

log
% the nesting says who pulled xcolor in: pgf did
(.../pgf/basiclayer/pgfcore.sty
(.../pgf/systemlayer/pgfsys.sty
...
)) (.../xcolor/xcolor.sty
...
)

% and with \listfiles, the summary table at the end of the .log
 *File List*
 article.cls    2023/05/17 v1.4n Standard LaTeX document class
  xcolor.sty    2022/06/12 v2.14 LaTeX color extensions (UK)
 ***********

加载顺序:为什么 hyperref 要靠后,以及哪些要排在它之后

hyperref 之所以要靠后加载,是因为它会覆盖大量机制——\ref\cite\caption、目录、索引——而覆盖必须作用在最终的定义上。如果后面又有人重新定义同样的东西,hyperref 做的手脚就白费了。 但规则是「靠近末尾」,不是「最后一个」。那些建立在 hyperref 之上的宏包当然必须排在它后面:常见的有 bookmarkcleverefhypcapglossaries。其中 cleveref 会自己检测顺序违规并报错,所以写错了立刻就能发现;它具体的顺序要求由「未定义引用」那一页详细讲解。

有一点必须说明白。大量「A 必须排在 B 之前」的传说,在 TeX Live 2024 上其实什么也不会发生。 我把 floathyperrefgeometryhyperrefalgorithmhyperrefbookmarkhyperrefglossarieshyperref 按两种顺序各试了一遍,没有一例产生错误或警告;这是宏包多年累积兼容代码的结果。因此值得遵守的顺序规则,是那些写在宏包自己手册里的,为了迎合来路不明的顺序说法而重排导言区,多半是浪费时间。不过把 hyperref 放在靠后位置的习惯仍应保留,因为它源自「覆盖」这件事本身的性质,而不是传说。

\usepackage\RequirePackage 的区别

在导言区里,这两者字面上就是同一个东西:latex.ltx 在处理 \documentclass 的过程中执行了 \let\usepackage\RequirePackage,从那一刻起它们就是同一条命令。差别只存在于 \documentclass 之前,以及 .sty.cls 文件内部。 在那些位置,\usepackage 会以 ! LaTeX Error: \usepackage before \documentclass. 停下——格式层的 \usepackage 存在的唯一目的就是给出这条诊断。因此自己写宏包或文档类时要用 \RequirePackage,也因此把它和 \PassOptionsToPackage 搭配,就能在 \documentclass 之上注入选项。若文档类想把自己的选项原样传给它加载的宏包,还有 \RequirePackageWithOptions 专门做这件事。

latex
% before \documentclass, only \RequirePackage works
\RequirePackage{fix-cm}
\PassOptionsToPackage{table}{xcolor}
\documentclass{article}

% inside your own mystyle.sty, likewise
\ProvidesPackage{mystyle}[2026/01/01 house style]
\RequirePackage{xcolor}
\RequirePackageWithOptions{geometry}  % forward this package's own options

确实仍然无法共存的组合:在 TeX Live 2024 上逐一验证

在选项之外,有些组合因为把同一套机制重复定义了两遍而无法共存。这样的组合比传说中少,而且症状未必是「加载时报错」。 干脆利落地拒绝的是 biblatexnatbib,会停在 ! Package biblatex Error: Incompatible package 'natbib'.(如果只是想要 \citet\citep,写 \usepackage[natbib=true]{biblatex} 就够了)。麻烦的是那些悄悄坏掉的:同时加载 subfiguresubcaption一条错误都不会有——问题要到很久之后才浮现,出现在你写下 \begin{subfigure}{0.4\textwidth} 的那一行,形式是 ! Missing number, treated as zero.。因为 \begin{subfigure}subfigure 定义的老式 \subfigure 命令吞掉,被完全按另一种方式解读了。

组合在 TeX Live 2024 上实际会发生什么
biblatex + natbib立刻报错停止;合并成一句 \usepackage[natbib=true]{biblatex}
natbib + biblatex这个顺序不会报错,只有 \citeauthor 等被重新定义的警告
subfigure + subcaption两者都能悄无声息地加载;之后 \begin{subfigure} 会坏掉,报出看似无关的错误
subfig + subcaption都能加载,但 subcaption 不再定义自己的环境,于是出现 ! LaTeX Error: Environment subfigure undefined.
caption + subfigure已经不再冲突;.log 里只留下 Package caption Info: subfigure package is loaded.
cleveref + hyperref先加载 cleveref 会中止运行;它必须排在 hyperrefvarioref 之后
cite + natbib不会报错,但 natbib 会警告不应与 cite 同用;二者留一

从这张表里该得出的教训是:别再听信「A 与 B 不兼容」的传言,动手写一份五行的文档验证一下captionsubfigure 的不兼容当年确实是错误,如今已降级为 Info。反过来,subfiguresubcaption 正是「加载时没报错所以没问题」这种判断最危险的例子。症状已经从「加载时报错」转移到「很久之后一条看似无关的错误」——这就是 TeX Live 2024 上宏包不兼容的现状,也正因如此,跑 \listfiles、读 .log 的习惯才格外划算。