この記事では,超基本的な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
には変数やマクロを使用したもっと効率的な書き方があります.
しかし,それを解説すると長くなるので,追々書いていきたいと思います.