UNIX と Windows で Makefile を共用しているときにアレ?ってなったのでまとめてみる。
はじめに
まず Makefile の例として Hello World を出力する C のサンプルプログラム sample.c と、実行ファイル sample を作成する Makefile の例を示そう。make は gmake を仮定している。
# include <stdio.h>
int main(int argc, char *argv[])
{
printf("Hello World!\n");
return 0;
}
TARGET = sample
OBJS = sample.o
$(TARGET): $(OBJS)
これで make を実行すれば sample.c から実行ファイルが sample が作成されると思う。そう、UNIX ならね。
UNIX の場合
$ make
cc -c -o sample.o sample.c
cc sample.o -o sample
$ ./sample
Hello World!
$
Makefile にはコンパイルの方法は何も書いてないけれども、暗黙の規則によって上のようにコンパイラが実行されているわけだ。
もちろんこれには何の問題もない。
Windows の場合
Windows で実行するとどうなるか。話を単純にするために make は gmake、C コンパイラは gcc ということにしておこう。
同じようにコンパイラが実行された。
D:\test> make
gcc -c -o sample.o sample.c
gcc sample.o -o sample
D:\test>
コマンドプロンプトから "sample" ってタイプして Enter キー押したら "Hello World!" って出たよ!
D:\test> sample
Hello Wolrd!
D:\test>
何の問題もない? いや、ちょっと待ってほしい。
フォルダをよくみると作成されたのは "sample" じゃなくて "sample.exe" だ。Windows では実行ファイルの拡張子が ".exe" だからこれはこれで正しい。
gcc のオプション "-o" では単に "sample" としか指定していないが、Windows 環境なので gcc が気を利かせて拡張子 ".exe" を付加してくれてるのだ。
Windows のコマンドプロンプトから実行するときは、拡張子 ".exe" を省略して "sample" としても "sample.exe" が見つかればそれが実行される。Windows 環境であればこれは期待通りの動作である。
何が問題?
もう一度 make を実行してみよう。
まずは UNIX。
$ make
make: 'sample' is up to date.
$
さっき "sample" を作成したので再コンパイルの必要はない、"sample" は最新であるとのメッセージが表示されて make は終了した。すばらしい。無駄なコンパイルは実行しないという make が誇る機能である。
同様に Windows で実行してみると…
D:\test> make
gcc sample.o -o sample
D:\test>
あれれ? gcc が実行されてしまったぞ。
どうしてこうなった?
これは Makefile の記述に原因がある。make は依存関係に書かれているターゲットとソースのファイルの日付を比べてコマンドを実行するべきかどうかを判断する。
この例でのターゲットは "sample" だ。
実際にフォルダにできているのは "sample.exe" なので make は "sample" がないものと思って振る舞う。"sample.o" はすでにできているのでターゲット "sample" を作成すべく gcc が実行されるが作成されるのはまたしても "sample.exe"...
どうしたらいい?
ターゲットに実際のファイル名を指定する
これはターゲットが実際のファイル名と一致していないことに原因がある。だから実際のファイル名をターゲットに指定すればよい。
TARGET = sample.exe
OBJS = sample.o
$(TARGET): $(OBJS)
うん、そうだね。これで Windows で正しく動くようになった。Windows での gcc の -o オプションは "sample" でも "sample.exe" でもどちらも期待通り動作する。
しかし UNIX 用とは異なる Makefile になってしまった。
条件文でターゲットのファイル名を切り替える
こうなったら環境をみて条件文で切り替えるしかないね。くどいようだけどここでは gmake を想定しているのでその他の make の人はマニュアルを参照すること。
ifeq ($(OS),Windows_NT)
TARGET = sample.exe
else
TARGET = sample
endif
OBJS = sample.o
$(TARGET): $(OBJS)
ここまでくればあと一息。
解決方法
実行ファイルの拡張子を EXEEXT という変数に格納することにして、
依存関係のターゲットには常に EXEEXT を付加しておけばいい。EXEEXT の中身は Windows 環境であれば ".exe" それ以外であれば空文字列だ。
TARGET = sample
OBJS = sample.o
ifeq ($(OS),Windows_NT)
EXEEXT = .exe
endif
$(TARGET)$(EXEEXT): $(OBJS)
これで UNIX, Windows 双方の環境で動作するようになった。
おわりに
いまどき直接 Makefile を書く人も少ないのかもしれないけれど、調べても情報が見つからなかったので自分でまとめてみた。
もっとこうしたほうがいいとか、普通はこうするとかいうネタを持ってる人がいたらコメントくれるか、どこかに記事をアップしてくれるといいんじゃないかな?
申し訳ないけど autoconf, automake 使えばとか CMake 使えばとかいうのは別のところで布教お願いします。