2009年05月13日

Sconsでvectexをコンパイルする

前回Blenderへのプロシージャルテクスチャの追加方法を確認できたので、続いて本番のvectexの方に取りかかりたいところですが、その前にもう少し調べておきたいことがありました。
Blenderにvectexを組み込むためには、vectexで使用しているAgg、ExpatなどのライブラリをBlenderのビルドシステムから利用できるようにする必要があります。

BlenderはビルドのためのツールとしてGnu Make、Scons、CMakeの3種類の方法を用意しています。(詳しくは、こちらのページをご参照ください。)

私自身はLinuxでのビルドにSconsを使い、WindowsではCMake + Visual C++を主に使っています。CMakeを使ってAnjuta、KDevelopなどのLinuxのIDEを使う方法、WindowsのMingw + Sconsというような方法でもビルドできることを確認しています。

vectex自体はGnu Makeでコンパイルするようになっていますので、Gnu Makeのビルドシステムを使うのが一番楽にvectexの組み込みができそうですが、残念ながらこの方法ではWindowsでコンパイルするのが難しいようです。
可能であれば3種類のすべてのビルドシステムに対応できるようにしたいところですが、とりあえずはSconsを使ってビルドを行うことを前提にvectexの組み込みを進めようと思います。

とは言ったものの、これまでLinuxでのプログラミングにはMakefileを少し書いたことがある程度で、他の方法は試したことがありません。
いきなりBlenderのSconsの設定ファイルを読むのは難しそうですので、もう少し基本的なところから少しずつ慣れていく方がよさそうです。

ということで、まずはvectexをSconsを使ってコンパイルしてみることにしました。(今回は、とりあえずLinux上のみに限定しておきます。)
作成したSconsの設定ファイルをこちら(scons_file090513.zip)にまとめてあります。

○Sconsによるビルドの基本
SconsはPythonを使ったビルドシステムです。

Sconsでビルドを行うには「SConstruct」というファイルを用意し、そのファイルが置かれているディレクトリに移動して、コマンドラインから「scons」と実行します。
それぞれ、Gnu Makeを使う場合の「Makefile」、「make」コマンドと同じような感じで使います。

詳しい使い方は、scons.orgのユーザーズガイドマニュアルをご参照ください。

Makefileの場合は、コマンドラインで入力するコマンドをそのままテキストファイルに記述する感じですが、SConscriptはかなり特殊な記述になります。

伝統的なhello worldで説明すると、
hello.c
#include <stdio.h>
int main(int argc, char** argv)
{
    printf("Hello world.\n");
}
というソースファイルに対して、

Makefile
hello: hello.c
    gcc -o hello hello.c

SConstruct
Program('hello','hello.c')
という感じになります。
pic090512_01.jpg pic090512_02.jpg

○vectexのMakefileをSConstructに置き換える
(single_file)
Agg、ExpatについてはとりあえずそれぞれのMakefileを使ってコンパイルを行うことにして、作成済みのlibagg.a、libexpat.aファイルを使用する形でSConstructファイルを作ってみました。
env = Environment()
env.Append(CCFLAGS = '-fPIC -shared -O -ansi')
env.Append(CPPPATH = ['.', 'blender','agg-2.5/include','expat-1.95.8/lib'])
env.Append(LIBS = ['m', 'c'])
env.Append(LIBS=File('agg-2.5/src/libagg.a'))
env.Append(LIBS=File('expat-1.95.8/.libs/libexpat.a'))
env.LoadableModule('vectex.so', ['vectex.c',  'vectex_agg.cpp','agg_svg_parser.cpp','agg_svg_path_renderer.cpp','agg_svg_path_tokenizer.cpp'])

先ほどのhello worldのときのProgram('hello','hello.c')に当たる部分は一番最後の行です。
普通の実行ファイルを作成する場合はProgram()というメソッドを使いましたが、BlenderのテクスチャプラグインのようなDLL、Shared Library形式のファイルを作成するにはSharedLibrary()またはLoadableModule()というメソッドを使用します。
複数のソースファイルから一つの実行ファイルを作成するような場合、

Program('ターゲット名',['ソースファイル1','ソースファイル2' ... ,'ソースファイルn'])

というようにソースファイルをリストでまとめて指定します。
ヘッダファイルについては自動的に処理されるのでファイル名を指定する必要はありません。

それ以外の部分は、コンパイル時のオプション指定、インクルードファイルの場所の指定、使用するライブラリの指定を行っています。
ライブラリの指定は本来ならば、

env.Append(LIBS = ['m', 'c', 'agg', 'expat'])
env.Append(LIBPATH = ['agg-2.5/src','expat-1.95.8/.libs'])


というような形でライブラリ名、ライブラリの場所をそれぞれまとめて指定するべきだと思うのですが、今回はExpatのライブラリファイルの置かれている場所にスタティックリンク用、ダイナミックリンク用の両方のファイルがあり、ファイルの名前を直接指定する方法を使っています。

Environment()というのはちょっと説明しづらいですが、env1、env2というように複数の環境を用意して、それぞれにデバッグ用、リリース用の設定を行うというような使い方ができるようです。
後述するように、サブディレクトリのコンパイルを行う際にトップディレクトリの設定を引き継ぐというような使い方もできます。

このSConstructファイルをvectexのトップディレクトリに置いて、コマンドラインから「scons」と実行することでlibvectex.soというファイルが作成されます。
vectex付属のMakefileを使うとvectex.soというファイル名でプラグインが作成されますが、sconsではShared Library形式でファイルを作成する場合、Linux上では自動的に名前の頭に「lib」が付くようになっています。
付かないように設定することもできるようなのですが、このままでも特に問題ないと思いますのでそのままにしています。
pic090512_03.jpg pic090512_04.jpg

○Agg、ExpatのSconsでのコンパイル(multi_file)
vectex付属のMakefileではAgg、ExpatのコンパイルについてはそれぞれのMakefileを呼び出す形で行っています。
Blenderのソースコードに含まれているFFmpegやOpenJPEGなどのライブラリを見てみると、SConscriptファイルが置かれていてSconsを使ってコンパイルを行うようになっています。
Blenderにこれらのライブラリを組み込むのであれば、Sconsでコンパイルできるようにした方がよさそうです。

SconsではトップディレクトリにあるSConstructというファイルとは別に、サブディレクトリにSConscriptというファイルを置いて、階層的にビルド設定ファイルを分離することができます。
この機能を使用してAgg、ExpatをSconsでコンパイルできるようにしてみました。
SConstruct
env = Environment()
SConscript(['agg-2.5/SConscript','expat-1.95.8/SConscript'])
env.Append(CCFLAGS = '-fPIC -shared -O -ansi')
env.Append(CPPPATH = ['.', 'blender','agg-2.5/include','expat-1.95.8/lib'])
env.Append(LIBS = ['m', 'c'])
env.Append(LIBS=File('agg-2.5/libagg.a'))
env.Append(LIBS=File('expat-1.95.8/libexpat.a'))
env.LoadableModule('vectex.so', ['vectex.c',  'vectex_agg.cpp','agg_svg_parser.cpp','agg_svg_path_renderer.cpp','agg_svg_path_tokenizer.cpp'])

先ほどと違っているのは、2行目
SConscript(['agg-2.5/SConscript','expat-1.95.8/SConscript'])

と6、7行目です。
env.Append(LIBS=File('agg-2.5/libagg.a'))
env.Append(LIBS=File('expat-1.95.8/libexpat.a'))

2行目では、Agg、Expatのディレクトリに置いたSConscriptを呼んで、それぞれのライブラリファイルを作成しています。
6、7行目は、Agg、Expatで作成されるライブラリファイルの場所を変更しているため、ライブラリファイルの指定をそれに合わせています。

○AggのSConscript
Agg-2.5/SConscript
env = Environment()
env.Append(CCFLAGS = '-fPIC -c -O3')
env.Append(CPPPATH = ['#agg-2.5/include'])
Export('env')
objs = []
for subdir in ['src', 'gpc', 'src/ctrl']:
    o = SConscript('%s/SConscript' % subdir)
    objs.append(o)
env.Library('agg', objs)
Aggのソースファイルは「src」「gpc」「src/ctrl」という別のサブディレクトリの中に置かれています。
そのため、それらのサブディレクトリにも、それぞれSConscriptファイルを置いて、AggのトップディレクトリにあるSConscriptファイルから呼び出すようにしました。

Export()を使用すると、envで設定したコンパイルオプション、インクルードファイルの場所、使用するライブラリなどの情報を呼び出される側のSConscriptに渡すことができます。
vectexトップディレクトリにあるSConstructとAgg、ExpatのトップディレクトリのSConscriptのenvの間では設定内容は引き継がれていません。

「CPPPATH =」でインクルードファイルの指定をしている部分を見ると、パスの頭に「#」が付けてあります。
このように「#」を付けてパスを指定すると、その部分はSconsのトップディレクトリ(SConstructのある場所)からの相対パスとして処理されます。
これによりサブディレクトリ内でのコンパイルを行っているときでも、問題なくインクルードファイルを読み込むことができるようになります。

最後の行がhello worldのProgram()に対応する部分で、スタティックライブラリを作成するLibrary()メソッドを使用しています。
SharedLibrary()と同様に、作成されるライブラリファイルの名前は、Linuxでは自動的にlibagg.aというように頭に「lib」が追加されます。

Aggの元のMakefileではsrcディレクトリにライブラリファイルが作成されるようになっていますが、今回作成したSconscriptファイルではAggのトップディレクトリにライブラリファイルlibagg.aが作成されるようになります。


「src」「gpc」「src/ctrl」に置くSConscriptファイルは基本的には同じ内容のものですが、「gpc」にあるソースファイルは拡張子が「.cpp」ではなくて「.c」となっているため、その部分だけ違っています。

Agg-2.5/src/SConscript
Agg-2.5/src/ctrl/SConscript
Import('env')
obj = env.Object(Glob('*.cpp'))
Return('obj')
Agg-2.5/gpc/SConscript
Import('env')
obj = env.Object(Glob('*.c'))
Return('obj')
Import()で、envに設定された情報をAggトップディレクトリのSConscriptファイルから受け取っています。
また、Return()を使用することで、コンパイルして作成されるオブジェクトファイルのリストをAggトップディレクトリのSConscriptファイルに返しています。

○ExpatのSConscript
expat-1.95.8/SConscript
env = Environment()
env.Append(CCFLAGS = '-fPIC -Wall -Wmissing-prototypes -Wstrict-prototypes -fexceptions -DHAVE_EXPAT_CONFIG_H -c')
env.Append(CPPPATH = ['#expat-1.95.8','#expat-1.95.8/lib','#expat-1.95.8/xmlwf'])
Export('env')
objs = []
for subdir in ['lib', 'xmlwf']:
    o = SConscript('%s/SConscript' % subdir)
    objs.append(o)
env.Library('expat', objs)
Aggのものとほとんど同じです。
コンパイルのオプションは、元のMakefileからそのままコピーしています。

expat-1.95.8/lib/SConscript
Import('env')
obj = env.Object(['xmlparse.c','xmlrole.c','xmltok.c'])
Return('obj')
expat-1.95.8/xmlwf/SConscript
Import('env')
obj = env.Object(['xmlwf.c','xmlfile.c','codepage.c','unixfilemap.c'])
Return('obj')
Aggのサブディレクトリでは、Grob()メソッドを使用して同じ拡張子のファイルをまとめて処理していましたが、expatでは「.c」ファイルをインクルードファイルとして使うというようなことが行われているため、実際にオブジェクトファイルを出力するファイルだけを列挙するようにしました。
posted by mato at 00:45| Comment(0) | Blender | このブログの読者になる | 更新情報をチェックする
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。