[ C++で開発 ]
目次
MPC:The Makefile, Project, and Workspace Creatorの略。マルチプラットフォーム対応のプロジェクトにおいて、複数のビルドツールに対応するために、各ビルドツールに対応した設定ファイルを自動生成するツールです。MPC用のプロジェクト設定を記述すると、GNU Make用Makefile、Borland Make用Makefile、nmake用Makefile、Microsoft Visual C++6.0〜8.0のプロジェクトファイル、Microsoft embedded Visual C++3.0/4.0用プロジェクトファイル、GNU Automake用ファイル、などの各種ビルドツール用設定ファイルを生成することができます。
MPC自体はPerl言語で記述されており、実行にはPerlが動作することが必要です。UNIX系のOSならば大抵Perlが利用可能となっています。Windowsの場合は、Perl環境としてCygwinもしくはActive Perlをインストールするのが一般的でしょう。
このMPCツールは、ACEと呼ばれる並列処理・プロセス間通信をカプセル化しオブジェクト指向ネットワークプログラミング機能を提供するオープンソース・フレームワーク製品のビルド用に使用されています。
MPCツールは、ACEおよびTAO(The ACE ORB)の商用サポートを行っている米OCI社が開発・無償提供しています。
上記ページの左側メニュー[Download]を辿ると、MPCのアーカイブをダウンロードできます。
※ZIP形式のアーカイブでも入手可能です。
入手したMPCアーカイブを展開します。MPCというディレクトリの下に展開されます。環境変数PATHにこのMPCディレクトリを追加します。
以下の2つのコマンドが使用できるようになります。
1.は、基本的には一つの実行ファイルまたはライブラリファイルを作成するための「プロジェクト」を生成するためのツールです。
2.は、ワークスペースを生成するツールです。ワークスペースは、複数の「プロジェクト」または「ワークスペース」を束ねる設定を記述します。
ex1ディレクトリに2つのソースファイル、1つのヘッダファイルを作成し、これらから1つの実行ファイルをビルドする場合の設定です。
src/ +-- Time.h +-- Time.cpp +-- main.cpp
MPCのプロジェクト設定ファイルを記述します。
project(timemain) { exename = time_main } |
projectで全体を囲みます。プロジェクト名を()内に記述します。プロジェクト名は省略可能で、その場合は()も省略します。省略時はMPCファイル名から拡張子.mpcを除いた名前がプロジェクト名となります。
ビルドする実行ファイル名を、exename = で指定します。この例ではtime_mainが実行ファイル名となります。(Windowsの場合の拡張子.exeは省略可)
カレント・ディレクトリにあるソースファイル、ヘッダーファイルがデフォルトでプロジェクトに取り込まれます。
例としてVC++ 7.1 (Visual Studio 2003)のビルドファイルを生成します。
ex1> mpc.pl -type vc71 timemain.mpc Generating vc71 output using time.mpc Generation Time: 0s ex1>
以下のVisual C++ 7.1用プロジェクトファイルが生成されます。
同様に、以下の指定で各種ビルドファイルが生成されます。
コマンドオプション | 生成されるファイル | 対象ビルドツール |
---|---|---|
mpc.pl -type nmake timemain.mpc |
Makefile.timemain.mak |
nmake |
mpc.pl -type make timemain.mpc |
Makefile.timemain |
make |
mpc.pl -type automake timemain.mpc |
Makefile.timemain.am |
automake |
mpc.pl -type bmake timemain.mpc |
Makefile.timemain.bmak |
Borland make |
※-typeで指定可能なオプションは現時点で、automake ,bds,bmake, cbx, em3, ghs, html, make, nmake, sle, vc6, vc7, vc71, vc8
上記では、timemain.mpcファイルと同じディレクトリにあるソースファイル、ヘッダーファイルがプロジェクトに取り込まれました。これを、ソースファイル、ヘッダーファイルを指定したい場合は、以下のように記述します。
project(timemain) { exename = time_main Source_Files { Time.cpp main.cpp } Header_Files { Time.h } } |
Source_Files { }の中に、ビルド対象のソースファイルを記述します。
Header_Files { }の中に、ビルド対象のヘッダーファイルを記述します。
コンパイルオプションもプロジェクトファイルに記述して生成することができます。注意点は、コンパイラやOSに固有な設定と共通な設定があるので、これらを使い分けることです。
project(timemain) { exename = time_main includes += /lib/boost_1_34_1 macros += _CRT_SECURE_NO_WARNINGS libpaths += /lib/boost_1_34_1/lib specific(vc9) { lit_libs += boost_date_time-vc90-mt } else { lit_libs += boost_date_time } Source_Files { Time.cpp main.cpp } Header_Files { Time.h } } |
includes) コンパイラのインクルードパスを定義 macros) コンパイラのデファイン定義 libpaths) リンカのライブラリパスを定義 specific(vc)) VC++9のときの lit_libs) リンク・ライブラリ |
ソースファイルのあるディレクトリとは別に実行ファイルを生成するディレクトリを指定することができます。exeoutに出力先ディレクトリを記述します。
project(timemain) { exename = time_main exeout = ../bin : } |
ex1ディレクトリにあるソースファイル、ヘッダファイルを作成し、これらから1つの共有ライブラリファイルをビルドする場合の設定です。
project(timelib) { sharedname = time Source_Files { Time.cpp } Header_Files { Time.h } } |
ビルドする共有ライブラリファイル名を、sharedname = で指定します。拡張子やUNIX系で使用するファイル名先頭の"lib"は記述しません。この例では、VC++プロジェクトファイルではtime.dll(またはtimed.dll)が共有ライブラリファイル名となります。makeファイルでは、libtime.soが共有ライブラリファイル名となります。
ソースファイルのあるディレクトリとは別にライブラリファイルを生成するディレクトリを指定することができます。liboutに出力先ディレクトリを記述します。
project(timelib) { sharedname = time libout = ../lib : } |
ちょっとしたプログラムを作る場合、mpcファイルの記述すら面倒な場合があります。そのときは、mpcファイルを記述しなくても、MPCのデフォルトの動作で対象のビルドツールのプロジェクトファイルを生成させることができます。
main関数を定義したソースファイルが含まれている場合、そのディレクトリにあるソースファイルを全部コンパイル・リンクして一つの実行ファイルを生成するプロジェクトを生成します。プロジェクト名は、そのディレクトリ名に基づき、実行ファイル名は、main関数を定義したソースファイル名に基づきます。
main関数を定義したソースファイルが含まれていない場合、そのディレクトリにあるソースファイルを全部コンパイル・リンクして一つの共有ライブラリファイルを生成するプロジェクトを生成します。
なお、インクルードパス、ライブラリパス、リンクするライブラリ名などは、mpc.plのコマンドラインから-value_projectオプションで指定できますが、ここまでするなら素直にmpcファイルを記述するべきです。
各種ライブラリを使ったプロジェクトの場合、追加のインクルードパス、ライブラリパス、リンクするライブラリ、ビルド前や後に実行するコマンドなどを指定する必要があります。
MPCは標準でBoostライブラリを使うための設定を保持しています。ただしデフォルトではBoostの設定が無効になっているので、これを有効にする必要があります。有効にするにはいくつかの方法がありますが、ここではその中から2つ紹介します。
1.は、global.featuresファイルの中に「boost = 0」の記述があるので、これを「boost = 1」に修正します。
2.は、コマンドラインで-features boost=1 を指定します。
または、「boost = 1」を記述したテキストファイル(例:myproj.features)を作成し、コマンドラインで-feature_file
myproj.features を指定します。
Boostを使用するプロジェクト定義で、boostの基底プロジェクトを継承するように記述します。
project(timelib) : boost_base { sharedname = time } |
boostの基底プロジェクトは、リンクするライブラリ種類に応じて複数用意されているので、適切なものを選択します。複数ライブラリを使用するときは、それぞれ継承するプロジェクトに指定します。
基底プロジェクト名 | 基底プロジェクト名 | 基底プロジェクト名 | |||
boost_base |
boost_program_options |
boost_test_exec_monitor |
|||
boost_date_time |
boost_python |
boost_thread |
|||
boost_filesystem |
boost_regex |
boost_unit_test_framework |
|||
boost_iostreams |
boost_serialization |
boost_wave |
|||
boost_prg_exec_monitor |
boost_signals |
これらboostの基底プロジェクトを使用すると、インクルードパス、ライブラリパス、リンクするライブラリ名に以下の環境変数を使用するプロジェクトが生成されます。
プロジェクトを実行するときは、これらの環境変数を定義しておきます。
rpmパッケージでインストールしたboostは、/usr/includeの下にboost/xxx.hppが置かれ、/usr/libの下にlibboost-xxxが置かれます。また、ライブラリ名はlibboost_regex.soのようにバージョン番号はファイル名には付きません。したがって、上記環境変数を設定していなくても、コンパイル・リンクが通ってしまいます。
クライアント・サーバのプログラムを作成するときなど、クライアント用実行ファイル、サーバ用実行ファイルと複数の実行ファイルを生成する場合の例を見てみます。
プロジェクトを束ねるディレクトリをcsappとして、そのディレクトリ配下にclientおよびserverディレクトリを作成します。
csapp +--- client | +--- src | +--- client.cpp +--- server +--- src +--- server.cpp
clientとserverそれぞれのディレクトリ下にプロジェクトファイルを記述します。
project(client) { exename = client Source_Files { client.cpp } } |
project(server) { exename = server Source_Files { server.cpp } } |
csapp直下にワークスペースファイルを記述します。
workspace { } |
UNIXのbash上でGNU Makefileを生成するコマンド実行例を以下に示します。
csapp$ mwc.pl -type make -recurse csapp.mwc Generating 'make' output using default input Generation Time: 0s csapp$
複数のライブラリファイルからなるクラスライブラリを構築するディレクトリ構成の場合の例を見てみます。
この例でのディレクトリ構成想定事項は以下のとおりです。
以下にABCライブラリのディレクトリ構成とMPCのワークスペースファイル・プロジェクトファイルを記述します。
abclib | +--- include | +--- abc | +--- alfa | +--- bravo | +--- charlie +--- src | +--- abc | +--- alfa | | +--- alfa.mpc | +--- bravo | | +--- bravo.mpc | +--- charlie | +--- charlie.mpc +--- abclib.mwc | +--- lib | +--- makeinclude +--- abclib.mpb
makeincludeには、各MPCファイルで共通する設定を記述した共通ファイルを置きます。
project(alfa) : abclib { sharedname = alfa Source_Files { Alfa.cpp } Header_Files { Alfa.h } } |
このalfaライブラリ生成用プロジェクトファイルは、abclibという基底プロジェクトの設定を継承します。
動的共有ライブラリ・ファイルとしてalfaという基幹名で生成します。UNIX系ならlibalfa.soとなります。
このプロジェクトを構成するソースファイルは、Alfa.cppで、ヘッダーファイルはAlfa.hです。
※ヘッダーファイルAlfa.hは別ディレクトリにあるので、このファイル名のみの記述でよいのかどうかは要調査。
project { includes += $(ABCLIB_ROOT)/include libout = $(ABCLIB_ROOT)/lib specific(make) { compile_flags += -Wall } } |
公開ヘッダーファイルはソースとは別ディレクトリに置くため、コンパイル時にはインクルードパスの指定が必要です。このインクルードパスはプロジェクトで共通なので、各プロジェクトファイルにコピー&ペーストするよりは共通ファイルに一元化すべきです。
同様にライブラリパスも共通です。
また、コンパイルオプションで共通するものもここに記載します。
workspace { } |
UNIXのbash上でGNU Makefileを生成するコマンド実行例を以下に示します。
abclib$ mwc.pl -type make -include ./makeinclude abclib.mwc Generating 'make' output using abclib.mwc Generation Time: 0s abclib$
-typeオプションでmake、すなわちGNUmake用Makefileを生成します。
各mpcファイルまたは共通のmpbファイルに、プロジェクトトップディレクトリを表すABCLIB_ROOT変数を使用してincludeディレクトリ等を指定しています。-relativeオプションを指定しなければ、この変数はそのまま生成されるMakefile等のプロジェクトファイルに変数のまま反映されます。そのため、ビルド実行時にこの環境変数をセットする必要があります。
CPPFLAGS = $(PICFLAGS) $(GENFLAGS) -Wall -D_REENTRANT -I"$(ABCLIB_ROOT)/include" |
一方、-relativeオプションで変数の内容を指定しておくと、この変数はMakefile等のプロジェクトファイルでは相対ディレクトリに展開されます。
abclib$ mwc.pl -type make -include ./makeinclude -relative ABCLIB_ROOT=. abclib.mwc Generating 'make' output using abclib.mwc Generation Time: 0s abclib$
CPPFLAGS = $(PICFLAGS) $(GENFLAGS) -Wall -D_REENTRANT -I"../../../include" |
CORBAのIDLコンパイラや、QtのMOCコンパイラなど、特定のツール固有のコマンドを実行するカスタムコマンドを定義します。
Define_Custom文を使って、MPCのプロジェクトファイル中に定義します。
ACE/TAOの場合、MPCが標準で組み込まれているので、ACE/TAOのMPCを使うとユーザ側でカスタムコマンドを定義せずに済みます。しかし、MPCを別にインストールしていたり、ACE/TAOのパッケージ版を使用してMPCが含まれていなかったりした場合、自前でIDLコンパイルのカスタムコマンドを定義する方法を知っていると対処が容易です。
Define_Custom(IDL) { automatic = 1 command = tao_idl inputext = .idl source_pre_extension = C, S header_pre_extension = C, S inline_pre_extension = C, S source_outputext = .cpp, .cxx, .cc, .C header_outputext = .h, .hpp, .hxx, .hh inline_outputext = .inl keyword idlflags = commandflags } |
IDL_Filesのカスタムコマンド定義。 automatic=1指定で、IDL_Filesで指定したファイルからの生成物は、自動的にSource_Files, Header_Files, Template_Filesなどに追加する。 カスタムコマンドはtao_idl。 対象入力ファイルの拡張子は.idl。 生成ソースファイルの拡張子直前にはCおよびSが付加 生成ヘッダファイルの拡張子直前にはCおよびSが付加 生成インラインファイルの拡張子直前にはCおよびSが付加 生成ソースファルの拡張子 生成ヘッダファイルの拡張子 生成インラインファイルの拡張子 IDLカスタムコマンドのコマンドオプションは、idlflagsで指定可能とする。 |
tao_idlの生成物に合わせて、source_pre_extension他を指定しています。この名前のルールでMakefileが出力されます。
注記)実際のACE/TAOに含まれるカスタム定義はもっと複雑になっています。
MPCを使うにあたって出くわした問題とその解決についてのメモです。
Cygwin環境では、ライブラリlibdlがありません。Cygwinでは、libdlが提供する関数はlibcygwinに含まれているので、/usr/lib/libcygwin.aにシンボリックリンクを作成する方法で回避します。
$ cd /usr/lib $ ln -s libcygwin.a libdl.a $
VC++では、プロジェクト設定に「デバッグビルド」、「リリースビルド」の構成が定義され、ビルド時にどちらでビルドするかを選択することができます。
一方、make(Makefile)プロジェクトでは、デフォルトでは最適化オプション"-O"が指定され、デバッグオプション"-g"は指定されません。
生成されるMakefile.xxx の中で以下の変数でオプションが定義されます。
LIBSUFFIX = GENFLAGS = -O |
この設定は、MPCのmake用テンプレート設定ファイルに定義されているものです。
... configurations = Release ... Debug { lib_modifier = d genflags = -g } Release { optimize = 1 genflags = -O } |
Makefileを生成する元ネタ定義であるconfigurations属性がReleaseに設定されているので、生成されるMakefileはReleaseで定義されているgenflagsの値が適用されます。
mwc.plコマンドを実行するときに、-value_templateオプションを指定して、configurations属性を上書きする
$ mwc.pl -type make -value_template configurations=Debug ... $
生成されるMakefile.xxxは以下になります(抜粋)。
LIBSUFFIX = d GENFLAGS = -g |
デバッグビルドとリリースビルドと頻繁に使い分ける場合、都度MPCでMakefileを生成し直すのは手間です。そこで、Makefileにはデバッグビルド、リリースビルド双方の設定を生成して、makeを実行するときにデバッグビルドかリリースビルドかを指定できるようにします。
$ mwc.pl -type make -value_template "configurations=Debug Release" ... $
生成されるMakefile.xxxは以下になります(抜粋)。makeマクロ変数CFGの値に応じて、デバッグビルドかリリースビルドかを切り替えられるようになっています。
CFG = Debug ifeq ($(CFG), Debug) ... LIBSUFFIX = d GENFLAGS = -g ... endif ifeq ($(CFG), Release) ... LIBSUFFIX = GENFLAGS = -O ... endif |
$ make // Debugビルドが行われる $ make CFG=Debug // Debugビルドが行われる $ make CFG=Release // リリースビルドが行われる
上記では、デフォルト(CFG変数を指定しない場合)ではデバッグビルドですが、デフォルトでリリースビルドにしたい場合は、MPCの-value_templateオプションのconfigurations=の後の並びを変えて、デフォルトにしたい構成を先に書きます。
$ mwc.pl -type make -value_template "configurations=Release Debug" ... $
CFG = Release ifeq ($(CFG), Release) ... endif ifeq ($(CFG), Debug) ... endif |
MPCが用意するGNU make用のMakefile設定は、デバッグビルドしたライブラリは、ファイル名に'd'が付きます。(例:libfoo.soがリリースビルドであれば、そのデバッグビルドはlibfood.soとなる)
これを、ファイル名は変えたくない場合、出力先ディレクトリを変えることで対応できる方法を考えてみます。
まず、makedll.mpt をカスタマイズします。
... configurations = Release Debug ... Debug { output_dir_ext = _debug genflags = -g } Release { output_dir_ext = _opt optimize = 1 genflags = -O } |
次に、make.mpdをカスタマイズします。
... SHTARGETDIR = <%if(dllout)%><%dllout%><%output_dir_ext%><%else%><%libout%><%output_dir_ext%> <%endif%><%slash%><%targetoutdir%> |
この2つのカスタマイズしたファイルを読み込ませ、mwcを実行します。
$ mwc.pl -type make -template make2 -ti dll:make2dll -value_template \ "configurations=Release Debug" ...
リンクオプションは、makeの場合、生成されるMakefileの中でLDFLAGS=で設定されます。通常はリンクするライブラリのパスとライブラリ名が反映されますが、他にもリンク時に指定したいオプションがあった場合、ここに反映するため、linkflagsを指定します。
project { specific(make) { linkflags += -pg } ... } |
Makefileを生成するための雛形となるmake.mpdの中に、linkflagsが定義されていれば指定するようになっています。
LDFLAGS =<%if(libpaths)%>...中略...<%if(linkflags)%> <%linkflags%><%endif%>...後略 |
makeの生成に関する設定が書かれたmakedll.mptには、コンパイラ種類によってデフォルトのlinkflagsが定義されています。
... SunCC { linkflags = -library=Cstd -library=Crun ... } acc { linkflags = -Wl,+s ... } |
LinuxでのGCCの場合、linkflagsは設定されていません。
Emacsでflymakeモードを有効にしているとき、Emacsはソースファイルを開いた場所でmakeをcheck-syntaxターゲットで実行し、エラーチェックを行います。そこで、MPCで生成するMakefileにcheck-syntaxターゲットを追加する方法を探しました。
上述の変更対応で実現できました。
$ export CUSTOM_TARGETS=check-syntax $
--- make.mpd.orig 2008-11-16 22:19:39.000000000 +0900 +++ make.mpd 2009-02-24 00:14:59.000000000 +0900 @@ -301,6 +301,9 @@ -$(RM) <%targetoutdir%><%obj_dir%> <%endif%> +check-syntax: + $(COMPILE.cc) $(EXPORTFLAGS) -Wall -Wextra -pedantic -fsyntax-only ${CHK_SOURCES} + <%if(prebuild)%> __prebuild__: @<%eval(prebuild)%> |
Makefile.xxx はテンプレートファイルmake.mpdが雛形を定義しているので、これにターゲットを追加します。
環境変数CUSTOM_TARGETS指定は忘れてしまってflymakeが無効になるのも面倒なので、MPCの提供ファイル自体を修正してしまいます。
--- MakeWorkspaceCreator.pm.orig 2008-11-16 22:19:39.000000000 +0900 +++ MakeWorkspaceCreator.pm 2009-02-24 00:04:56.000000000 +0900 @@ -23,7 +23,7 @@ # Data Section # ************************************************************ -my($targets) = 'clean depend generated realclean $(CUSTOM_TARGETS)'; +my($targets) = 'clean depend generated realclean check-syntax $(CUSTOM_TARGETS)'; |
Makefileはテンプレートファイルはなく、PerlスクリプトのMakeWorkspaceCreator.pmが直接生成しているので、Perlスクリプトを修正します。
この記述は、MPCのFAQに記載されているものですが、MPC 3.6.0で確認したところ、以下を記述しても有効になりません。
workspace { cmdline += -value_template "configurations=Release Debug" -include $(HOME)/MPC } |
MPCは設定ファイルを読み込みます。デフォルトでは、MPCのディレクトリにある設定ファイルを読み込みますが、プロジェクト固有の設定ファイルを読み込むように設定しておくことで、プロジェクト固有の指定を行います。
(1) $MPC_ROOT/config/base.cfgファイルを新規に作成し、以下内容を記述する
$PROJECT_ROOT=$PROJECT_ROOT/MPC/config
(2) プロジェクトのディレクトリを環境変数PROJECT_ROOTに設定する
(3) $PROJECT_ROOT/MPC/config ディレクトリにMPC.cfgファイルを作成する
ここで、環境変数PROJECT_ROOTは名前は別に何でも構いません。
MPC.cfgの書き方
default_type = make // コマンドラインの -type make を指定
指定できる設定は、以下
command_line default_type dynamic_types includes logging main_functions verbose_ordering
詳しくは、MPCのドキュメント参照
Source_Files {} を複数定義して、Source_Files内にbuildflagsで固有にコンパイルオプションを指定します。
project(alfa) : abclib { sharedname = alfa Source_Files { buildflags += -finstrument-functions AlfaX.cpp } Source_Files { AlfaY.cpp AlfaZ.cpp } } |
複数のプロジェクトを収容するワークスペースを構築する際、プロジェクトのビルド順番はMPC実装依存(アルファベット順にみえる)です。そこで、ライブラリを構築するプロジェクトファイル(*.mpc)に、依存するプロジェクト名をafter += で記述します。
project(bravo) : abclib { sharedname = bravo libs += alfa after += alfa Source_Files { BravoFoo.cpp BravoBar.cpp } } |
ライブラリ alfa をリンクするように指定すると、デバッグビルドを行うと alfad と末尾に'd'付くライブラリをリンクしようとしてファイルが見つからずリンクエラーとなります。また、デバッグビルドの場合、自分のライブラリ名もsharedname=bravoと指定していてもデバッグビルドでは、bravodと末尾に'd'が付いた名前になっています。
これは、同じディレクトリに異なるビルドオプションで生成したライブラリを共存させるため、および同じビルドオプションで生成したライブラリ同士をリンクするための機構です。特にWindows VC++では、デバッグとリリースで互換性のないライブラリができるので(newとdeleteが置き換えられるのは有名な話)、この機構が必要になっています。
project(bravo) : abclib { sharedname = bravo libs += alfa ... } |
外部のライブラリは、デバッグビルド版が提供されていないことがよくあります。その場合は、リンク指定をlibsではなく、lit_libsで指定します。
project(bravo) : abclib { sharedname = bravo lit_libs += alfa ... } |
mwc.pl/mpc.plのコマンドラインオプション-value_templateで、lib_modifier属性をブランクに設定します。
$ mwc.pl -type make -value_template lib_modifier= ... $
デバッグビルド指定とともに使うときは、
$ mwc.pl -type make -value_template configurations=Debug -value_template lib_modifier= ... $
のように複数回-value_templateオプションを指定します。
特定のディレクトリを除外したい場合は、
mwc.plの-excludeオプションで、除外したいディレクトリまたはmpcファイル名を指定します。複数除外するときはカンマで区切って列挙します。
workspace { } |