複数ファイル構成(\input / \include / subfiles)

複数ファイル構成の LaTeX プロジェクトを十数個の .tex に割っても、コンパイラは眉ひとつ動かしません。pdflatex main.tex は全部を読んで 1 つの PDF を出します。見失うのはエディタのほうです。第 3 章を開いたままビルドキーを押すと ! LaTeX Error: Missing \begin{document}. が返ってくる——エディタが素直に「いま見ているファイル」を組んだからです。これを直す一行が各章の先頭に置く % !TEX root = ../main.tex で、おもしろいのは、この行を LaTeX が一度も読まないことです。ただのコメントであり、宛先はコンパイラではなくエディタです。このページは分割プロジェクトのその второй層——どのエディタがこのマジックコメントを読み、読まないエディタは代わりに何を使うのか、SyncTeX はどうやって正しい章ファイルへ戻ってくるのか、ビルド生成物は実際どこに落ちるのか、そして目の前のファイルが本体でないときに何が壊れるのか——を扱います。分割そのものを担う \input\include\includeonly は別の話で、ページ末尾からたどれます。

% !TEX root — 章ファイルを開いたまま本体を組ませる

本体でないファイルすべての冒頭に % !TEX root = ../main.tex を置けば、どれを前面にしていてもビルドキーは正しい動作をします。TeXShop 自身のドキュメントにある二つの細部は押さえておく価値があります。どちらも人がよく引っかかるからです。第一に、この行は ファイルの最初の 20 行以内 に現れなければなりません——長いライセンスヘッダの下に埋めると、単に見つけてもらえません。第二に、パスは その行が書かれているファイルからの相対 で解決されます。プロジェクトのルートからではありません。chapters/ に置いた章ファイルに必要なのは main.tex ではなく ../main.tex です。絶対パスも書けますが、プロジェクトを移動できなくする代償が付きます。本体ファイル自身にこの行は不要です——それがすでに root なのですから。

text
thesis/
  main.tex                 <- the root; needs no magic comment
  chapters/
    03-results.tex         <- carries the line below
latex
% chapters/03-results.tex -- the first lines of the file
% !TEX root = ../main.tex     % relative to THIS file, not to thesis/
% !TEX TS-program = xelatex   % pin the engine for the whole project
% !TEX encoding = UTF-8 Unicode

\chapter{Results}

この行には先代があり、なぜ後継が勝ったのかが示唆的です。TeXShop にはかつて「Set Project Root...」というメニュー命令があり、答えを章ファイルの隣の別ファイルに記録していました——two.tex には two.texshop が付く、という具合です。その目に見えないファイルを捨てると、TeXShop はまた章ファイルのほうを組みはじめました。TeXShop のドキュメントは現在、この命令を「% !TEX root 方式のほうが堅牢なのでメニューから外した」と説明しています。理由はその一語に尽きます。ファイルの中にある行はファイルと一緒に旅をするからです。コピーしても、上位フォルダの名前を変えても、Git で clone しても、あなたのエディタを一度も開いたことのない共著者に渡しても生き残ります。ファイルの傍らに置いた設定は、いつか必ずファイルとはぐれます。

LaTeX 自身が % !TEX root を読まない理由

% がコメントを始めるからです。コメントは TeX の字句解析の段階で、ほかの何が起きるより先に捨てられます。pdflatexxelatexlualatex はその行に何も見ていません。これはあるプログラム(エディタ)から別のプログラム(エディタのビルド命令)への伝言が、たまたまソースファイルを経由しているだけのものです。実務上の帰結が二つあります。第一に、この行が間違っていても誰も警告してくれません。 存在しないファイルを指させば、エディタは黙って自前の推測——たいていは開いているファイル——に戻り、あなたはまた ! LaTeX Error: Missing \begin{document}. を見ます。第二に、端末や CI から走るビルド(latexmk main.tex、Makefile、GitHub Actions のステップ)はコマンドラインで本体ファイルを名指しするので、マジックコメントを完全に無視します。この行は対話的な編集のための便宜であって、プロジェクトの定義の一部ではありません。

TeX が読むコメントもちょうど一つだけ存在するので、混同しないために知っておく価値があります。主入力ファイルの最初の一行が %& で始まっていると、エンジン自身がそれを解析してフォーマットを選びます%&pdflatex%&latex)。この挙動は -parse-first-line オプションと parse_first_line 設定変数で制御される、と tex の man ページに書かれています。こちらは TeX のフォーマット読み込み機構に由来する、エンジン側の仕掛けです。対して % !TEX ... と綴られるものはすべてエディタ側にあります。見た目が似ているのは、どちらも「LaTeX がつまずかない場所に指示を隠す」ことを望んだ結果の偶然です。

latex
% main.tex -- first line only: the ENGINE parses this one
%&pdflatex

% chapters/03-results.tex -- the engine never sees this one
% !TEX root = ../main.tex

どのエディタが % !TEX root を読み、読まないエディタは何を使うのか

TeXShop・TeXworks・TeXstudio、そして LaTeX Workshop 拡張を入れた VS Code はこの行を読みます。Emacs(AUCTeX)は独自のファイルローカル変数を使い、Overleaf はソースではなくプロジェクトの設定から答えを取ります。なかでも LaTeX Workshop は判断手順を全部ドキュメント化しているので、見ておく価値があります。まず開いているファイルのマジックコメントを見る、なければそのファイル自身が \documentclass\begin{document} を含むかを見る、それでも決まらなければワークスペース直下の .tex を走査して「そのファイルを取り込んでいるもの」を探し、次に \documentclass[main.tex]{subfiles} という subfiles の型を認識し、最後は前回コンパイルが残した .fls のファイル一覧に頼ります。マジックコメントが勝つのは、最初に見られるからです。逆に効かせたくないときの設定名は latex-workshop.latex.build.enableMagicComments です。

エディタ読むもの備考
TeXShop% !TEX rootこの指令の出どころ。% !TEX TS-programencodingspellcheck も同じ書式
TeXworks% !TEX root同じマジックコメント方式を採用
TeXstudio% !TeX rootまず自動検出を試み、この行があれば優先する
LaTeX Workshop% !TEX rootVS Code 用。5 段階の探索の 1 段目。latex-workshop.latex.build.enableMagicComments で無効化
AUCTeXTeX-masterEmacs 用。ファイルローカル変数で、慣例としてファイル末尾に置く
Overleafプロジェクト設定メニューの「Main document」で指定。ソース中には何も残らない

例外として面白いのが Emacs です。AUCTeX は同じ問いを立てますが、答えをファイルローカル変数として、しかも慣例上ファイルの 末尾 のブロックに保存します。片方しか読まないエディタどうしなので、両方書いておいても衝突しません——AUCTeX のブロックは他のエディタにとってただのコメントであり、% !TEX root は Emacs にとってただのコメントです。共有リポジトリでは両方入っている章ファイルをよく見かけますが、それで正しく、費用は 2 行です。Overleaf はこの議論の外側にいます。main document はプロジェクトの属性でメニューから設定するので、ソースと食い違う余地がありません——裏を返せば、プロジェクトをダウンロードしてローカルで開いたとき、その情報はファイルに付いてきません。

latex
% chapters/03-results.tex -- both conventions in one file
% !TEX root = ../main.tex

\chapter{Results}

%%% Local Variables:
%%% mode: latex
%%% TeX-master: "../main"
%%% End:

ファイルをまたぐ SyncTeX——PDF をクリックすると正しい章が開く理由

SyncTeX は、ページ上のすべてのボックスについて どの入力ファイルの何行目から来たか を記録しているからです。だから PDF 上で第 3 章の段落をダブルクリックすると、main.tex ではなく chapters/03-results.tex が開きます。有効化は -synctex=1。できるのはプロジェクトのルートに置かれた main.synctex.gz 一つだけで、名前は root ファイルから取られます。章ごとの synctex ファイルというものは存在しません。単一の索引がプロジェクト全体を覆っていて、まさにそれゆえに、その中のどのファイルでも指し示せるのです。

terminal
$ pdflatex -synctex=1 main.tex
$ synctex edit -o "2:100:200:main.pdf"
SyncTeX result begin
Output:main.pdf
Input:./chapters/ch2.tex
Line:2
SyncTeX result end

コマンドライン版を一度試してみると仕組みが具体的になります。synctex edit は PDF のページと座標を受け取ってファイル名と行番号を返し、synctex view は逆向きに、ソースの行から紙面の位置を返します。背後にある Synchronize TeXnology は、その man ページ自身の言葉によれば Jérôme Laurens に負うところが大きく、いまは TeX Live の一部として保守されています。そして TeXShop のドキュメントは前節との接続を明示しています——逆引き検索のクリックが main.tex ではなく 正しい章のウィンドウ を開いて前面に出せるのは、% !TEX root の行があるからだ、と。二つの機能をふつう一緒に設定するのは、まさにこの理由によります。

罠は、章を単体でコンパイルしたときに来ます。エンジンの生成物は 入力ファイルの隣ではなく、ビルドを走らせた作業ディレクトリ に落ちます。プロジェクトのルートから pdflatex -synctex=1 chapters/03-results.tex を叩けば、03-results.synctex.gz はルートに、main.synctex.gz の真横に生まれます。これで同じソース行を記述した索引が二つ並び、片方は 1 ページ目から始まる 1 章分の PDF を指しています。ビューアがどちらを読むかで、クリックした先のページ番号が食い違いはじめます。本全体のビルドに戻るときは、章単位のビルドが残した PDF と synctex ファイルを消しておいてください。

ビルド生成物はどこに落ちるか——.gitignore と掃除の話

\include は章ごとに .aux を書き、しかもそれを 章ファイルの隣に 書きます。chapters/01-intro.tex を持つプロジェクトをビルドすると、その横に chapters/01-intro.aux ができています。残りはすべて本体ファイルと同じルートに留まります——main.auxmain.logmain.tocmain.outmain.synctex.gz、そして latexmk を使っていれば main.flsmain.fdb_latexmk です。つまり生成物は 1 か所ではなく、ソースツリー全体に薄く撒かれます。

text
thesis/
  main.tex  main.pdf
  main.aux  main.log  main.toc  main.out
  main.synctex.gz  main.fls  main.fdb_latexmk
  chapters/
    01-intro.tex   01-intro.aux    <- one .aux per \include, here
    02-method.tex  02-method.aux

Git にとっては見た目ほど厄介ではありません。.gitignore のパターンはスラッシュを含まなければ どの深さでも一致する ので、素の *.aux の一行が chapters/01-intro.aux もすでに覆っています。覆わないのはルート固定の /*.aux のほうであり、同じくプロジェクトのルートで rm *.aux を叩く掃除の癖も届きません。そしてもっと意外なことに、latexmk も届きません——TeX Live 2024 で試すと、latexmk -c はもちろん latexmk -C でさえ、ルート直下の中間ファイルは消しても chapters/*.aux は残します。「触っていない章でエラーが出る」タイプの古い .aux を疑うときは、find . -name "*.aux" -delete のように明示的に掃除してください。

分割が本当にツールを壊す場所が一つあります。-output-directory です。\include を使うプロジェクトで pdflatex -output-directory=build main.tex と出力先を分けようとすると、実行は死にます。TeX は build/chapters/01-intro.aux を開こうとし、そのサブディレクトリが存在しないので、! I can't write on file に続いて致命的エラーが出て、PDF は一枚も出ません。TeX はディレクトリを作りません。 対処は二つ。同じ階層構造を自分で先に掘っておくか、仕事を latexmk -outdir=build に渡すかです。latexmk はサブディレクトリを自分で作ります。ツリーの外にビルドする複数ファイルのプロジェクトが、たいていエンジンではなく latexmk に駆動されているのは、これが理由です。

terminal
# fails: TeX will not create build/chapters/ by itself
pdflatex -output-directory=build main.tex
# ! I can't write on file ... chapters/01-intro.aux

# works: mirror the subdirectories first
mkdir -p build/chapters && pdflatex -output-directory=build main.tex

# works: latexmk creates them for you
latexmk -pdf -outdir=build main.tex

その .fls こそ、分割したプロジェクトで保存時の再ビルドが成立している理由でもあります。-recorder を付けて走らせると(latexmk は自動で付けます)、エンジンは開いたすべてのファイルを記録するので、main.fls には章ごとに INPUT chapters/01-intro.tex の行が並びます。latexmk はそこから作った依存リストを main.fdb_latexmk に保持し、その全部を監視します。だから第 3 章を保存すると本全体が組み直される——「第 3 章は本の一部だ」とどこにも書いていないのにです。分割の構造を二重に宣言する必要はありません。\include の並びがそのまま依存関係の宣言になっています。

開いているファイルが本体でないとき、何が壊れるか

症状は三つあり、見た目は互いにまるで似ていません。第一に、ふつうの章ファイルを単体でコンパイルすると即座に止まります——最初の \chapter! Undefined control sequence.、続いて ! LaTeX Error: Missing \begin{document}.、そして ! Emergency stop. が出て PDF は生まれません。そのファイルに \documentclass がないのですから当然です。第二に、subfiles の章を単体でコンパイルするともっとたちが悪い。成功してしまう からです。1 ページ目から始まるもっともらしい 1 章分の PDF ができ、他章への相互参照は ?? と印刷されます。第三に、作業ディレクトリを間違えて走らせた場合は、代わりに画像で落ちます。プロジェクト内のすべての相対パスは、ファイルの位置ではなく ビルドを走らせた場所 から解決されるからです。

  • ビルドキーが違うファイルを組む → 本体以外のすべてに % !TEX root を、先頭 20 行以内に、そのファイルからの相対パスで書く。
  • ! LaTeX Error: Missing \begin{document}. → 章ファイルを直接コンパイルしている。そのファイルにプリアンブルは無いし、今後も持たせない。
  • 画像が消える/ファイルが見つからないで止まる → ビルドがプロジェクトのルート以外で走っている。相対パスは作業ディレクトリから解決される。
  • PDF をクリックすると章ではなく本体が開く → その回のビルドに -synctex=1 が無いか、ビューアが古い .synctex.gz を読んでいる。
  • 失敗した実験のあとにルートへ .log.pdf が散る → エンジンは生成物を入力ファイルの隣ではなく作業ディレクトリに書く。

結局のところ、この層を静かにさせるのは二つの習慣です。ひとつ、ファイル名とフォルダ名に空白を入れないこと。ここに出てきた道具はどれも、いずれパスをシェルか % 区切りのマジックコメントに渡します。空白はクォート由来のバグが棲む場所です。もうひとつ、ビルドは必ずプロジェクトのルートから始めること——手で叩くにせよ、Makefile からにせよ、エディタに任せるにせよです。作業ディレクトリは \includegraphics\include-output-directory がそろって参照する唯一の基準点であり、これがずれると三つが同時にずれます。この二つが守れていれば、複数ファイルという層は見えなくなります。そしてこの層は、見えていないときだけ仕事をしています。