> For the complete documentation index, see [llms.txt](https://overleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://overleaf-pro.ayaka.space/latex/zh-cn/zhi-shi-ku/064-how-does-overleaf-compile-my-project.md).

# Overleaf 如何编译我的项目？

## 自动化 LaTeX 编译

为了支持目录、图目录、交叉引用、术语表、索引和参考文献引用等功能，LaTeX 使用多遍排版流程：在一次遍历（编译）中生成的数据会写入文件，并作为后续所需遍历的输入。下面的视频展示了你在 Overleaf 项目中每次点击“重新编译”按钮时会发生什么：

{% embed url="<https://videos.ctfassets.net/nrgyaltdicpt/Uo3I2FXEI8H6aFPBZq171/e2139f9575a4db6955dc2278dc08283c/latexmk-ol.mp4>" %}

在进行手动编译时，通常可以明显看出是否需要再进行一次 LaTeX 运行（遍历），但自动化 LaTeX 编译则带来许多困难。自动化方案面临的一个关键挑战是确定 *需要多少* 次 LaTeX 遍历（编译）来解决任何数据依赖关系，从而确保最终排版的文档是最新且完整的。另一个复杂之处在于正确识别必须执行的外部程序，以预处理文档内容的部分——例如运行 BibTeX/BibLaTeX 生成参考文献列表，或执行更专业的图形应用程序，如 [MetaPost](https://tug.org/metapost.html) 或 [Asymptote](https://asymptote.sourceforge.io/).

## 介绍 latexmk

用于自动化编译的一个成熟方案是 [`latexmk`](https://ctan.org/pkg/latexmk?lang=en)，这是一个大型 Perl 脚本，包含在 TeX Live 中，并由 Overleaf 用于编译你的 LaTeX 项目。 `latexmk` 会自动触发所需次数的 LaTeX 运行，并执行外部（辅助）程序，以处理文档内容的部分。

### 自定义 latexmk

尽管 `latexmk` 非常灵活，不同的 LaTeX 编译环境以及种类繁多的文档类型，使其不切实际地为 `latexmk` 尝试提供能够完全满足每一种可想象的 LaTeX 文档需求的自动化方案。相反， `latexmk` 的行为可以通过配置文件自定义 *在 **Perl***:

* 中实现的“系统级”配置文件，用于适配特定的计算环境/平台，以及
* 一个用户级配置文件，用于处理文档特定需求

该 [`latexmk 手册`](http://mirrors.ctan.org/support/latexmk/latexmk.pdf) 提供了有关配置文件安装和使用的更多细节。

## Overleaf 的 LatexMk 文件

Overleaf 的“系统级”配置文件遵循 `latexmk` 约定（针对 Linux）：其名称为 `**LatexMk**` 并位于文件夹 `/usr/local/share/latexmk/`. **注意**：该文件会在读取 *在……之前* 任何用户级配置文件之前被读取。

用户级配置文件必须命名为 `latexmkrc` 并添加到你的 Overleaf 项目中，以提供自定义规则来满足你的 Overleaf LaTeX 项目/文档的特定需求。

Overleaf 的 `LatexMk` 文件包含 Overleaf 特定处理的代码，以及术语表、术语命名表和其他常用宏包的规则。Overleaf 的 `LatexMk` 文件可能会随 TeX Live 的不同版本而略有变化，以适应 TeX Live 本身中宏包和软件工具的更改与更新。

### 更改 LaTeX 的编译模式

默认情况下，Overleaf 指示 latexmk 使用一种我们（Overleaf）称之为 **尽管有错误仍尝试编译**的编译模式。在该模式下，LaTeX 编译器即使遇到 [LaTeX 编译错误](/latex/zh-cn/latex-ji-chu/05-errors.md)；即使生成的 PDF 可能包含错误输出，也仍然会被生成。通常，编译错误可能过于严重，或数量过多，以至于 LaTeX 编译器无法修复，因此它可能仍会“退出”而不生成 PDF。

你可以将 LaTeX 编译模式更改为在遇到第一个错误时立即停止编译：请参阅帮助文章 [使用“遇到第一个错误即停止”编译模式](/latex/zh-cn/zhi-shi-ku/149-using-the-stop-on-first-error-compilation-mode.md) 以获取更多信息。

### 如何获取 Overleaf 的 LatexMk 文件副本

以下链接会打开一个项目，用于排版 Overleaf 的 `LatexMk` 文件，该文件与您项目所使用的 LaTeX 编译器和 TeX Live 版本一起使用，并且也可供下载：

```latex
\documentclass[a4paper]{article}
\usepackage[margin=1cm]{geometry}
\usepackage{verbatim,shellesc}
\ShellEscape{cp /usr/local/share/latexmk/LatexMk ./LatexMk}
\begin{document}
\section*{关于本项目}
本项目提供对 Overleaf 使用的系统 \texttt{LatexMk} 初始化（配置）文件的访问。\texttt{LatexMk} 是一个 Perl 脚本，可能会根据项目所选的 \TeX{} Live 版本和编译器略有不同。如果你需要 100\% 确定正在使用哪个 \texttt{LatexMk}，请将本项目中的代码添加到你的项目中并编译，从而排版出 \texttt{LatexMk} 的清单，并将其作为输出文件之一提供下载。
\section*{列出 \texttt{LatexMk} 文件}
\verbatiminput{./LatexMk}
\end{document}
```

&#x20;[在 Overleaf 中打开此代码。](https://www.overleaf.com/docs?engine=\&snip_name=Display+Overleaf+latexmkrc+file\&snip=%5Cdocumentclass%5Ba4paper%5D%7Barticle%7D%0A%5Cusepackage%5Bmargin%3D1cm%5D%7Bgeometry%7D%0A%5Cusepackage%7Bverbatim%2Cshellesc%7D%0A%5CShellEscape%7Bcp+%2Fusr%2Flocal%2Fshare%2Flatexmk%2FLatexMk+.%2FLatexMk%7D%0A%5Cbegin%7Bdocument%7D%0A%5Csection%2A%7BAbout+this+project%7D%0AThis+project+provides+access+to+the+system+%5Ctexttt%7BLatexMk%7D+initialization+%28configuration%29+file+used+by+Overleaf.++%5Ctexttt%7BLatexMk%7D+is+a+Perl+script+which+may+vary+slightly+according+to+the+%5CTeX%7B%7D+Live+version+and+compiler+chosen+for+the+project.+If+you+need+to+be+100%5C%25+certain+which++%5Ctexttt%7BLatexMk%7D+is+being+used%2C+add+code+from+this+project+to+your+project+and+compile+to+typeset+a+listing+of+%5Ctexttt%7BLatexMk%7D+and+make+it+available+for+download+as+one+of+the+output+files.%0A%5Csection%2A%7BListing+the+%5Ctexttt%7BLatexMk%7D+file%7D%0A%5Cverbatiminput%7B.%2FLatexMk%7D%0A%5Cend%7Bdocument%7D)

在编译上面的项目后， `LatexMk` 文件可通过 **日志和输出文件** 按钮下载，如下面的简短视频所示：

{% embed url="<https://videos.ctfassets.net/nrgyaltdicpt/1re6AeMUF2EQnlZ8Cse5ke/ae6ee91fa7e2b2efa8ffb27ad8bab1a0/latexmkrc.mp4>" %}

### 其他说明与建议

如果你想设置或检查用于编译你项目的 TeX Live 版本，请参阅这篇 [Overleaf 博客文章](https://www.overleaf.com/blog/new-feature-select-your-tex-live-compiler-version#How-Switch) 或下图：

![选择 Overleaf TeX Live 版本](/files/1d0ec3b69086ea5d7c1c368741f83132507dd923)

如果你想使用 latexmk 离线构建你的 Overleaf 项目，可能需要使用一份 Overleaf 的 LatexMk 文件副本，以确保它应用 Overleaf 的构建流程——注意 Overleaf 的作业名被设为 output。

Overleaf 使用 latexmk 的 `-cd` 命令行标志进行编译，并且编译始终从项目的根目录运行。请注意某些 `latexmk` 选项与 Overleaf 不兼容——尤其是如果你设置了 `$preview_continuous_mode` 或 `$output_dir` 选项，可能会破坏 Overleaf 上的预览。

有关 `latexmkrc` 文件的更多信息，请阅读文章 [如何在 Overleaf 中使用 latexmkrc](/latex/zh-cn/shen-ru-wen-zhang/28-how-to-use-latexmkrc-with-overleaf.md) ，其中包含我们友好的 Overleaf TeXperts 提供的实用技巧。

## Overleaf 的 LatexMk 文件清单（2023）

为方便起见，以下清单展示了 Overleaf 的 `LatexMk` 文件在 TeX Live 2023 中的使用情况——请使用上方链接获取最新版本。

```perl
# 设置
$xdvipdfmx = "xdvipdfmx -z 6 -i dvipdfmx-unsafe.cfg -o %D %O %S";

# 允许 pstricks 透明效果的变通方案 (https://github.com/overleaf/issues/issues/3449)
$dvipdf = "dvipdf -dNOSAFER -dALLOWPSTRANSPARENCY %O %S %D";

###############################
# PDF 文件后处理 #
###############################

$compiling_cmd = "internal overleaf_pre_process %T %D";
$success_cmd = "internal overleaf_post_process %T %D";
$failure_cmd = $success_cmd;

# 等同于 -gt 选项。用于防止 latexmk 跳过重新编译
# output.log 和 output.pdf 的
$go_mode = 3;

# 等同于 -bibtex 选项。当 bbl 文件
# 已过期时无条件运行 bibtex。
$bibtex_use = 2;

my $ORIG_PDF_AGE;

sub overleaf_pre_process {
    my $source_file = $_[0];
    my $output_file = $_[1];

    # 如果存在，获取现有 PDF 的年龄
    $ORIG_PDF_AGE = -M $output_file
}

sub overleaf_post_process {
    my $source_file = $_[0];
    my $output_file = $_[1];
    my $source_without_ext = $source_file =~ s/\.tex$//r;
    my $output_without_ext = $output_file =~ s/\.pdf$//r;

    # 查找 knitr concordance 文件
    my $concordance_file = "${source_without_ext}-concordance.tex";
    if (-e $concordance_file) {
        print "正在为 knitr 修补 synctex 文件...\n";
        system("patchSynctex.R", $source_without_ext, $output_without_ext);
    }

    # 如果 PDF 文件不存在或未更新，则提前返回
    my $NEW_PDF_AGE = -M $output_file;
    return if !defined($NEW_PDF_AGE);
    return if defined($ORIG_PDF_AGE) && $NEW_PDF_AGE == $ORIG_PDF_AGE;

    # 找出 qpdf 的位置
    $qpdf //= "/usr/bin/qpdf";
    $qpdf = $ENV{QPDF} if defined($ENV{QPDF}) && -x $ENV{QPDF};
    return if ! -x $qpdf;
    $qpdf_opts //= "--linearize --newline-before-endstream";
    $qpdf_opts = $ENV{QPDF_OPTS} if defined($ENV{QPDF_OPTS});

    # 运行 qpdf
    my $optimised_file = "${output_file}.opt";
    system($qpdf, split(' ', $qpdf_opts), $output_file, $optimised_file);
    $qpdf_exit_code = ($? >> 8);
    print "qpdf 退出代码=$qpdf_exit_code\n";

    # 如果 qpdf 成功，则替换输出文件
    # qpdf 成功时返回 0，出现警告时返回 3（仍会生成输出 PDF）
    return if !($qpdf_exit_code == 0 || $qpdf_exit_code == 3);
    print "正在将优化后的文件重命名为 $output_file\n";
    rename($optimised_file, $output_file);

    print "正在提取 $output_file 的 xref 表\n";
    my $xref_file = "${output_file}xref";
    system("$qpdf --show-xref ${output_file} > ${xref_file}");
    $qpdf_xref_exit_code = ($? >> 8);
    print "qpdf --show-xref 退出代码=$qpdf_xref_exit_code\n";
}

##############
# 术语表 #
##############
add_cus_dep( 'glo', 'gls', 0, 'glo2gls' );
add_cus_dep( 'acn', 'acr', 0, 'glo2gls');  # 来自 Overleaf v1
sub glo2gls {
    system("makeglossaries $_[0]");
}

#############
# makeindex #
#############
@ist = glob("*.ist");
if (scalar(@ist) > 0) {
    $makeindex = "makeindex -s $ist[0] %O -o %D %S";
}

################
# 术语命名表 #
################
add_cus_dep("nlo", "nls", 0, "nlo2nls");
sub nlo2nls {
        system("makeindex $_[0].nlo -s nomencl.ist -o $_[0].nls -t $_[0].nlg");
}

#########
# Knitr #
#########
add_cus_dep( 'Rtex', 'tex', 0, 'do_knitr');
add_cus_dep( 'Rnw', 'tex', 0, 'do_knitr');
sub do_knitr {
    Run_subst(qq{Rscript -e '
        library("knitr");
        opts_knit\$set(concordance=T);
        knitr::knit(%S, output=%D);
        '}
    );
}

##########
# feynmf #
##########
push(@file_not_found, '^feynmf: Files .* and (.*) not found:$');
add_cus_dep("mf", "tfm", 0, "mf_to_tfm");
sub mf_to_tfm { system("mf '\\mode:=laserjet; input $_[0]'"); }

push(@file_not_found, '^feynmf: Label file (.*) not found:$');
add_cus_dep("mf", "t1", 0, "mf_to_label1");
sub mf_to_label1 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t1"); }
add_cus_dep("mf", "t2", 0, "mf_to_label2");
sub mf_to_label2 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t2"); }
add_cus_dep("mf", "t3", 0, "mf_to_label3");
sub mf_to_label3 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t3"); }
add_cus_dep("mf", "t4", 0, "mf_to_label4");
sub mf_to_label4 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t4"); }
add_cus_dep("mf", "t5", 0, "mf_to_label5");
sub mf_to_label5 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t5"); }
add_cus_dep("mf", "t6", 0, "mf_to_label6");
sub mf_to_label6 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t6"); }
add_cus_dep("mf", "t7", 0, "mf_to_label7");
sub mf_to_label7 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t7"); }
add_cus_dep("mf", "t8", 0, "mf_to_label8");
sub mf_to_label8 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t8"); }
add_cus_dep("mf", "t9", 0, "mf_to_label9");
sub mf_to_label9 { system("mf '\\mode:=laserjet; input $_[0]' && touch $_[0].t9"); }

##########
# feynmp #
##########
push(@file_not_found, '^dvipdf: Could not find figure file (.*); continuing.$');
add_cus_dep("mp", "1", 0, "mp_to_eps");
sub mp_to_eps {
    system("mpost $_[0]");
    return 0;
}

#############
# asymptote #
#############

sub asy {return system("asy \"$_[0]\"");}
add_cus_dep("asy","eps",0,"asy");
add_cus_dep("asy","pdf",0,"asy");
add_cus_dep("asy","tex",0,"asy");

#############
# metapost  #  # 来自 Overleaf v1
#############
add_cus_dep('mp', '1', 0, 'mpost');
sub mpost {
    my $file = $_[0];
    my ($name, $path) = fileparse($file);
    pushd($path);
    my $return = system "mpost $name";
    popd();
    return $return;
}

##########
# chktex #
##########
unlink 'output.chktex' if -f 'output.chktex';
if (defined $ENV{'CHKTEX_OPTIONS'}) {
    use File::Basename;
    use Cwd;

    # 识别主文件
    my $target = $ARGV[-1];
    my $file = basename($target);

    if ($file =~ /\.tex$/) {
        # 在有限范围内更改目录
        my $orig_dir = cwd();
        my $subdir = dirname($target);
        chdir($subdir);
        # 在主文件上运行 chktex
        $status = system("/usr/local/bin/run-chktex.sh", $orig_dir, $file);
        # 返回原始目录
        chdir($orig_dir);

        # 在 VALIDATE 模式下，我们在运行 chktex 后始终退出
        # 否则，如果设置了 EXIT_ON_ERROR，我们就退出

        if ($ENV{'CHKTEX_EXIT_ON_ERROR'} || $ENV{'CHKTEX_VALIDATE'}) {
            # chktex 不允许我们通过退出状态访问错误信息
            # 因此查看输出内容
            open(my $fh, "<", "output.chktex");
            my $errors = 0;
            {
                local $/ = "\n";
                while(<$fh>) {
                    if (/^\S+:\d+:\d+: Error:/) {
                        $errors++;
                        print;
                    }
                }
            }
            close($fh);
            exit(1) if $errors > 0;
            exit(0) if $ENV{'CHKTEX_VALIDATE'};
        }
    }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://overleaf-pro.ayaka.space/latex/zh-cn/zhi-shi-ku/064-how-does-overleaf-compile-my-project.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
