Doxygen 1.8.11でのPDF作成の苦悩
現在携わっているプロジェクトで、実装完了後にDoxygenで資料を作成しようという話が出ました。
Doxygenとは、C言語やC++、Javaなどで、ソースファイルのコメントからドキュメントを生成するためのツールです。規則に沿ってコメントを書いていけば、ソースコードの完成と(ほぼ)同時にドキュメントも完成するという優れもの。
とはいえ、実際にはこれまで使用したことが無かったので事前に調査を兼ねて使ってみました。
最終形態は、ブラウザでの閲覧が出来るHTML形式ではなく、紙にも印刷できるよう(納品にも使えるよう)PDF形式での出力とします。とはいえ、閲覧性を無視したくはないので「しおり」は付けたいですよね。それと目次、索引も欲しいところ。
最初はそんな風に簡単に考えていたんですが、意外とはまる要素が次々と襲い掛かってきて、なんだかんだと苦労しました。
詳しい方なら苦労しないのでしょうが、Doxygen初心者がはまった点を情報共有として公開したいと思います。
前提条件の環境
ちょっとわけありで、Ubuntu 16.04 LTS を使用しています。ですので、Doxygenも1.8.11となります。
まず、この前提条件が古すぎて、ネット上での情報に辿り着けなかったのが苦労した原因なのかな、と思います。
なので、Ubuntu22.04やDoxygen1.9.xxなら、何の問題もないかもしれません。あくまでも、Doxygen1.8.11を使用している方への情報提供ということで…。
生成環境の用意
ドキュメント生成に使用する題材は、C言語のプログラム。
- main.c
- thread1.c
- thread1.h
- thread2.c
- thread2.h
という5つのファイルからなるものです。mainでスレッドを2つ作って、1秒ごとにそれぞれのスレッドがグローバル変数を排他制御せずにインクリメントする、という怪しいもの。内容はどうでもよくて、複数ファイル、複数関数から成り、各関数の関連が面白そうなものがいいかな、というだけの話です。
ディレクトリ構成は次のようにしています。
TestDir
├ src
│ └ 各ソースファイル
├ obj
│ └ 生成された実行ファイル他
└ makefile
で、まずは、ビルドが通ってLinux上で動作することを確認。Doxygenって、ビルドが通らなくても大丈夫なんでしたっけ?というくらい初心者です(笑)
そもそもDoxygenが入っていなければ話になりませんので、インストール。
$ sudo apt update
$ sudo apt install doxygen
PDF化は、LaTexを使用したいと考えていますので、Ubuntu 16.04でLatexを使用するための、いくつかのパッケージをインストール。
なにが必須かは判らないんですが、いくつかのサイトを参考に、こんな感じで。(結果的に良かったので大丈夫なんでしょう、きっと)
$ sudo apt install texlive-latex-extra texlive-lang-japanese texlive-fonts-extra xdvik-ja
関連図などの画像を生成するために、Graphvizを使用しますので、これもインストール。
$ sudo apt install graphviz
これだけだったと思います。
ちなみに、LaTexとは、
LATEX(ラテック、ラテフ、など。)とは、レスリー・ランポートによって開発されたテキストベースの組版処理システムである。電子組版ソフトウェアTEXにマクロパッケージを組み込むことによって構築されており、単体のTEXに比べて、より手軽に組版を行うことができるようになっている。
https://ja.wikipedia.org/wiki/LaTeX
というものです。今回は、PDF作成のための中間生成物のように(ブラックボックス的に)扱っていますが、ちゃんと理解を深めれば、いろいろと悩むことも少なかったのかもしれませんね。
Graphvizの方は、
Graphviz (Graph Visualization Software) は AT&T研究所が開発したオープンソースのツールパッケージであり、DOT言語で記述されたグラフ構造(ノードとエッジから成るネットワーク構造)を描画する。パッケージにはアプリケーションソフトウェアからツールを使うためのライブラリも含まれる。GraphvizはEclipse Public Licenseライセンスで提供されるフリーソフトウェアである。
https://ja.wikipedia.org/wiki/Graphviz
ということです。いずれも、今回の Doxygen生成 → PDF 化に必要となったパッケージです。
新たに構築したUbuntu環境ではないため、既にインストールされているパッケージが効いていたら参考にならず申し訳ないですが、今回追加したのはこれだけです。
最初の一歩
まずはmakefileのある、TestDirでDoxygen用の設定ファイルを生成します。
$ doxygen -g
これで、Doxyfile と呼ばれるファイルが、同じディレクトリに生成されます。
長いので引用はしないですが、1行目は
# Doxyfile 1.8.11
から始まります。doxygenのバージョンによって設定項目が異なるため、他から流用せずに使用する環境で生成するのが良いのかな、と思います。
では、さっそくこの「素の」DoxyfileでPDF化をやってみましょう。
$ doxygen
設定ファイル名を Doxyfile から変更していなければ、これでOKです。変更していれば、 $ doxygen の後に設定ファイル名を付ければいいそうです。
標準の設定で、Latexの生成までは完了しますので、同階層に latex というディレクトリが出来ています。この中を探しても、PDFファイルはありません。そのため、
$ cd latex
$ make
とします。これで、latexディレクトリにあるMakefileが処理されて、PDFファイルが生成されます。
出来ましたね。さっそく中身を…。
なんということでしょう。中身が空っぽです。
そりゃそうですよね。設定ファイルをまったく触ってないんですから。
Doxyfile を変更する
最低限必要と思われる箇所を修正してみます。
CREATE_SUBDIRS = YES
OUTPUT_LANGUAGE = Japanese
OPTIMIZE_OUTPUT_FOR_C = YES
INPUT = ./src
RECURSIVE = YES
これだけ。
特に、最後の二つは致命的ですね。サブディレクトリが入力ファイルに扱われていなければ、そりゃ、出力結果が空になるはずです。
で、同じように実行。
Doxygenの実行は問題なく完了したんですが、latexディレクトリでのmake実行でエラーが発生。
エラーで停止してもEnterで先送りできる、という話を聞いたので、ひたすらEnterを押してみたんですが、結局のところ、PDFが出来ず。
何がダメだったんだろう、とネットで調べると、LaTexの処理に upLaTex を使用するのが良い、と。(もっとも、先に texlive-xxx をインストールしているので、ここでの実験結果はちょっと「やらせ」的な感じですけどね)
upLaTex を使用する
Doxyfileを追加で修正。
修正したのは次の2ヶ所だけです。
LATEX_CMD_NAME = uplatex
USE_PDFLATEX = NO
Doxygenの標準機能としてのPDF作成をやめて、upLaTex という処理を通すことになります。upLaTex は、日本語文字を扱うのに必要な感じですね。
これで実行してみます。
$ doxygen
$ cd latex
$ make
初期設定とは違い、これだけではPDFは生成されません。
$ dvips refman.dvi
これで、生成されたdviファイルをPDF化することができます。
出来ましたね。
本文もきちんと日本語で表示されています。
ただ、しおりが…。
本文がちゃんとした日本語表示だけに、残念です。
LaTexのMakefileを修正する
しおりの文字化けについては、参考にさせて頂いたサイトがあって、そこには、latex/Makefileを次のように修正する、とありました。
【修正前】
refman.pdf: refman.ps
ps2pdf refman.ps refman.pdf
【修正後】
refman.pdf: refman.dvi
dvipdfmx refman.dvi
これで、同じように実行してみます。Doxyfileは変えていませんので、makeから。
$ make
$ dvipdfmx refman.dvi
PDF化のコマンドが少し変わります。
これでどうでしょうか。
おや。しおりが消えちゃいました。
すみません。参考にしたサイトには続きがありました。
refman.texを修正する
肝心なのは、こちらですね。
LaTexで日本語のしおりを作る際には、pxjahyper を使用するのが良いらしい、と。
このため、latexディレクトリにある、refman.texファイルを修正します。
【修正前】
% Hyperlinks (required, but should be loaded last)
\usepackage{ifpdf}
\ifpdf
\usepackage[pdftex,pagebackref=true]{hyperref}
\else
\usepackage[ps2pdf,pagebackref=true]{hyperref}
\fi
\hypersetup{%
colorlinks=true,%
linkcolor=blue,%
citecolor=blue,%
unicode%
}
この部分。pdftexでの出力とするか、ps2pdfでの出力とするかの条件分岐がありますが、Makefileの方に修正を入れた通り、dvipdfmxにします。Doxygenの設定次第で条件分岐が必要となるのですが、pdftexの方は使用しないため、条件分岐も省略します(というか、参考にさせて頂いたサイトの通りです)。
【修正後】
% Hyperlinks (required, but should be loaded last)
\usepackage[dvipdfmx,
pagebackref=true,
colorlinks=true,
linkcolor=darkgray,
urlcolor=darkgray,
bookmarks
]{hyperref}
\usepackage{pxjahyper}
ここを書き換えずにMakeしてしまったので、dvipdfmxでは、しおりが生成されなかった、ということですね。今さらですが。
というわけで、実行します。今回もmakeから。
$ make
$ dvipdfmx refman.dvi
PDFが生成されました。
本文は先ほどと同じように日本語で出てます。
それに、しおりもきちんと日本語になりました。
やれやれ、一件落着。
と一息ついたのも束の間。
先頭からページを進めていくと、冒頭にある Contents のページは、
このように問題ないんですが、ファイル索引のページがこんなことに。
といっても、これは pxjahyper でも、dvipdfmx での問題でもなく、そもそもの Doxygen での生成時の問題のようです。
しおりに気をとられて気にしてなかったんですが、Doxyfileを
LATEX_CMD_NAME = uplatex
USE_PDFLATEX = NO
のように修正した時に生成されたPDFから、すでにファイル一覧のページ番号が ?? になっていました。
苦悩の末に…
ところが、です。
しおりの日本語化までは、ネット上で比較的たやすく情報を得られたのに、このページ番号部分の「??」化については、なかなか情報が見つからなかったんです。
しばらくいろいろと試行錯誤してみたんですが、どうも埒が明かず、「ファイル索引を非表示にしちゃおう」なんて、臭い物に蓋をするようなことまで考えたんですが、ある意味、それがきっかけで解決に近付きました。
latexディレクトリにある refman.texファイルの後半に、
%===== C O N T E N T S =====
\begin{document}
から始まる部分があります。ここからが、出力内容の記述になります。
で、少し後に、
%--- Begin generated contents ---
\chapter{ファイル索引}
\input{files}
\chapter{ファイル詳解}
\input{d0/d29/main_8c}
\input{da/d36/thread1_8c}
\input{df/db0/thread1_8h}
\input{db/d96/thread2_8c}
\input{d2/d38/thread2_8h}
%--- End generated contents ---
のようなものが出てきます。これは、「generated contents」とあるように、自動生成される領域になるため、Doxyfileの設定やソースコードによって変化します。
\chapter{ファイル索引}
が、
この部分でしょう。ということは、
この辺りは、次の
\input{files}
に当たるはず。
これは、C言語でヘッダファイルを include するように、さもそこにあるかのように取り込んでしまう処理です。
何を取り込むのかというと、filesに拡張子 tex を付けた files.tex です。
\section{ファイル一覧}
詳解が付けられているファイルの一覧です。\begin{DoxyCompactList}
\item\contentsline{section}{src/\hyperlink{main_8c}{main.\+c} \\*メインプログラムファイル。スレッドを作成および管理します。 }{\pageref{d0/d29/main_8c}}{}
\item\contentsline{section}{src/\hyperlink{thread1_8c}{thread1.\+c} \\*スレッド1の実装 }{\pageref{da/d36/thread1_8c}}{}
\item\contentsline{section}{src/\hyperlink{thread1_8h}{thread1.\+h} \\*スレッド1のヘッダーファイル }{\pageref{df/db0/thread1_8h}}{}
\item\contentsline{section}{src/\hyperlink{thread2_8c}{thread2.\+c} \\*スレッド2の実装 }{\pageref{db/d96/thread2_8c}}{}
\item\contentsline{section}{src/\hyperlink{thread2_8h}{thread2.\+h} \\*スレッド2のヘッダーファイル }{\pageref{d2/d38/thread2_8h}}{}
\end{DoxyCompactList}
「詳解が付けられているファイルの一覧です。」なんて出力されている文言が入っているので、間違いなしですね。
ひとつの項目(例えば main.c)は、
\item\contentsline{section}{src/\hyperlink{main_8c}{main.\+c} \\*メインプログラムファイル。スレッドを作成および管理します。 }{\pageref{d0/d29/main_8c}}{}
このような1行で構成されています。
この中の hyperlink と pageref が怪しいですね。表示順からすると、pageref ですかね。
\pageref{目印名} とすることで、そこに、目印名のページ番号が入るはず…なんですが。
この例でいうと、/latex/d0/d29/ディレクトリには、main_8cが実際に存在していますし、ページ番号が付与されてもおかしくないんですが、なぜか ?? です。
じゃぁ、d0/d29/main_8c の指定がおかしいんじゃない?と、いろいろ試行錯誤して、
\hyperlink{main_8c} ⇒ \hyperlink{d0/d29/main_8c}
\pageref{d0/d29/main_8c} ⇒ \pageref{main_8c}
なんて置き換えてみます。
\item\contentsline{section}{src/\hyperlink{d0/d29/main_8c}{main.\+c} \\*メインプログラムファイル。スレッドを作成および管理します。 }{\pageref{main_8c}}{}
全行を同じように書き換えて、make → dvipdfmx refman.dvi を実行すると…。
え?ほんとに?
ページ番号が表示されました。
ページ番号をクリックすると、きちんと、そのページにジャンプしてくれます。
Doxygen が生成する files.tex が、そのままでは使えなかったとは、思いもしなかったですね。
これで、完成です。
次の項目でも書いていますが、 hyperlink{ へのディレクトリ追記は不要だったようです。
削除して確認したところ、ページ番号が表示されることを確認しました。
情報が少ない理由
それにしても、なぜネット上にこの手の情報が無いのかと思っていたんですが、別PCで動いているUbuntu 22.04 で同じように試してみると、インストールされた Doxygen のバージョンが 1.9.1。
これだと、files.tex には
{\pageref{main_8c}}{}
と、ディレクトリが付きません。hyperlinkの方も
\hyperlink{main_8c}{main.\+c}
とディレクトリが付かないので、共に不要だったのかもしれないですね。
そんなわけで、Doxygenのバグだったようです。
これですかね。
In a pagref the path should not be present.
https://github.com/doxygen/doxygen/commit/efd49dacfbae1ad55d7922a748e2c1d60068b014
「pagrefにパスは要らない」って、まさにこれっぽいです。
8年も前の2016年5月9日にリリースされた Doxygen 1.8.12 で改修されているようですから、恐ろしくピンポイントな現象ってことですね。Ubuntu 16.04(+Doxygen 1.8.11)でやむなく作業をされている方々のため「だけ」の情報展開となったようです。