MakefileのPHONYで便利コマンドを書く

314, 2021-08-27

目次

MakefileのPHONYで便利コマンドを書いたら世界が変わった

Makefileにはターゲットと依存ファイルを記述すると、そのターゲットをビルドするためのコマンドを実行することができます。
さらに.PHONYを書くとダミーターゲットを作ることができます。

ダミーターゲットを使うと、そのダミーターゲットをビルドするためのコマンドを実行することができます。
実際にはターゲットはダミーなので、コマンドのみが実行されます。
つまりMakefileでコマンドだけを実行したい場合は、この.PHONYでダミーターゲットを作れば可能だということになります。

この記事ではこの.PHONYとダミーターゲットについて詳しく見ていきます。
具体的には↓になります。

  • 普通のMakefileの記述

  • 端末から生成するファイルを指定

  • .PHONYを使ったダミーターゲットの記述

  • ダミーターゲットをサブコマンドとして実行する

  • .PHONYの役割と必要性

  • 便利なサブコマンドを沢山作る

  • サブコマンドをサブコマンド内から実行する

普通のMakefileの記述

普通のMakefileの書き方についておさらいします。
Makefileはビルドを行うためのファイルです。
ビルドとはファイルをコンパイラでコンパイルすることです。
さらにファイルには依存するファイルがあります。

つまり、ビルドには↓の要素が必要になります。

  • ビルドするファイル

  • ファイルの依存関係

  • コンパイルを実行するコマンド

この3つの要素を簡潔に記述できるのがMakefileです。
Makefileではこの3つの要素を↓のように記述することができます。

ビルドするファイル(ターゲット): 依存するファイル
    ビルドのためのコマンド

たとえばmain.cというC言語のファイルがあったとします。
ビルドではこのmain.cというソースファイルをmain.oというオブジェクトファイルに変換したいとします。
オブジェクトファイルとはソースコードから生成できる中間形式のファイルのことです。
そしてこのmain.oを最終的にapp.exeという実行ファイルにしたいとします。

main.omain.cmain.hの2つのファイルに依存しています。
コンパイラはgccを使いましょう。

そうするとこのmain.cをビルドするためのMakefileは↓のようになります。

app.exe: main.o
    gcc main.o -o app.exe

main.o: main.c main.h
    gcc -c main.c

↑のコードをMakefileというファイルで保存し、端末からmakeを実行するとapp.exe実行ファイルが生成されます。
app.exemain.oに依存しています。main.oはオブジェクトファイルです。
そしてmain.omain.cmain.hに依存しています。

Makefileは「app.exeをビルドするためにはmain.oが必要だな」と判断します。
そして次に「main.oをビルドするためにはmain.cmain.hが必要だな」と判断します。
そして「main.cmain.hは存在するからmain.oをコマンドで生成しよう」となります。

こういう感じでMakefileはビルドを実行していきます。

端末から生成するファイルを指定

さきほどのMakefileで、たとえばmain.oのみを生成したい場合はどうすればいいのでしょうか?
そういう場合は↓のようにコマンドを実行します。

$ make main.o

こうするとmain.oのみのビルドが実行されます。
このようにmakeコマンドの引数にターゲットを指定すると、そのターゲットのビルドのためのコマンドが実行されるわけです。

では、ターゲットを生成せずにコマンドのみを実行できればサブコマンドのように使えるのではないか?
という話になりますが、まさにその通りで、そのために.PHONYを記述します。

.PHONYを使ったダミーターゲットの記述

.PHONYPHONYは「偽物」という意味です。つまりダミーですね。
.PHONYは↓のように記述します。

.PHONY: ダミーターゲット名

たとえば端末からmake cleanというサブコマンドを実行したいとします。
このサブコマンドはビルドされたファイルを掃除するためのコマンドです。
この場合、Makefileには↓のように記述します。

.PHONY: clean
clean:
    rm app.exe main.o

ダミーターゲットをサブコマンドとして実行する

先ほど書いたcleanというダミーターゲットを端末からサブコマンドとして実行します。
そうすると↓のような結果になります。

$ make clean
rm app.exe main.o

rm app.exe main.oというコマンドが実行されファイルが掃除されました。
このようにダミーターゲットを使うことで色々なサブコマンドを作ることができます。

.PHONYの役割と必要性

では.PHONYを書かない場合はサブコマンドは実行できないのでしょうか?
結論から言うとできます
↓のようにMakefileにcleanを書くだけでも端末からmake cleanは実行できます。

clean:
    rm app.exe main.o

ではなぜ.PHONYを書く必要があるのでしょうか?
実は現在のディレクトリ以下にターゲットと同じファイル名が存在する場合、このサブコマンドは機能しません。

たとえば↓のように現在のディレクトリ以下にcleanというファイルを作ります。
そしてmake cleanを実行すると↓のような結果になります。

$ touch clean
$ make clean
make: 'clean' is up to date.

cleanは更新済み」と表示されてますね。
つまりMakefileがcleanというファイルをビルドするターゲットのファイルとして認識していて、さらにcleanというファイルが存在しているので、結果的にコマンドを実行しないということになります。

このケースではmake cleanはサブコマンドとして実行したいわけですから、ちょっと困るわけです。
そういう時に.PHONYを使います。
先ほどのMakefileの記述を↓のように戻します。

.PHONY: clean
clean:
    rm app.exe main.o

この状態でmake cleanを実行するとちゃんとファイルが削除されます。
Makefileがcleanをダミーターゲットとして認識しているからですね。

便利なサブコマンドを沢山作る

サブコマンドを作れるとなったら色々作ってみたいのが人の心情と言うものです。
たとえば私は↓のようなサブコマンドをよく作ります。

$ make clean
$ make init
$ make full

make cleanは先ほども紹介したビルドされたファイルを削除するコマンドです。

そしてmake initはビルド環境を初期化するためのコマンドです。これはたとえばbuild/というディレクトリを作ったり、テストに必要なファイルをbuild/以下にコピーしたりします。

.PHONY: init
init:
    mkdir build/
    cp test.txt build/

make fullは各種サブコマンドを呼び出してフルビルドを行うコマンドです。
これについては後述します。

サブコマンドをサブコマンド内から実行する

サブコマンドをサブコマンド内から呼び出すこともできます。
たとえばmake fullというサブコマンドは↓のように書くことができます。

.PHONY: full
full:
    make clean
    make init
    make

↑のようにmake cleanmake init, そしてmakeを順々に呼び出しています。
make fullを実行するとまずmake cleanでファイルの掃除が行われ、make initでビルド環境が初期化されます。そしてmakeで各種ビルドが行われます。

ビルドを1からクリーンな状態で実行したい場合に便利なコマンドですね。
ただ注意点として、最初のビルドはmake full以外のコマンドで行う必要があります。
これはビルドされたファイルが存在しないとmake cleanが失敗するためです。

おわりに

今回はMakefileの.PHONYとサブコマンドについて見てきました。
Makefileは扱いになれると非常に便利なツールと言えます。

ビルドを自動化できて嬉しいよね

せやな