Contents
SCons
SConsはmakeやAntのように数多くあるビルドツールのうち一つであり、Pythonで書かれている。 ビルド設定のファイルをPythonで記述することができ、またSCons自身が依存関係解析の機能を持っているため、 makeと比べるとずっと簡単にビルド環境を構築することができる。
Hello, World的なもの
以下のような簡単なCプログラムをSConsでビルドしてみる。
hello.c
#include <stdio.h>
int main (int argc, char *argv[]) {
printf("Hello, World!\n");
return 0;
}
以下のような内容でSConstructというファイルを作成する。このファイルがmakeにとってのMakefileに相当する。
SConstruct
Program('hello', 'hello.c') # helloがMakefileでいうターゲット
たったこれだけである。Makefileよりもずっと簡単だ。あとはsconsコマンドを実行すればビルドが完了する。
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o hello.o -c hello.c
gcc -o hello hello.o
scons: done building targets.
$ ./hello
Hello, World!
$
コンパイルオプションを指定する
Program('hello', 'hello.c', CCFLAGS='-Wall -O2')
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o hello.o -c -Wall -O2 hello.c
gcc -o hello hello.o
scons: done building targets.
$ ./hello
Hello, World!
$
リンクオプションを指定する
Program('hello', 'hello.c', LINKFLAGS='-pg')
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o hello.o -c hello.c
gcc -o hello -pg hello.o
scons: done building targets.
$
ライブラリを指定する
Program('hello', 'hello.c', LIBS='m') # 数学ライブラリlibmをリンクする。実際には必要ない。
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o hello.o -c hello.c
gcc -o hello hello.o -lm
scons: done building targets.
$ ./hello
Hello, World!
$
インクルードパスを指定する
Program('hello', 'hello.c', CPPPATH='/usr/local/include')
$ scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o hello.o -c -I/usr/local/include hello.c
gcc -o hello hello.o
scons: done building targets.
$ ./hello
Hello, World!
$
クリーンアップ
$ scons -c
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed hello.o
Removed hello
scons: done cleaning targets.
$
MakefileとSConstructを見比べてみる
次に実際に拙作のプロジェクトdtlで使われているMakefileとSContructを見比べてみよう。
dtlのサンプルプログラムのためのMakefile
CPP = g++
CPPFLAGS = -c -O2 -Wall
OBJS_FLAGS = -O2 -o
STRDIFF_OBJS = strdiff.o common.o
STRDIFF3_OBJS = strdiff3.o common.o
UNIDIFF_OBJS = unidiff.o common.o
UNISTRDIFF_OBJS = unistrdiff.o common.o
INTDIFF_OBJS = intdiff.o
INTDIFF3_OBJS = intdiff3.o
PATCH_OBJS = patch.o common.o
FPATCH_OBJS = fpatch.o common.o
ALL_TARGETS = strdiff strdiff3 unidiff unistrdiff intdiff intdiff3 patch fpatch
DEPENDENCY_FILE = Makefile.depend
.cpp.o:
$(CPP) $(CPPFLAGS) $<
all : $(ALL_TARGETS)
strdiff : $(STRDIFF_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(STRDIFF_OBJS)
strdiff3 : $(STRDIFF3_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(STRDIFF3_OBJS)
unidiff : $(UNIDIFF_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(UNIDIFF_OBJS)
unistrdiff : $(UNISTRDIFF_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(UNISTRDIFF_OBJS)
intdiff : $(INTDIFF_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(INTDIFF_OBJS)
intdiff3 : $(INTDIFF3_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(INTDIFF3_OBJS)
patch : $(PATCH_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(PATCH_OBJS)
fpatch : $(FPATCH_OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(FPATCH_OBJS)
clean:
${RM} *.o *~ $(ALL_TARGETS)
depend:
@echo "generating dependency file"
@$(CPP) -MM *.cpp > $(DEPENDENCY_FILE)
include $(DEPENDENCY_FILE)
これは複数あるサンプルプログラムをビルドするためのものでターゲットがたくさんある。 また、依存関係は「make depend」を実行することによってgccの-MMオプションを使ってMakefile.dependに吐くようにしている。
dtlのサンプルプログラムのためのSConstruct
env = Environment(CCFLAGS='-Wall -O2')
targets = { 'strdiff' : ['strdiff.cpp', 'common.cpp'],
'intdiff' : ['intdiff.cpp'],
'strdiff3' : ['strdiff3.cpp', 'common.cpp'],
'intdiff3' : ['intdiff3.cpp', 'common.cpp'],
'unidiff' : ['unidiff.cpp', 'common.cpp'],
'unistrdiff' : ['unistrdiff.cpp', 'common.cpp'],
'patch' : ['patch.cpp', 'common.cpp'],
'fpatch' : ['fpatch.cpp', 'common.cpp']
}
[ env.Program(target, targets[target]) for target in targets ]
Makefileに比べてかなりすっきりしたのが分かっていただけるだろうか。 しかもPythonで書けるのでMakefileに比べてずっとプログラマティックだ。 依存関係の解析も実行時にSConsが全部やってくれるのでdependターゲットはもはや必要ない。
次にdtlのテストプログラムのビルドについて見てみよう。
dtlのテストプログラムのためのMakefile
# Makefile for test programs
CPP = g++
CPPFLAGS = -c -O2 -Wall
OBJS_FLAGS = -O2 -o
LIBS = -lgtest
OBJS = dtl_test.o
DEPENDENCY_FILE = Makefile.depend
.cpp.o:
$(CPP) $(CPPFLAGS) $< $(LIBS)
dtl_test : $(OBJS)
$(CPP) $(OBJS_FLAGS) $@ $(OBJS) $(LIBS)
check : dtl_test
@./dtl_test
clean :
${RM} *.o *~ dtl_test
depend:
@echo "generating dependency file"
@$(CPP) -MM *.cpp > $(DEPENDENCY_FILE)
include $(DEPENDENCY_FILE)
基本的にはさきほどのMakefileと考え方は同じだ。ただ、テストプログラムなのでcheckターゲットがあり、 これを実行すると実際にテストが実行される。
dtlのテストプログラムのためのSConstruct
env = Environment(CCFLAGS='-Wall -O2', LIBS='gtest')
test = env.Program('dtl_test', ['dtl_test.cpp'])
test_alias = env.Alias('check', test, test[0].abspath)
env.AlwaysBuild(test_alias)
サンプルプログラムの時と同様にかなり短くなった。 ただ、「make check」と同じようなことをやるには少々面倒な手順が必要で上記のような書き方をする必要がある。
SConsはmakeの代替となり得るか?
ここまで書いてきたように、SConsはmakeに比べると非常に簡潔に書くことができ、 またPythonで記述することが可能なので、makeに比べると柔軟性・拡張性に優れている。
しかし、逆にmake(やAutotools)では簡単にできたことがSConsはかえって書くのが面倒になってしまった部分もある。例えば、Autotoolsを使用していれば、 次のようなコマンドを打つ機会があるだろう。
- make install
- make check
- make dist
これらのコマンドに該当する処理はAutotoolsによって自動生成されるため、普通は自分で書く必要はない。 だが、SConsではSConsが用意しているInstall関数等を使って自分で書く必要がある。 「make check」の書き方についてはさきほどのテストプログラムのビルド・実行が参考になるかと思う。
また、makeではファイル間の依存関係をあらかじめ記述しておく必要があるが、 SConsでは内部で勝手にやってくれる。さらにOSがWindowsなのかLinuxなのかといった環境の違いも全部面倒を見てくれる。これ自体は非常に大きなメリットだが、SConsがやらなければならない仕事が非常に多くなり、ビルド自体が遅くなるというデメリットがある。 実際、SConsはある一定以上の規模に達したプロジェクトではスケールしないらしい。 makeの場合はそもそも最初から依存関係が記述されていることを前提にしているので、その点は問題にならない。1
関連ページ
参考
そのかわり、上記にもあるようにdependというターゲットを作り、「make depend」で「gcc -MM *.c > Makefile.depend」とかやらないといけないし、ソースが複数のディレクトリに別れていたりすると専用のスクリプトを用意したりとひどく面倒なことになる。ある程度規模が大きくなったプロジェクトは大抵そうだ (1)
