2008年9月29日月曜日

動的 Make (?)

同期に、
「膨大なソースコードをチェックアウトしてコンパイルする際、
ソースを全部持ってきてからじゃないとコンパイルできないのはおかしい。」
と言われたので、なるほどと思い、どうやればソースを持ってきながら
コンパイルをできるかを試行錯誤してみた。

前提は、

  1. Makefile は最初に持ってくる

  2. Makefile にはコンパイル順序等の依存関係が正しく記述されている

  3. ソースコードは"とりあえず" scp で持ってくる

  4. 必要なソース及びヘッダは一つのディレクトリにまとまっている


くらいかな。3,4番目はすぐ拡張できそうだけど、とりあえず、そういう制限を設ける。
この時、あるプログラムが、main.c, source1.c source2.c source3.c からなり、
それら全てが、header.h をインクルードしているとする。

以下のような Makefile を書くと、XXX.c => XXX.o を作るときに、
XXX.c がローカルに存在しなければ、scp を使ってそのファイルを取得しに行く。
さらに、"gcc -MG -MM XXX.c" をして、XXX.c がインクルードしている
システムヘッダファイル以外のヘッダファイル(アプリケーション用のヘッダ?)を探し、
やっぱりローカルに存在しなければ取得する。これらのプロセスを経て、XXX.c を
コンパイルするに足るファイル群が揃えば、XXX.c => XXX.o を作る。
そして、最終的に必要なオブジェクトファイル群が出来上がったら、それらを結合して
prog を作成する。


PROG = prog
CC = /usr/bin/gcc
CFLAGS = -g -DDEBUG -Wall -Wmissing-prototypes -Wmissing-declarations
OBJS = main.o source1.o source2.o source3.o
SRCS = $(OBJS:.o=.c)
FGET = scp -q
ORIGIN = source-host.example.com:/path/to/sources/

.SUFFIXES: .c .o
.PHONY: all clean

all: $(PROG)

$(PROG): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $(OBJS)

$(SRCS):

.c.o:
@if test ! -f $<; then \
$(FGET) $(ORIGIN)$< .; \
else :; fi
@headers=`$(CC) -MG -MM $< | sed 's/$*.o: $<//g'`; \
for h in $$headers; do \
if test ! -f $$h; then \
$(FGET) $(ORIGIN)$$h .; \
else :; fi \
done
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@;

clean:
-rm -f $(PROG) *.o *.c *.h *core *~


なんとなく、うまく動いたけど、いくつか問題が。
一つ目は、ファイルの取得に scp を使っており、一つのファイルを取得する度に
セッションの確立、切断が発生するということ。ここは、うまいラッパーを書いて、
処理をする必要がありそう。二つ目は、"make -j4" なんてやった場合に発生する問題。
"make -j4"すると、".c.o:"の処理がソースファイル毎に独立に実行されるので、
複数のソースファイルが同じヘッダファイルをインクルードしている場合には、
実行時にそのヘッダファイルがないと、4つの処理がそれぞれ同じヘッダファイルの
取得を行ってしまうということ。これは、並列処理の特性上仕方ないのかもしれなけど、
もう少しうまく処理してくれる中間層が必要かな。

久々に Makefile 直書きしたから、結構楽しかった。
普段は、automake, autoconf のお世話になりっぱなしなので。。。。

0 件のコメント: