基本的なMakefileを作っておく
C言語でコマンドやユーティリティを開発する場合、単一のCソースコードだけを使うというのはまれなケースだ。開発の初期段階はそれでもよいと思うが、しばらくしてソースコードが膨れ上がると複数のファイルに分割するようになる。C言語に慣れている開発者であれば、最初から複数のファイルに分割した状態でコーディングを始めるだろう。そこで役立つのが「Makefile」である。
Makefileは、コンパイル方法やコンパイルの依存関係などを書いておくものだ。ビルド、クリーンナップ、などの処理をあらかじめMakefileに書いておくことで、「開発段階では『make』または『make clean』としか入力しない」という状況が整う。いちいちccコマンドを実行するのは面倒なので、Makefileに処理をまとめておくのである。
C言語のソースコードをビルドするためのMakefileは比較的使い回しが効くので、1つ作っておけば後はコピー&ペーストと簡単な編集で再利用できると思う。作っておいて損はないだろう。
Makefileのサンプル
まずは今回のファイル配置を把握しておこう。用意したファイルは、次の5つだ。
$ tree .
.
├── Makefile
├── message.c
├── usage.c
├── welcome.c
└── welcome.h
0 directories, 5 files
$
前回作成した「welcome.c」というファイルは、「welcome.c」「message.c」「usage.c」という3つのファイルに分けてある。message.cにはメッセージを返す「message()」という関数を、usage.cには使い方を出力する「usage()」という関数を実装した。welcome.cにはオプションを処理するコードを加えると共に、usage()とmessage()を呼び出す処理を追加してある。次のような感じだ。
welcome.c - オプション処理を追加するとともに、usage()およびmessage()を利用するように変更
#include "welcome.h"
int
main(int argc, char *argv[])
{
int opt;
while (-1 != (opt = getopt(argc, argv, "uh"))) {
switch (opt) {
case 'u':
case 'h':
usage();
exit(EXIT_FAILURE);
break;
default:
usage();
exit(EXIT_FAILURE);
break;
}
}
printf("%s", message());
exit(EXIT_SUCCESS);
}
message.c - メッセージを返すmessage()を実装
#include "welcome.h"
char *
message()
{
char *b;
b = calloc(1, sizeof(char) * 1024 * 4);
strcpy(b,
" .-/+oossssoo+/-." "\n"
" `:+ssssssssssssssssss+:`" "\n"
" -+ssssssssssssssssssyyssss+-" "\n"
" .ossssssssssssssssssdMMMNysssso." "\n"
" /ssssssssssshdmmNNmmyNMMMMhssssss/" "\n"
" +ssssssssshmydMMMMMMMNddddyssssssss+" "\n"
" /sssssssshNMMMyhhyyyyhmNMMMNhssssssss/" "\n"
".ssssssssdMMMNhsssssssssshNMMMdssssssss." "\n"
"+sssshhhyNMMNyssssssssssssyNMMMysssssss+" "\n"
"ossyNMMMNyMMhsssssssssssssshmmmhssssssso" "\n"
"ossyNMMMNyMMhsssssssssssssshmmmhssssssso" "\n"
"+sssshhhyNMMNyssssssssssssyNMMMysssssss+" "\n"
".ssssssssdMMMNhsssssssssshNMMMdssssssss." "\n"
" /sssssssshNMMMyhhyyyyhdNMMMNhssssssss/" "\n"
" +sssssssssdmydMMMMMMMMddddyssssssss+" "\n"
" /ssssssssssshdmNNNNmyNMMMMhssssss/" "\n"
" .ossssssssssssssssssdMMMNysssso." "\n"
" -+sssssssssssssssssyyyssss+-" "\n"
" `:+ssssssssssssssssss+:`" "\n"
" .-/+oossssoo+/-." "\n");
return b;
}
usage.c - 使い方を出力するusage()を実装
#include "welcome.h"
int
usage()
{
fprintf(stderr, "usage: welcome [-uh]\n");
return 0;
}
welcome.cからmessage()とusage()が利用できるように、「welcome.h」というヘッダファイルを作成しておく。どのファイルからもこのヘッダファイルを取り込むように(include)してある。
welcome.h - 今回のコマンドのヘッダファイル
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int usage();
char *message();
makeで関連するファイルをすべてビルドして、最後にオブジェクトファイルを結合して「welcome」というバイナリファイル(実行ファイル)を生成するMakefileを次のように書いておく。
SRCS= welcome.c message.c usage.c
CMD= welcome
OBJS= ${SRCS:.c=.o}
all: ${OBJS}
cc -o ${CMD} ${OBJS}
.SUFFIXES: .c .c
.c.o:
cc -c $<
clean:
rm -f ${OBJS} ${CMD}
ここでは、「make clean」でオブジェクトファイルとバイナリファイルが削除され、ソースコードだけが残るようにしてある。
今回取り上げたようなフラットな構造のC言語ソースコードの組み合わせであれば、上記Makefileの「SRCS=」の行に指定するソースコードファイルの一覧と「CMD=」に指定するコマンド名を変えるだけで、ほかのコマンドやユーティリティのビルドにも利用できる。本連載はソースコードの解説は範疇ではないのでMakefileの記述内容について詳しくは説明しないが、興味があれば調べてみていただきたい。それほど複雑な書き方はしていないので、すぐにわかると思う。
ビルドの例と実行の例
では早速、ビルドしてみよう。環境が整っていれば、次のようにmakeコマンドを実行することでccコマンドによるコンパイルが実行される。
$ make
cc -c welcome.c
cc -c message.c
cc -c usage.c
cc -o welcome welcome.o message.o usage.o
$ tree
.
├── Makefile
├── message.c
├── message.o
├── usage.c
├── usage.o
├── welcome
├── welcome.c
├── welcome.h
└── welcome.o
0 directories, 9 files
$
実行する前に一度「make clean」を試してみてほしい。今、生成したオブジェクトファイルとバイナリファイルが削除されるはずだ。
$ make clean
rm -f welcome.o message.o usage.o welcome
$ tree
.
├── Makefile
├── message.c
├── usage.c
├── welcome.c
└── welcome.h
0 directories, 5 files
$
makeでビルド、make cleanでクリーンナップだ。ここまで準備しておくと開発の作業がとても楽になる。
ビルドしてwelcomeコマンドを実行すると、次のようになる。
オプションを利用すると、次のようにusage()がメッセージを出力することなどを確認することができる。
仕組みさえ作っておけば、後は細かくコーディングとビルドを繰り返すことである程度のコマンドは作れるようになる。C言語でプログラミングをしたことがないのであれば、サンプルをコピー&ペーストするなどしてぜひ一度お試しいただきたい。