Makefileの作り方


ソースコードをコンパイルする場合、毎回コンパイルコマンドを打たなければならないため、特に特殊なライブラリを使っていたりする場合には非常に面倒です。そこで、これを解決する方法として、makefileを用いる方法があります。これにより、毎回長いコマンドを打たなければならないという煩わしい作業を減らすことができます。今回はmakefileについて解説を行いたいと思います。
では内容に入ります。

必要なもの

  • Linux環境もしくはCygwin環境のPC(今回はUbuntu 14.04 LTS上で行います)
  • テキストエディタ(今回はgeditを用いています)
makefileとはあらかじめ定められたSyntax(文法のようなもの)にしたがって書かれたテキストファイルで、ソースファイルからソフトウェアをビルドする際に用いられます。通常はUnix環境で用いられますが、WindowsにおいてもCygwin等の環境で用いられることがあります。
以下にmakefileのコンテンツを記述します。
  • Explicit ruleはひとつ以上のファイルをいつ/どのように変更するかについての指示する。これはターゲットと呼ばれる。そして、そのターゲットが依存する他のファイルをリスト化し、ターゲットの必要条件を呼び出す。さらに、ターゲットを生成したり、アップデートするための命令を与える。
  • Implicit ruleはファイル名に基づくファイルのクラスをいつ/どのように変更するかについて指示する。ターゲットが、そのターゲット名に似た名前のファイルにどのように依存しているかを記述し、そのようなターゲットを生成したりアップデートする命令を与える。
  • Variable definitionは、定義のあとで記述される変数のための文字列を特定する。
  • Ditectiveは、makefileを読んでいる間に、他のmakefileを読むといったような、特殊なことを実行する命令を与える。
  • '#'で始まるmakefileの行はコメントとして扱われ、#を含む行は無視される。
以上の5つのコンテンツで、makefileは構成されています。次に、makefileの基本的なルールを記述します。

target : prerequisites
        system command(s)
  • targetはたいてい、プログラムで生成されるファイル名(実行ファイルやオブジェクトファイル)が使われる。また、実行名(cleanなど)がtargetに使われることもある。
  • prerequisitesはtargetを生成するために用いられるファイルが使われる。targetを生成するために複数のファイルを必要とする場合もあるが、targetが"clean"などの実行名である場合には必ずしもprerequisitesを必要としない。
  • system command(s)の前の空白はスペースキーではなく、Tabキーで入力する。system command(s)は実行するactionを記述する。1つのターゲットに対して基本的に一つ以上のsystem command(s)を持ち、複数のsystem command(s)を記述する場合には、それぞれのsystem commandを";"で隔てる。
以上のようなルールでmakefileは作成されます。最後にmakefileを実行するための方法を記述します。
make
基本的にはこれをTerminalで実行するだけです。makeコマンドは実行された際のカレントディレクトリ内でmakefileを読み取ります。そして、ルールに従い処理が行われます。makefileによってはmakeコマンドの後ろにオプションをつける場合もあります。例えば、ルール内でとりあげた実行名cleanをtargetに用いている場合では、cleanをプログラムで生成するわけではないとして考えているので、cleanを実行するには、これを指定してあげる必要があるため、"make clean"としてTerminalで実行します。また、makefile名がmakefileでない場合でもmakeコマンドを実行する場合もあり、この場合、読み込むmakefile名を指定してあげる必要があります。このようにケースバイケースで対応してください。

たくさん説明してきましたが、おそらくこれだけを読んでもよくわからないと思うので、C言語のプログラムをコンパイルする場合を例に挙げながら詳しく説明していきたいと思います。
// test.c
#include <stdio.h>

int main(void){
    printf("Hello World!\n");
    return 0;
}
上のソースコードはC言語の入門の際によくみるものですが、これを実際にコンパイル&実行してみましょう。(今回はtest.cというファイル名で作成しています)
gcc -o test test.c; ./test
上のコマンドを実行すると、Terminal上で"Hello World!"という結果を得るはずです。ここからはこれをもとにmakefileを作成していきます。

makefileを作成する前に、自動変数についても軽く説明しておきます。
  • $@ルールのtargetのファイル名。複数のtargetをもつ場合には、ルールのコマンドを実行したtargetの名前が$@に入る。
  • $<1つ目のprerequisites名。targetがImplicit ruleからsystem command(s)を受け取ると、Implicit ruleによって追加された1つ目のprerequisites名になる。
  • $?targetよりあとに更新されたすべてのprerequisites名。それぞれのprerequisite名はスペース(空白)で隔たれている。
  • $^すべてのprerequisites名。それぞれのprerequisite名はスペース(空白)で隔たれている。prerequisiteのそれぞれについて、同一名のprerequisiteは一度しかコピーされない。つまり、$^は、あるtargetに対して一度のみその名前をコピーする。
  • $*:Implicit ruleに一致する語幹。基本的には、サフィックス(拡張子)を除いたtargetのファイル名が$*に入る。
では、実際にmakefileを作成する作業に入っていきます。まず、test.cを作成したディレクトリ内に新たにmakefileという名前のファイルを作成してください。作成したら、テキストエディタで開いてください。その後、以下のソースコードを書いてください。
# makefile
CC :=gcc

.PHONY: clean

test: test.c
    $(CC) -o $@ $^; ./$@
clean:
    $(RM) test
何度も言いますが、system command(s)の前の空白はスペースキーで入力してください。スペースキーで入力した場合、"makefile:6: *** 分離記号を欠いています.  中止."のようになり、実行できません。ここで、先ほどまでに説明していないものが出てきているので、これらについて説明を行います。

まず、"CC"ですが、これは変数です。変数の定義の際には、代入演算子を用います。ここで使った変数は単純展開変数と呼ばれ、代入行はmakefileから読み込まれると右辺が即時評価されます。代入の際は":="を用います。この他にも変数として、再帰展開変数があり、代入の際は"="を用います。再帰展開変数の性質としては、右辺の内容を憶えておき、実際に変数を使う際に展開されます。つまり、
  • 単純展開変数では、値は定義された時点で読み込まれていて、別の変数にある参照も関数もすべて一気に展開する。
  • 再帰展開変数では、指定した値は指定通り格納され、値の中に別の変数への参照があろうと、値に置き換える処理を行うまでは参照は展開されない。
という違いがあります。"CC"の話に戻りますが、定義した変数を利用する際は、$()で変数を囲って使います。つまりここでは"$(CC)"として利用しています。

次に、".PHONY"ですが、これはフォニーターゲットと呼ばれます。上で説明したルールでも言いましたが、targetとして、実行名がtargetに利用されることもあり、この場合はフォニーターゲットを利用します。フォニーターゲットがない場合、makefile内の処理中に、今回のフォニーターゲットである"clean"という名前のファイルができてしまった場合、targetとして成立してしまい、誤動作を生じてしまう可能性が生まれます。そこで、".PHONY"という擬似的なtargetを明示的に宣言します。

最後に、"$(RM)"ですが、これはImplicit ruleに用いられる変数で、makefile内で定義なしで元々利用できる変数です。"$(RM)"は"rm -f"の意味を持っており、ファイルを削除するコマンドです。他にもこのような変数はたくさんあり、実は先ほどの"$(CC)"もC言語プログラムをコンパイルするプログラムであり、"cc"という意味を持っていますが、今回は"gcc"として利用したかったので、単純展開変数として利用しました。

以上が、各要素についての説明になります。次に、makeコマンドを実行した際にどのような動作を行うかについて説明します。

makeを実行すると、
  1. まず、フォニーターゲットではないtestというtargetが評価される。targetが実行される条件は以下の通りである。
    • testファイルが存在しない。
    • testファイルが存在するが、prerequisitesであるtest.cよりもタイムスタンプ(作成or更新された日時)が古い。
  2. 評価される際のマクロは以下のようであり、実際に実行される際には次のようになる。
    • "$(CC) -o $@ $^; ./$@"  →  "gcc -o test test.c; ./test"
のようになります。また、make cleanを実行すると、
  1. targetとしてcleanが呼ばれているので、フォニーターゲットであるcleanというtargetが評価される。このときのマクロは以下のようであり、実際に実行される際には次のようになる。
    • "$(RM) test" → "rm -f test"
のようになります。以上が動作内容になります。今回は"test.c"のようにファイル名を指定して実行しましたが、拡張子からディレクトリ内の任意のファイルを利用することも可能です。これにはwildcardの機能を利用します。使い方としては"SOURCE := $(wildcard *.c)"のようにすることで、"SOURCE"という変数内にディレクトリ内にあるc拡張子のファイル名を格納できます。また、実行ファイル名として、"EXECUTABLE := $(SOURCE:.c=)"とすることで、"EXECUTABLE"という変数内に"SOURCE"の拡張子以外のファイル名が格納されました。これにより、先ほどまででいうところのtest.cが"SOURCE"、testが"EXECUTABLE"として扱うことが可能です。このように色々な応用が可能であるため、各自でオリジナルのmakefileを作成することが可能です。

以上。

SHARE
Blogger Comment

0 コメント:

コメントを投稿