この記事では,超基本的なMakefileの作り方を解説していきます.
make/Makefileとは?##
makeは主にコンパイラ型言語のプログラムをビルドするために使用されるコマンドです.
$ make
とするだけで勝手にソースコードから実行ファイルが作成されます.
もちろん,ソースコードだけ作成してmakeコマンドを実行すれば勝手に実行ファイルが作成されるような魔法のコマンドではありません.
どのようにソースファイルをビルドするのかを記述したファイルが必要です.
それがMakefileと呼ばれるファイルです.
makeを使うメリット##
Makefileを記述する必要があるのなら,makeコマンドを使用する利点はどこにあるのでしょうか?
利点を説明する前に少しc言語のビルドについて考えてみましょう.
ソースファイルから実行可能なファイルを作るまでにはいくつかの手順があります.
簡単に説明すると,以下のようになります.
手順①プリプロセス
#defineで宣言されたマクロの展開など,プロプロセッサ命令を処理します.機械語に変換するための前処理ですね.
手順②コンパイル
ソースファイルをアセンブリ言語に変換します.gccでは,-Sオプションを指定すると,アセンブリで書かれたファイルが生成されます.
手順③アセンブル
アセンブリで書かれたファイルをコンピュータが理解できる機械語に変換します.gccでは,-cオプションを指定すると,機械語で書かれたオブジェクトファイルが生成されます.
手順④リンク
複数のオブジェクトファイルを連結し,実行可能ファイルを作成します.
これでmakeを使う利点を説明する準備ができました.
複数のソースファイルを扱うプロジェクトを想像してください.
一部のファイルに変更を加えただけで,変更していないファイルについても再度オブジェクトファイルを生成してからリンクしていたのではリソースの無駄となります.かと言って,変更を加えるたびにそのファイルを控えておいて手動でビルドするのは手間ですし,ミスが起こる原因ともなります.
そこで,Makefileに一度ビルドのルールを記述しておけばその後,makeを実行するだけで変更を加えたファイルのみオブジェクトファイルを作り直し,リンクして,実行可能ファイルを自動で作成してくれます.
大きいプロジェクトになればなるほど,makeのメリットは大きくなるでしょう.
Makefileを書いてみよう##
まずは,一番基本的なMakefileを作ってみましょう.
今回は以下のプログラムをビルドします.
# include <stdio.h>
int main(void){
printf("Hello, World!\n");
return 0;
}
これをビルドして,hello_worldという名前の実行ファイルを作成するMakefileは以下のようになります.
hello_world: hello_world.c
gcc hello_world.c -o hello_world
ソースファイルとMakefileを同じディレクトに置いて,そのディレクトリでmakeコマンドを使って見ましょう.
$ make
gcc hello_world.c -o hello_world
上のようになり,実行ファイルhello_worldが作成されていると思います.
ファイルの中身を変更せずにもう一度makeコマンドを実行するとどうなるでしょうか?
$ make
make: 'hello_world' is up to date.
ファイルに変更がないため,hello_worldという実行ファイルがもう一度作られることはありません.
次に,ソースファイルの中身を書き換えてから,makeコマンドを実行してみましょう.
$ make
gcc hello_world.c -o hello_world
すると,今度はgccコマンドが実行され,hello_worldが作り直されています.
このようにmakeコマンドは,実行ファイルが作成された後にソースファイルが変更されたかを確認してビルドするかどうかを決めていることがわかります.
次に,Makefileの書き方を解説します.
ターゲット名: 依存関係
[ Tab ]コマンド
ターゲット名にはこれから作成するファイル名を書きます.
依存関係にはターゲットを作成するために必要なファイルを書きます.
コマンドにはターゲットを作成するためのコマンドを記述します.
注意したいのは,コマンド行の先頭にはタブを入れなければならないことです.
そんなに難しくはないですね.
複数ファイルのMakefile##
次は,ソースファイルが複数ある場合のMakefileの書き方です.
ディレクトリ構成は以下のようになっているとします.
______________ test1.c
|______ test2.c
|______ test3.c
|______ Makefile
この場合のMakefileは以下のようになります.
test: test1.o test2.o test3.o
gcc test1.o test2.o test3.o -o test
test1.o: test1.c
gcc -c test1.c
test2.o: test2.c
gcc -c test2.c
test3.o: test3.c
gcc -c test3.c
testという実行ファイルを作成するルールの他に,testを作成するための依存ファイルになっているオブジェクトファイルを作成するルールも記述されています.
makeコマンドを実行すると,makeはまず,testを作成するためにtest1.o, test2.o, test3.oを探します.
見つからない場合,それらを作成するルールがMakefile内に記述されていないかを探します.
今回の場合,オブジェクトファイルを作成するルールが存在するので,そのルールに従って,オブジェクトファイルが作成されます.
その後,オブジェクトファイルを用いて,目的であったtestを作成します.
実際にmakeコマンドを実行してみましょう.
$ make
gcc -c test1.c
gcc -c test2.c
gcc -c test3.c
gcc test1.o test2.o test3.o -o test
testが作成されました.
test2.cだけを編集して,もう一度makeを実行すると以下のようになります.
$ make
gcc -c test2.c
gcc test1.o test2.o test3.o -o test
test2.cに関数オブジェクトファイルだけが作り直されているがわかったと思います.
おわりに##
このように,複数ファイルがある場合でもルールを追加していけばMakefileを作ることが出来ます.
しかし,もっとファイルが多くなった場合にMakefileを作成するのが面倒に思えてくるでしょう.
実は,Makefileには変数やマクロを使用したもっと効率的な書き方があります.
しかし,それを解説すると長くなるので,追々書いていきたいと思います.