自動化のためのGNU Make入門講座 - Makefileの基本:ルール
自動化のためのGNU Make入門講座 -
Makefileの基本:ルール
ここでは,メイクファイルの基本的な書き方としてルールの説明をしましょう.ここからは,Makefileといえばmakeが参照するメイクファイルのことを指すものとします.
コメントと改行
その前に少しコメントの書き方について解説しておきます.Makefileでは,#
から行末までがコメントです.例えば,
# # Hello Worldを出力する # all: @echo Hello World!
のように使います.
また,makeは基本的に行指向です.見やすくするために改行したい場合はバックスラッシュ\
を使って改行を無視させることができます.例えば,マクロの定義(後述)で
object_files = \ foo.o \ bar.o \ baz.o
と書けば,makeは改行を無視して次のように解釈します.
object_files = foo.o bar.o baz.o
ここで注意することは,foo.oだけをコメントアウトしたいために
object_files = \
# foo.o \
bar.o \
baz.o
と書いてもダメだということです.これは,makeが
object_files = bar.o baz.o
と解釈してエラーになってしまうためです.不便なので,なんとかならないでしょうかね(nmakeでは都合よく解釈してくれます).
ルールとターゲット
さて,本題のルールについて説明しましょう.Makefileは,基本的に次のような構造になっています.
[作りたいファイル名]: [そのファイルを作るためのコマンド行]
この一かたまりをルールと呼びます.ルールの中にある[作りたいファイル名]のことをターゲットと呼びます.[そのファイルを作るためのコマンド行]の手前には,タブ1文字を入れることに注意してください.例えば,"Hello World!"という文字が入ったhello.txtというファイルを作るMakefileは次のようになります.
hello.txt: echo Hello World > hello.txt
また,[そのファイルを作るためのコマンド行] は複数行書きこんでもかまいません.hello.txt というファイルを,HelloとWorldの2行からなるテキストファイルとして作成する場合は次のようにします.
hello.txt: echo Hello > hello.txt echo World >> hello.txt
このとき,2行目以降にも行頭にタブ1文字いれることに注意してください.makeは最初にタブ文字がある行をコマンド行と解釈するようになっています.
タスク
ターゲットとして,実際に存在しないファイル名を指定することができます.これは, 擬似ターゲットと呼んだり,ダミーターゲットと呼ばれたりしますが,統一された用語はないようです.そこで,ここでは便宜上タスクまたはタスクターゲットと呼ぶことにします(これと対比させるため,ファイルをファイルターゲットと呼ぶことにしましょう).タスクターゲットは,ある特定のファイルを作るためではなく,作業を行うコマンドとして利用したい場合に用いられます.
.PHONY: [実行したいタスク名] [実行したいタスク名]: [そのタスクを行うためのコマンド行]
.PHONYは,タスクターゲットを宣言するためのターゲットです(phony: 偽の,まやかしの).また,[そのタスクを行うためのコマンド行]の手前にもタブ1文字を入れることに注意してください.
タスクターゲットの例として,そのディレクトリの.classの拡張子をもつファイルをすべて削除するcleanターゲットを書いてみましょう.
.PHONY: clean clean: rm -f *.class
こうすると,「make clean」ですべてのclassファイルが削除されます.
ところで,タスクターゲットは.PHONYターゲットを使わず作ることもできます.
clean: rm -f *.class
しかし,もしタスク名と同名のファイルやディレクトリがあると混乱しますので,.PHONYは積極的に書くようにしましょう.
自分でルールを書いて確かめよう
さて,これから書くことは,実際にMakefileを書いて確かめるのが一番いいと思います.もしファイルターゲットを作りたいときは,
targetA: touch targetA
のようにtouchコマンドを使うと便利です.touchコマンドは,ファイルが存在しない場合引数と同名の空のファイルを作り,存在している場合はファイルの更新日付を今の時刻に変更するコマンドです.
タスクターゲットは,echoコマンドを使えば実験できます.
.PHONY: targetA targetA: echo run targetA
このように,自分でいろいろなルールを書いて試してみてください.
複数のルール
Makefileには,複数のルールを含めてかまいません.
hello.txt: echo Hello World > hello.txt .PHONY: clean clean: rm -f *.class
複数のルールを書いた場合には,makeを実行するときにターゲット名を指定するようにします.指定しなかった場合は,一番最初に書かれたルール(上の例ではターゲットがhello.txtのルール)が実行されます.したがって,このMakefileでcleanターゲットを実行するには,
$ make clean
とすればOKです.hello.txtの場合は,
$ make hello.txt
または
$ make
ということになります.さらに,cleanとhello.txtを同時に行いたい場合は,
$ make clean hello.txt
と複数のターゲットを指定します.
依存ターゲット
ここで改めてMakefileの構造をみるためにDocument Object Modelを書いてみましょう.
Makefile は複数のルールを持っています.ルールは,ターゲットとコマンド行からなります.個々のターゲットは実際のファイルまたはタスクです.
ここで,ターゲットの部分をもう少し詳しく見てみましょう.
このように,各々のターゲットは複数の依存ターゲットを持っている場合があります.
依存ターゲットとは,そのターゲットを作るために必要なターゲット(部品)のことです.targetAというターゲットを作るためにtargetBという部品が必要な場合,Makefileを次のように書きます.
targetA: targetB [targetAのコマンド行]
これは「targetAの依存ターゲットがtargetBである」ということを表しています.
依存ターゲットがあれば,makeはターゲットの更新日時を比較してコマンド行を実行すべきかどうか判断します.つまり,依存しているものより更新日時が古ければ,コマンド行を実行してターゲットを最新にしようとするわけです.例えば,
targetA:targetB [targetAのコマンド行] targetB: [targetBのコマンド行]
というMakefileで
$ make targetA
を実行するとどうなるか順に見てみましょう.
依存ターゲットtargetBを作成(=[targetBのコマンド行]を実行)しようとします.ただし,次の条件が成り立つときは何もしません.
- targetBがファイルターゲットかつ
- targetBという名前のファイルがすでに存在している場合
ターゲットtargetAを作成(=[targetAのコマンド行]を実行)しようとします.ただし,次の条件が成り立つときは何もしません.
- targetAとtargetBがファイルターゲットかつ
- targetAとtargetBという名前のファイルが存在しており,
- targetAの更新日付がtargetBの更新日付より新しい場合
ポイントとなるのは,ファイルターゲットの場合実際のファイルの更新日時を比較している,ということです.もし更新日時が依存ターゲットより新しいならば,makeは何しません.ファイルやタスクの依存関係を記述し,適切な順番でコマンドを実行できるところがmakeの優れたところです.
依存ターゲットが複数ある場合は,
targetA: targetB targetC [targetAのコマンド行]
という風に書きます.このときtargetAの依存ターゲットはtargetBとtargetCの2つです.
いろいろなルールの書き方
同じターゲットを複数のルールに分けて書く
ルールにはいろいろな書き方があります.例えば,依存ターゲットを指定するために,ルールを分けて書くことができます.
targetA: targetB targetA: targetC [コマンド行]
このとき,最初の「targetA: targetB」はコマンド行を持たない依存関係のみを表すルールと考えることができます.しかし,このように同じターゲットを複数のルールに分けて書くときは,コマンド行の書くルールをどれか一つにしなくてはなりません.例えば,
targetA: targetB [コマンド行1] targetA: targetC [コマンド行2]
と書くとmakeはワーニングを出しどちらか一方のコマンド行しか実行しません.次のような特殊な書き方をすればコマンド行を複数に分けることもできますが,あまり使うことはないでしょう(:
の代わりに::
を使う).
targetA:: targetB [コマンド行1] targetA:: targetC [コマンド行2]
コマンド行がないターゲット
また,コマンド行を全くもたないターゲットを書くこともできます.これは,複数のターゲットをまとめて1つのターゲットとして扱いたい場合に用いられます.例えば,
.PHONY: rebuild rebuild: clean build ;
と書いた場合,cleanとbuildを順に行なう,という意味になります.ここで,セミコロン;
は依存ターゲットとコマンド行のセパレータの意味を持っています(つまり「改行+タブ文字」の代わり).上のルールは,
.PHONY: rebuild
rebuild: clean build
#←この行はタブ文字+空文字列でコマンド行がないことを表す
と書き直すこともできますが,分かりにくいのでセミコロンを使います[1].
もっとも簡単なルールは,依存ターゲットもコマンド行ももたないターゲットのみのルールでしょう.
.PHONY: all all: ;
このルールは,文字通り何もしません.もしこれがMakefileの先頭に書いてあれば,ユーザが「make」としても何も実行されないことになります.適切なデフォルトターゲットがない場合,これによってユーザの誤操作を防ぐことができます.このようなコマンド行を持たないターゲットは,通常タスクターゲットです.
複数のターゲットを持つルール
一つのルールで複数のターゲットを指定することもできます.例えば,
targetA targetB: targetC [コマンド行]
と書いた場合,次の2つのルールがあるのと等価です.
targetA: targetC [コマンド行] targetB: targetC [コマンド行]
これも場合によっては便利なときがあります.
[1] もっとも,あるターゲットのルールすべてにコマンド行が書いていなければ「コマンド行をもたないターゲット」ということになります.明示的にコマンド行が持たないことを表したい場合以外は特にセミコロンをつけなくてもOKです.