makeは、makefileというファイルを作成し、makeコマンドを実行することで自動的にファイルを生成できます。makeは主にプログラムのビルド作業に利用されます。また、この記事で利用しているmakeはGNU makeになります。
makefileでは、生成するファイル(target)と生成するのに前提となるファイル(prerequisites)の関係を記述し、その後に生成のためのコマンド(recipe)を記述します。
makeは、targetのファイルが存在しない場合又は、targetのファイルとprerequisitesのファイルの更新日時を確認し、prerequisitesのファイルの方が新しい場合、targetのファイルを生成します。この機能により、例えば、プログラムをビルドする場合、必要なプログラムのみをビルドできます。
目次
makefileの構文
makefileのルールの基本的な定義
1 2 3 4 |
target … : prerequisites … recipe … … |
target(ターゲット):生成するファイル
prerequisites(前提条件):ファイルを生成するのに前提となるファイル(依存ファイル)
recipe:ファイル生成で実行するコマンド
makeコマンドのオプションについて
makeコマンドのオプションについては別記事で紹介しています。
簡単なmakeの例
ここでは、単純なテキストファイルを用いてmakeを利用します。
makefileを作成し、makeコマンドを実行するとカレントディレクトリにあるmakefileを読み取り処理が行われます。
ここでの処理は、依存ファイルprerequisites.txtからターゲットファイルtarget.txtを作成します。この生成は単純にcatコマンドを利用して、prerequisites.txtの内容をtarget.txtへリダイレクトします。
以下はmakefileとprerequisites.txtの内容、そして、コマンド例と実行結果になります。
makefile
1 2 |
target.txt : prerequisites.txt cat prerequisites.txt > target.txt |
prerequisites.txt
1 2 |
prerequisites.txt content test make |
コマンド例と実行結果
1 2 3 4 5 6 7 8 |
$ls makefile prerequisites.txt $ $make cat prerequisites.txt > target.txt $ $ls makefile prerequisites.txt target.txt |
ここでもう一度、makeコマンドを実行すると以下のようになります。
1 2 |
$make make: 'target.txt' is up to date. |
これは、target.txtのタイムスタンプがprerequisites.txtのタイムスタンプより新しいため何も処理が行われません。また、タイムスタンプは以下のような状態になっています。
1 2 3 |
$ls -go --full-time prerequisites.txt target.txt -rw-rw-r-- 1 36 2018-04-14 13:29:44.296196999 +0900 prerequisites.txt -rw-rw-r-- 1 36 2018-04-14 13:29:48.710402999 +0900 target.txt |
ここで、touchコマンドを用いて、prerequistes.txtのタイムスタンプを更新し、prerequisites.txtのタイムスタンプがtarget.txtのタイムスタンプより新しい場合にmakeコマンドを実行すると、makeによってtarget.txtが再生成できます。
1 2 3 4 5 6 7 |
$touch prerequisites.txt $ls -go --full-time prerequisites.txt target.txt -rw-rw-r-- 1 36 2018-04-14 13:39:53.340567000 +0900 prerequisites.txt -rw-rw-r-- 1 36 2018-04-14 13:29:48.710402999 +0900 target.txt $ $make cat prerequisites.txt > target.txt |
makeコマンドを引数なしで実行した場合、一番上のターゲットから始まります。このmakeが処理を始めるターゲットをデフォルトゴール(default goal)といいます。
デフォルトゴール以外のターゲットから始めたい場合は、makeコマンドの引数にターゲットを指定します。
ゴールになっているターゲットの処理が終了したら、makeの処理は終了します。なので、cleanがターゲットになっているルールは処理されません。
makefile
1 2 3 4 |
target.txt : prerequisites.txt cat prerequisites.txt > target.txt clean : rm target.txt |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 |
$ls makefile prerequisites.txt $ $make cat prerequisites.txt > target.txt $ls makefile prerequisites.txt target.txt $ $make clean rm target.txt $ls makefile prerequisites.txt |
makeのターゲットについて
Phony Target(偽のターゲット)
makefileのターゲットはファイル名ではない名前を利用することができます。このようなターゲットはPhony Targetといいます。
Phony Targetは組み込みターゲット名.PHONYを利用します。.PHONYで宣言したPhony Targetは同じ名前のファイルが存在しても実行することができます。
makefile
1 2 3 4 5 |
target.txt : prerequisites.txt cat prerequisites.txt > target.txt .PHONY : clean clean : rm target.txt |
コマンド例と実行結果
1 2 3 4 |
$ls clean makefile prerequisites.txt target.txt $make clean rm target.txt |
また、よく使うPhony Targetは、Arguments to Specify the Goalsにリストされています。
ターゲットの依存ファイルの処理について
makeの処理は基本的にデフォルトゴールから始まります。
ゴールに指定されたターゲットに依存ファイルが存在するとき、makeは基本的にファイルの更新日時のチェックを行います。
しかし、その依存ファイルをターゲットとしたルールが存在する場合は、まずそのルールを処理していきます。
依存ファイルをターゲットとしたルールの処理が終了した後に、依存ファイルと元のターゲットファイルの更新日時をチェックを行いルールを適用します。
また、依存ファイルをターゲットとしてルールの処理を行った結果、そのターゲット(依存ファイル)が作成されなかった場合、元のターゲットのルールのコマンドは実行されます。
以下はmakefileのみを作成し、実行した結果になります。
makefile
1 2 3 4 5 6 7 8 9 10 11 12 |
target.txt : file1.txt #target target.txt file1.txt : file2.txt touch file1.txt file2.txt : last touch file2.txt .PHONY : last last : #target last |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ls makefile $make #target last touch file2.txt touch file1.txt #target target.txt $ $ls file1.txt file2.txt makefile $make #target last touch file2.txt touch file1.txt #target target.txt |
Phony Targetを依存ファイルとしている場合、上の例で2回目のmakeを行うとき、依存ファイルをターゲットとしたルールを適用した結果、ターゲットファイルが存在している場合でも、そのターゲットに対するルールのコマンドは実行されていきます(file2.txt : lastの行)。その結果、makeのためのすべてのコマンドが実行されてしまいます。
しかし、ターゲットファイルが存在するときに、ターゲットへのコマンドは実行したくないけれど、依存ファイルのルールは処理したい場合はorder-only prerequisitesが利用できます。order-only prerequisitesは通常のprerequisitesの後に'|'を利用します。
makefile
1 2 3 4 5 6 7 8 9 10 11 12 |
target.txt : file1.txt #target target.txt file1.txt : file2.txt touch file1.txt file2.txt : | last touch file2.txt .PHONY : last last : #target last |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 |
$ls makefile $make #target last touch file2.txt touch file1.txt #target target.txt $ls file1.txt file2.txt makefile $make #target last #target target.txt |
複数のターゲットがあるルール
以下のような複数のターゲットがあるルールは
1 2 |
target1.txt target2.txt : echo $@ |
単純に、
1 2 3 4 |
target1.txt : echo $@ target2.txt : echo $@ |
と同じ意味になります。
一つのターゲットに対しての複数のルールの設定について
一つのターゲットに対して、複数のルールを設定することで、依存ファイルを追加することができます。
makefile
1 2 3 4 5 6 7 8 9 10 11 |
all : program program : prog.o touch program prog.o : prog.c #dependency: $^ touch prog.o prog.o : header1.h prog.o : header2.h |
コマンド例と実行結果
1 2 3 4 5 6 |
$ls header1.h header2.h makefile prog.c $make #dependency: prog.c header1.h header2.h touch prog.o touch program |
しかし、ターゲットに対して実行されるコマンド(recipes)は一つになります。複数のルールに対して、それぞれ違うrecipesを設定するとエラーメッセージを表示しています。
makefile
1 2 3 4 5 6 7 8 |
all : test .PHONY : test test : file1.txt #file1.txt test : file2.txt #file2.txt |
コマンド例と実行結果
1 2 3 4 5 6 |
$ls file1.txt file2.txt makefile $make makefile:8: warning: overriding recipe for target 'test' makefile:5: warning: ignoring old recipe for target 'test' #file2.txt |
ここでは、下に記述している方のコマンドが前のレシピを上書きしています。
複数のルールに対して、それぞれ違うrecipesを設定したい場合は、'::'を使用します。
makefile
1 2 3 4 5 6 7 8 |
all : test .PHONY : test test :: file1.txt #file1.txt | dependency: $^ test :: file2.txt #file2.txt | dependency: $^ |
コマンド例と実行結果
1 2 3 4 5 |
$ls file1.txt file2.txt makefile $make #file1.txt | dependency: file1.txt #file2.txt | dependency: file2.txt |
暗黙的なルール
拡張子によって、処理が決まっている場合などは暗黙的なルール(implicit rules)を設定することで、makefileの記述を簡潔にすることができます。
暗黙的なルールの種類として、パターンルールとサフィックスルールがあります。
パターンルール(pattern rules)
ターゲットや依存ファイルに対して、%の文字を利用することで、パターンルールによる暗黙的なルールを設定できます。
makefile
1 2 3 4 5 6 7 8 |
%.o : %.c touch $@ %.c : touch $@ all : prog1.o prog2.o prog3.o |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 |
$ls makefile $make touch prog1.c touch prog1.o touch prog2.c touch prog2.o touch prog3.c touch prog3.o rm prog2.c prog3.c prog1.c |
上の例では、.oのファイルを作成するために.cのファイルを作成するような、暗黙的なルールの後に暗黙的なルールを処理する部分があります。これは暗黙的なルールの連鎖(chains of implicit rules)になり、実行結果の最後にmakefileで記述のないファイルの削除が行われます。
削除されたファイルは中間ファイル(intermediate file)といい、この場合では.cのファイルが当てはまります。中間ファイルが削除される条件は、始めに中間ファイルが存在せず、暗黙的なルールの連鎖でmakeがその中間ファイルを作成したときになります。
また、より限定的なパターンルールとして利用できるルールとして、静的パターンルール(static pattern rules)もあります。静的パターンルールは暗黙的なルールではありませんが、特定なファイルに対して、暗黙的なルールを上書きするようなルールを記述したい場合に利用できます。
これは、パターンルールの始めにターゲットを記述し、その記述したターゲットの中でのパターンルールを適用できます。
makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
%.o : %.c touch $@ %.c : touch $@ %.txt : touch $@ .PHONY : all all : prog1.o prog2.o prog3.o prog1.c prog2.c : %.c : %.txt cp $*.txt $*.c |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 |
$ls makefile $make touch prog1.txt cp prog1.txt prog1.c touch prog1.o touch prog2.txt cp prog2.txt prog2.c touch prog2.o touch prog3.c touch prog3.o rm prog3.c |
サフィックスルール(suffix rules)
サフィックスルールは暗黙的なルールの古い形式になります。サフィックスルールには2種類あり、double-suffixとsingle-suffixになります。
また、サフィックスルールを使用するには、組み込みのターゲット名.SUFFIXESに使用する接尾辞を追加する必要があります。
double-suffixは、接尾辞を2つ指定し並べたものになります。例えば、
1 |
.c.o : |
はパターンルールの
1 |
%.o : %.c |
と同じになります。
makefile
1 2 3 4 5 6 7 8 |
.c.o: touch $@ %.c: touch $@ .PHONY : all all : prog1.o prog2.o prog3.o |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 |
$ls makefile $make touch prog1.c touch prog1.o touch prog2.c touch prog2.o touch prog3.c touch prog3.o rm prog2.c prog3.c prog1.c |
single-suffixは、接尾辞を1つだけ指定したものになります。例えば、
1 |
.txt : |
はパターンルールの
1 |
% : %.txt |
と同じになります。
makefile
1 2 3 4 5 6 |
.SUFFIXES: .txt .txt : cp $< $@ .PHONY : all all : test |
コマンド例と実行結果
1 2 3 4 |
$ls makefile test.txt $make cp test.txt test |
サフィックスルールについて、makeはデフォルトのサフィックスルールが設定されている場合があります。意図しない接尾辞が処理されないようにするには
1 2 |
.SUFFIXES : .SUFFIXES : .txt |
のように一度デフォルトの接尾辞を削除した後に、.txtのような必要な接尾辞を設定します。
デフォルトのサフィックスルールを確認するには、makefileが存在しないディレクトで、
1 |
make -p |
で確認できます。これはルールや変数の設定があるmakeのデータベースを確認するオプションになります。
使用するmakefileのmakeのデータベースだけを確認する場合は処理を実行しないdry-runモードの-nオプションをつける
1 |
make -np |
や、またはコマンドを何も表示や実行しないで終了ステータスのみを返す-qオプションをつける
1 |
make -qp |
でmakeのデータベースを確認できます。-nオプションや-qオプションがない場合はmakeの処理が実行された後に、makeのデータベースが表示されます。
また、デフォルトのサフィックスルールのドキュメントはGNU makeの場合、Catalogue of Rulesに、デフォルトのサフィックスルールに使用する変数はImplicit Variablesになります。
中間ファイル
中間ファイルを生成したとき、暗黙的なルールの連鎖で作成した中間ファイルを削除したくない場合があります。その場合は、組み込みのターゲット名.SECONDARYを利用すると中間ファイルは削除されません。
makefile
1 2 3 4 5 6 7 8 9 10 |
%.o : %.c touch $@ %.c : touch $@ .PHONY : all all : prog1.o prog2.o prog3.o .SECONDARY: prog1.c prog2.c prog3.c |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 |
$ls makefile $make touch prog1.c touch prog1.o touch prog2.c touch prog2.o touch prog3.c touch prog3.o $ls makefile prog1.c prog1.o prog2.c prog2.o prog3.c prog3.o |
逆に中間ファイルとして削除したいファイルは組み込みのターゲット名.INTERMEDIATEが利用できます。
makefile
1 2 3 4 5 6 7 8 9 10 |
%.o : %.c touch $@ %.c : touch $@ .PHONY : all all : prog1.o prog2.o prog3.o .INTERMEDIATE: prog1.o prog2.o prog3.o |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 |
$ls makefile $make touch prog1.c touch prog1.o touch prog2.c touch prog2.o touch prog3.c touch prog3.o rm prog2.c prog3.o prog3.c prog1.o prog2.o prog1.c $ls makefile |
また、.SECONDARYではなく、.PRECIOUSを利用しても中間ファイルは残ります。
変数
変数の代入
makeでの変数の主な代入演算子は、'='もしくは、':='(または'::=')があります。また、変数の呼び出しは$(var)や${var}のように呼び出すことができます。
変数に対して、'='を用いた代入は再帰的に変数を展開できます。また、代入演算子の周りにあるスペースは無視されます。
makefile
1 2 3 4 5 |
a = $(b) b = $(c) c = hello all : ; #a:$(a) |
コマンド例と実行結果
1 2 3 4 |
$ls makefile $make #a:hello |
makeでターゲットとコマンドの行を一行で記述したい場合は、
1 |
targets : prerequisites ; recipe |
のように';'の記号が利用できます。
また、':='(または':==')の場合はその時点の変数を単純に展開します。
makefile
1 2 3 4 5 6 7 |
a = hello b := $(a) a = world all : #a:$(a) #b:$(b) |
コマンド例と実行結果
1 2 3 4 5 |
$ls makefile $make #a:world #b:hello |
代入演算子を両方利用してみると、違いがさらに分かると思います。
makefile
1 2 3 4 5 6 7 8 9 10 |
obj = a.o b.o c.o obj_e = $(obj) obj_ce := $(obj) obj = d.o e.o f.o .PHONY: all all : #obj:$(obj) #obj_e:$(obj_e) #obj_ce:$(obj_ce) |
コマンド例と実行結果
1 2 3 4 5 6 |
$ls makefile $make #obj:d.o e.o f.o #obj_e:d.o e.o f.o #obj_ce:a.o b.o c.o |
他の代入演算子
変数が設定されていない変数のみに代入したい場合は'?='が利用できます。
makefile
1 2 3 4 5 6 7 8 |
a = hello a ?= world b ?= world all : #a:$(a) #b:$(b) |
コマンド例と実行結果
1 2 3 4 5 |
$ls makefile $make #a:hello #b:world |
シェルでのコマンド結果を代入したい場合は'!='が利用できます。
makefile
1 2 3 4 5 6 |
a != printf "%d" 10 b != ls all : #a:$(a) #b:$(b) |
コマンド例と実行結果
1 2 3 4 5 |
$ls makefile $make #a:10 #b:makefile |
変数の文字列を追加したい場合は'+='が利用できます。
makefile
1 2 3 4 |
a = hello a += world all : ; #a:$(a) |
コマンド例と実行結果
1 2 3 4 |
$ls makefile $make #a:hello world |
'+='の演算子を利用した代入
1 2 |
a := hello a += world |
は
1 2 |
a := hello a := $(a) world |
に書き換えることもできます。ただし、最初に=を使用した代入の場合は、変数は再帰的な展開がされる変数として扱われます。
複数行の代入にはdefineが利用できます。defineは、defineのキーワードの後に変数と代入演算子を指定し、そのあとの行に代入したい行を記述し、最後にendefのキーワードで終了します。
makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 |
define a = #aaa #bbb endef define b := #ccc #ddd endef all : $(a) $(b) |
コマンド例と実行結果
1 2 3 4 5 6 7 |
$ls makefile $make #aaa #bbb #ccc #ddd |
make特有の変数
make特有の変数は以下の表になります。主に暗黙的なルールを利用する際に便利な変数になります。
make特有の変数の表
変数 | 意味 |
$@ | ターゲットのファイル名 |
$% | アーカイブファイルのメンバー名 |
$< | 最初の依存ファイル名 |
$? | ターゲットより新しい全ての依存ファイル |
$^ | 全ての依存ファイル(スペース区切り) ただし、order-only prerequisitesは除く |
$+ | 全ての依存ファイル ただし、繰り返されるファイル名もリスト通り表示 |
$| | 全てのorder-only prerequisites(順序付けの依存関係)の名前 |
$* | パターンルールなどで%の文字で パターンマッチした文字列(stem,ステム) |
$(@D) | $@でのディレクトリ部分 |
$(@F) | $@でのファイル部分 |
$(*D) $(*F) |
$*でのディレクトリ部分とファイル部分 |
$(%D) $(%F) |
$%でのディレクトリ部分とファイル部分 |
$(<D) $(<F) |
$<でのディレクトリ部分とファイル部分 |
$(^D) $(^F) |
$^でのディレクトリ部分とファイル部分 |
$(+D) $(+F) |
$+でのディレクトリ部分とファイル部分 |
$(?D) $(?F) |
$?でのディレクトリ部分とファイル部分 |
また、下の例は、上の変数を利用したmakeの使用例になります。
makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
%.log : ; %.txt : a.log dir1/b.log dir1/dir1-1/c.log | %.log # $$@: $@ # $$%: $% # $$<: $< # $$?: $? # $$^: $^ # $$+: $+ # $$|: $| # $$*: $* # $$(@D): $(@D) # $$(@F): $(@F) # $$(*D): $(*D) # $$(*F): $(*F) # $$(<D): $(<D) # $$(<F): $(<F) # $$(^D): $(^D) # $$(^F): $(^F) # $$(+D): $(+D) # $$(+F): $(+F) # $$(?D): $(?D) # $$(?F): $(?F) all : target.txt |
コマンド例と実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ls makefile $make # $@: target.txt # $%: # $<: a.log # $?: a.log dir1/b.log dir1/dir1-1/c.log # $^: a.log dir1/b.log dir1/dir1-1/c.log # $+: a.log dir1/b.log dir1/dir1-1/c.log # $|: target.log # $*: target # $(@D): . # $(@F): target.txt # $(*D): . # $(*F): target # $(<D): . # $(<F): a.log # $(^D): . dir1 dir1/dir1-1 # $(^F): a.log b.log c.log # $(+D): . dir1 dir1/dir1-1 # $(+F): a.log b.log c.log # $(?D): . dir1 dir1/dir1-1 # $(?F): a.log b.log c.log |