百日半狂乱

Shut the fuck up and write some code!!

makeを使ったライブラリのビルド管理と暗黙ルールデータベースの話

CでもC++でも良いんだけど、コードが大きくなってくるとファイルの分割を考え始める.

追記(204/1/30):このままコンパイラをg++にするとコンパイルが通らなかった.また、-Wallオプションを付けないというとんでもないことをしている.その辺のことをこちらの記事に書いたので併せて参照をば.

例えば以下のようなことをしたいと考えたとする.

  1. ライブラリとなるソースコード(hello.c)をコンパイルして、arコマンドで静的ライブラリ(hello.a)を作る.
  2. ライブラリを呼び出すソースコード(run.c)のコンパイル時にライブラリをリンクして実行ファイルを生成する.

これらを手でやろうと思うとこれだけでもう大変なのでmakeを使う.

makeでライブラリのビルドを管理する(サンプル)

これでおしまいにしようかと思ったけど、なんでこれで上手く行くのか毎回忘れるので簡単にまとめることにした.

今回はこのサンプルを使って、暗黙ルールデータベースを見てみる.

追記(2014/1/24):libhello.a: libhello.a(hello.o)というのは決して一般的な記法ではなく、アーカイブファイル用の記法なので注意(こちらを参照).

実行されるコマンドの確認

ところでmakeは-nオプション( --just-print, --dry-run, --recon)を使うと、今makeを叩いたらどんなコマンドが実行されるのか確認することができる.

印字されるコマンドは実際に実行されるわけではない.

例えば上記のコードを落としてきて、そのままmake -nを叩くと以下のようになると思う*1

$ make -n
gcc    -c -o hello.o hello.c
ar rv libhello.a hello.o
gcc     run.c libhello.a   -o run

しかし、上記Makefileにはgccやarをどのように実行するかなどを示すコードは一切登場しない.

これらはソースコードの依存関係を解決しながら暗黙ルールデータベースに従って実行される.

暗黙ルールデータベースを確認する

暗黙ルールデータベースは-p(--print-data-base)オプションで確認することができる*2

データベースを見るといってもルールの一覧がそのままターミナルに印字されるだけなので、ビルドインの暗黙ルールが大量に印字される.lessとかmoreで見ると良い.

$ make -p -f/dev/nul | less

関係ありそうなところを見てみる

例えばhello.oはどのようなルールによってコンパイルされるのだろうか.自分の環境ではご丁寧に以下のような情報がデータベース上にあった.

hello.o: hello.c hello.h
#  Implicit rule search has been done.
#  Implicit/static pattern stem: `hello'
#  Last modified 2014-01-21 02:52:15.9335139
#  File has been updated.
#  Successfully updated.
# variable set hash-table stats:
# Load=0/32=0%, Rehash=0, Collisions=0/0=0%
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<

どうやらbuilt-inのルールである$(COMPILE.c) $(OUTPUT_OPTION) $<が実行されるようだ.

"COMPILE.c"で検索*3をかけるとどうもコイツが正体のようだ.

%.o: %.c
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<

こいつが何を意味しているのか、簡単にmakeのルールについて確認する.

makeのルール

makeにおけるルールはターゲット(target)、必須項目(prereq)、実行コマンド(commands)の3つの部分から構成されており、

target: prereq1 prereq2
        commands

のような形で記述される.

makeは必須項目がすでに存在する場合は、ターゲットを作成するためにコマンドを実行する.

もし、必須項目が存在しない場合(もしくはファイルのタイムスタンプがターゲットよりも新しければ)は、makeは先に必須項目をターゲットとするルールを探す.

全てのルールは暗黙ルールデータベースの中にある

話を%.o:%.cのルールの話に戻すと、makeはある.oファイルが必要な場面になれば、必須項目である.cファイルの存在とタイムスタンプを確かめて必要ならばビルドインのコマンドを実行する.

このファイル間の依存関係が重要で、-nオプションで確認した複数のコマンドがあの順番で実行されるのも、依存関係に従ってあの順番でコンパイルしないと、当たり前だけど目的の実行ファイルを作成できないからだ.

makeが何をしているのかわからなくなったら、ターゲットファイルから必須項目に向かって順に降りていくように暗黙ルールデータベースを探索してみると良い.

実行ファイルを作成したい → ライブラリが必要 → ライブラリに含めるオブジェクトファイルが必要 → オブジェクトファイルの元となるソースコードが必要

という依存関係がmake -nの結果から見えてくるようになる.

makeが上手く動かない場合は、大体はこの依存関係の設定に矛盾があるからだろうと思う.

他にも色々端折ってるけどキリがないので、詳しくはGNU Make 第3版をば*4

GNU Make 第3版

GNU Make 第3版

暗黙ルールデータベースという言葉もこの本から拝借.

ちなみに、このデータベースに載っているビルドインのルールはMakefileに別途記述することで上書きすることができる.

そうなるともうビルドインという文字はなくなって、自分で定義したルールに置き換わっていることがmake -pで確認できるはず.

「暗黙ルールデータベースというデフォルト設定があって、Makefileでそれをカスタムする」と考えると個人的にはMakefileに無駄な記述が減って考え方もすっきりする.

最初は壮絶に苦痛だったけどわかってくるとmake面白い.今時感ゼロだけど.

二十五日半狂乱、8日目(の分)の記事

*1:ちなみにビルド後にmake -nを叩くと、もう何にもすることねーぞ!と言われる.その状態でtouch hello.cとしてもう一回make -nを叩くとまた出力が変わる

*2:ただmake -pだけではデータベースを印字する前にビルドしてしまうので、ビルドしたくなければmake -p -f/dev/nulとすればデータベースの印字だけを行うことができる

*3:ビューアーにlessやmoreを使ったんなら/で検索

*4:この本、第6章にそこまでの総括的なMakefileがあるので、個人的には先に6章見た方が4,5章飽きずに読めると思います.