超初歩的なmakeまとめ

これまでGNU環境で、プログラムを作成するに当たってMakefileが必要になったとき、公開されている既存のものを参考にして、必要な部分のみの修正で事が足りていました。
しかし、1からMakefileを作成するとなると、やはりその基本を抑えておく必要があります。
そこで、いろいろと調べた結果などをまとめたいと思います。

尚、今回使用したmakeのバージョンは GNU Make 3.81 です。

超基本的なMakefile文法解説

Makefileは以下のような構文ブロック(エントリ)から成り立っています。

ターゲット: コンポーネント # 依存関係行/ルール行 
<tab>コマンド         # コマンド行 
  • ターゲットに対してコンポーネント複数指定でき、省略することも可能。
  • コンポーネントは、同Makefile中の別のエントリのターゲットを指定することも可能。
  • コマンド行は先頭がtabで、ターゲットのためのコマンドラインを記述。
  • コマンド行は1エントリ中に複数書く事ができ、省略することも可能。
  • ターゲットは複数指定することも可能。
  • #以降はコメント
超基本的な動作解説

makeの基本的な動作の流れをつかむため、先の文法に倣い、以下のようなファイルを作成します。

A : C B
    echo A
B : D
C : 
    echo C
D :
    echo D

ファイル名をMakefileで保存し、makeを実行すると、

% make -s
C
D
A

と出力されました。(-s は進捗状況出力を抑えます。結果を見やすくするために指定しました)


ポイントは、

という点です。


次に、以下のようなMakefileを考えます。

hoge: foo bar
    cat foo bar > hoge
foo:
    touch foo
bar:
    touch bar

このMakefileに対して立て続けに2回実行してみると、警告を発して終了します。

% make
touch foo
touch bar
cat foo bar > hoge
% make
make: `hoge' は更新済みです

理由は、ターゲットを評価する時点で、

  1. ターゲットと同名のファイル名がカレントディレクトリに存在する。
  2. コンポーネントが指定されている場合、タイムスタンプの関係がコンポーネント<ターゲット

この両方が成立すると、makeはターゲットを生成しないためです。
試しに以下のように外部からコンポーネントを更新しmakeすると、

% touch foo
% make
cat foo bar > hoge

上記の条件は成立しなくなるため、最終ターゲットは生成されます。

サフィックスルール

コンパイルアセンブルを行う際、構築のためのコマンドに対するソースファイルと、その結果となる出力ファイルのサフィックスは、言語によって通例的に決まっています。(C言語で言うならccで.c→.o)
簡単に言ってしまうと、このサフィックスの関係の定義がサフィックスルールです。
入力/出力も含めて、サフィックスルールで登場するサフックスの定義は以下のように行います。

.SUFFIXES: サフィックス サフィックス ...

そして、サフィックスルールはエントリと同じように定義しますが、依存関係行を以下のようにします。

.サフィックス.サフィックス:

出力用の2つ目のサフィックスは省略可能で、その場合、出力ファイルはサフィックスがないものとして扱われます。
また、コンポーネントは指定しても無視されます。
以下のMakefileは実際にサフィックスルールを利用したものです。

.SUFFIXES: .x .y .z
dst: src.z
    mv src.z dst
.y.z:
    cp src.y src.z
.x.y:
    cp src.x src.y
src.x:
    touch src.x

1行目がサフィックスルールで使用するサフィックスの定義、2・3つ目がサフィックスルールを定義したエントリです。
結果は以下のようになります。

% make
touch src.x
cp src.x src.y
cp src.y src.z
mv src.z dst
rm src.y

依存関係の流れの概要は、

  • 最終ターゲットdstはコンポーネントsrc.zに依存しており、
  • src.zは2つ目のルール「*.y→*.z」が適用されて、src.yに依存し、
  • src.yがターゲットである3つ目のルール「*.x→*.y」が適用され、src.xに依存し、
  • srx.xをターゲットとする4つ目のエントリのコマンドから順次処理されていく。

となります。
最後に記述にないrmが実行されていますが、これは、サフィックスルールを適用する過程で生成され、最終的に不要になるコンポーネントをmakeが削除しているためです。
また、サフィックスルールは標準で定義されており(デフォルトルール)、暗黙のうちに適用されるものがあります。
デフォルトルールを確認するにはmake実行時に-pオプションを指定します。

その他特殊ターゲット

その他、makeには特殊なターゲットが用意されています。以下にまとめます。

ターゲット 効能
.SILENT: コマンド行の状況を出力しない。-sと同等。
.IGNORE: コマンド行が失敗しても処理を継続する。
.DEFAULT: Makefileにエントリがなかった場合に評価されるターゲットエントリ。
.PRECIOUS:ファイル名 指定したファイルをmakeが消去しないようにする。
.PHONY:ターゲット ファイルが不要なターゲットを定義する。

.PRECIOUSはサフィックスルールの解説例で、src.yを指定すれば最後に勝手に削除されるのを防止できます。
.PHONYは以下のようなcleanターゲットでよく利用されます。

.PHONY: clean
clean:
    rm *.o 

理由は、仮にcleanというファイルが存在した場合、コンポーネントを持たないターゲットとして扱われ、コマンドが実行されません。
そのため、.PHONYによってcleanというターゲットはファイル名ではない事を指示します。

マクロ

Makefile中に環境変数のような感覚で、マクロを利用する事ができます。
マクロを定義する場合の例は以下の通りです。

CORE = main.o low.o
PLATFORM = peripheral.o ${CORE}
print_objs:
    echo ${PLATFORM}

定義したマクロを参照する際の{と(はどちらでもOKです。1文字のマクロは括弧不要です。
exportされている環境変数はマクロとして扱う事ができます。
また、以下のようにコマンドラインからマクロを定義する事も可能です。

% MACRO1=macro1 make MACRO2=macro2

makeコマンドの前後どちらでも定義可能ですが、以下の同名マクロの優先順位付けにより意味が異なります。

  1. makeで定義された標準のマクロ
  2. 環境変数コマンドラインのmakeより前に定義されたマクロ(上の例ではMACRO1)
  3. Makefile中に定義されたマクロ
  4. コマンドラインのmakeより後に定義されたマクロ(上の例ではMACRO2)

1.の標準で定義されているマクロから、よく使われるものを以下に示します。

マクロ 効能 注意点
$@ ターゲット名を示す 依存関係行では$$@
$? 指定されたコンポーネントのうち、ターゲットより新しいもの全てを示す サフィックスルールでは使用不可
$ ターゲットより後に更新されたコンポーネント サフィックスルールと.DEFAULTエントリのみ使用可
$* $<のサフィックスを除いたもの サフィックスルールのみ使用可

その他、デフォルトで定義されているマクロはオプション-pで確認する事ができます。

まとめ

見よう見まねでなんとなく理解していたつもりだったあったMakefileでしたが、改めていろいろと調べていくうちに新たな発見がありました。
ざっくりとしたまとめすぎて、今回取り上げ切れませんでしたが、他にもmakeには作業を効率化する機能が満載です。
これからは積極的にMakefileを記述していきたいと思います。
また、プログラム生成用途以外の作業をMakefileで記述してみると面白いかもしれません。