[ C++で開発 ]

imake

X Window Systemを構築する際に使用されるmakeツールの一つ。プラットフォーム間の違いを吸収する仕組みを持ち、各プログラムの構築にはMakefileを記述するより簡単な記述でMakefileを生成する。最近ではAutomakeが使われることが増えてきているが、X Window Systemがある限りimakeは不滅でしょう。

imake環境

最近のUNIX系OSは標準でX Window Systemが搭載されているので、そのままでimakeが利用できる環境となっています。XFree86にも含まれているので、Linuxでも勿論利用可能です。

一方、Windowsでは、Cygwinに含まれるXFree86パッケージ群を導入することによりimake環境を利用することができます。

Windows+Cygwin

imake環境は、XFree86-binパッケージに含まれるコマンドの他、Imake用設定ファイルが含まれるXFree86-progパッケージが必要です。

Cygwin用XFree86 4.3.0での問題回避

xmkmfを実行するとエラーが発生します。

ex1$ xmkmf
mv -f Makefile Makefile.bak
imake -DUseInstalled -I/usr/X11R6/lib/X11/config
/usr/X11R6/lib/X11/config/site.def:44: host.def: No such file or directory
/usr/X11R6/lib/X11/config/site.def:146: host.def: No such file or directory
imake: Exit code 1.
  Stop.
ex1$

メッセージのとおり、host.defが見つからないというものです。とはいえ正しい対処方法がわからないので、暫定処置として、/usr/X11R6/lib/X11/config/xf86site.defをhost.defとして置く事にします。(あってる?)

ex1$ cd /usr/X11R6/lib/X11/config
config$ cp xf86site.def host.def
config$ 

これでxmkmfが動くようになりました。

ex1$ xmkmf
mv -f Makefile Makefile.bak
imake -DUseInstalled -I/usr/X11R6/lib/X11/config
ex1$ 

最初の一歩

Cプログラム編(Cygwin)

まずはC言語の単一ソースからなるプログラムhelloをimakeで構築します。ここではWindows+Cygwinで実行していますが、UNIX全般でも共通と思います。

hello.c
#include <stdio.h>

int main(int argc, char* argv[]) {
        printf("Hello, world!\n");
}

hello.cをコンパイルして、実行ファイルhelloをリンクして作成するためのルールをImakefileに記述します。

Imakefile
SRCS = hello.c
AllTarget(hello.exe)
NormalProgramTarget(hello,hello.o,NullParameter,NullParameter,NullParameter)
DependTarget()
注意点
マクロの引数に空白を入れるとNGとなることがあるので、空白は入れないようにします。

まだ一度もxmkmfコマンドを実行していなければ、一回実行します。imakeでは、Makefileを生成するためにmakeコマンドにターゲット"Makefile"を指定して実行します。しかし、最初はMakefileが存在しないため、xmkmfによって"Makefile"ターゲットが記述されたMakefileを生成します。一度Makefileが生成されたら、以後はxmkmfは実行し直すことはないでしょう。

ex1$ xmkmf
mv -f Makefile Makefile.bak
imake -DUseInstalled -I/usr/X11R6/lib/X11/config
ex1$ 

次に、ターゲット"Makefile"を指定してmakeを実行してMakefileを生成します。

ex1$ make Makefile
+ rm -f Makefile.bak
+ mv -f Makefile Makefile.bak
imake -DUseInstalled -I/usr/X11R6/lib/X11/config -DTOPDIR=.
-DCURDIR=.
ex1$

ではいよいよ最初のプログラムをビルドしてみましょう。

ex1$ make
gcc -O2 -fno-strength-reduce -Wall -Wpointer-arith     -I/usr/X11R6/include    -
D__i386__ -DWIN32_LEAN_AND_MEAN -DX_LOCALE -D_X86_ -D__CYGWIN__ -D_XOPEN_SOURCE
-D_POSIX_C_SOURCE=199309L -D_BSD_SOURCE -D_SVID_SOURCE -D_GNU_SOURCE -DNO_ALLOCA
  -DFUNCPROTO=15 -DNARROWPROTO       -c -o hello.o hello.c
hello.c: In function `main':
hello.c:6: warning: control reaches end of non-void function
rm -f hello.exe
gcc -o hello.exe -O2 -fno-strength-reduce -Wall -Wpointer-arith     -L/usr/X11R6
/lib    hello.o         -Wl,--enable-auto-import
ex1$

生成した最終ターゲットファイル、中間ファイルを削除します。

ex1$ make clean
rm -f hello.exe
rm -f *.CKP *.ln *.BAK *.bak *.o core errs ,* *~ *.a .emacs_* tags TAGS make.log
 MakeOut  *.obj *.orig *.rej junk.c *.exe *.dll *.lib *~ "#"*
ex1$

ソースファイルとヘッダファイルの依存関係を生成します。

ex1$ make depend
makedepend  --   -I/usr/X11R6/include    -D__i386__ -DWIN32_LEAN_AND_MEAN -DX_LO
CALE -D_X86_ -D__CYGWIN__ -D_XOPEN_SOURCE -D_POSIX_C_SOURCE=199309L -D_BSD_SOURC
E -D_SVID_SOURCE -D_GNU_SOURCE -DNO_ALLOCA  -DFUNCPROTO=15 -DNARROWPROTO     -DU
SE_MAKEDEPEND -- hello.c
ex1$

すると、Makefileの末尾にソースファイルからヘッダファイルへの依存関係が追記されます。

Makefileの末尾
# ----------------------------------------------------------------------
# dependencies generated by makedepend

# DO NOT DELETE

hello.o: /usr/include/stdio.h /usr/include/_ansi.h /usr/include/newlib.h
hello.o: /usr/include/sys/config.h /usr/include/machine/ieeefp.h
hello.o: /usr/include/cygwin/config.h
hello.o: /usr/lib/gcc-lib/i686-pc-cygwin/3.2/include/stddef.h
hello.o: /usr/lib/gcc-lib/i686-pc-cygwin/3.2/include/stdarg.h
hello.o: /usr/include/sys/reent.h /usr/include/sys/_types.h
hello.o: /usr/include/sys/types.h /usr/include/machine/types.h
hello.o: /usr/include/sys/features.h /usr/include/cygwin/types.h
hello.o: /usr/include/sys/sysmacros.h /usr/include/stdint.h
hello.o: /usr/include/sys/stdio.h

C++プログラム編(Cygwin)

今度はC++言語の単一ソースからなるプログラムhelloをimakeで構築します。

hello.cpp
#include <iostream>

int main(int argc, char* argv[]) {
    std::cout << "Hello, world!" << std::endl;
}

hello.cをコンパイルして、実行ファイルhelloをリンクして作成するためのルールをImakefileに記述します。

Imakefile
SRCS = hello.cpp
AllTarget(hello.exe)
NormalCplusplusProgramTarget(hello,hello.o,NullParameter,
NullParameter,NullParameter)

xmkmfを実行します。

ex2$ xmkmf
mv -f Makefile Makefile.bak
imake -DUseInstalled -I/usr/X11R6/lib/X11/config
ex2$ 

次に、ターゲット"Makefile"を指定してmakeを実行してMakefileを生成します。

ex2$ make Makefile
+ rm -f Makefile.bak
+ mv -f Makefile Makefile.bak
imake -DUseInstalled -I/usr/X11R6/lib/X11/config -DTOPDIR=.
-DCURDIR=.
ex2$

ではいよいよ最初のプログラムをビルドしてみましょう。

ex2$ make
g++ -O2 -fno-strength-reduce    -I/usr/X11R6/include   -D__i386__ -DWIN32_LEAN_A
ND_MEAN -DX_LOCALE -D_X86_ -D__CYGWIN__ -D_XOPEN_SOURCE -D_POSIX_C_SOURCE=199309
L -D_BSD_SOURCE -D_SVID_SOURCE -D_GNU_SOURCE -DNO_ALLOCA       -c -o hello.o hel
lo.cpp
rm -f hello.exe
g++ -o hello.exe -O2 -fno-strength-reduce     -L/usr/X11R6/lib    hello.o
  -Wl,--enable-auto-import
ex2$

となりビルドOKです。

Cygwinでのimakeの問題点

AllTargetで実行ファイル名に展開されない

ImakefileのAllTargetに、hello.exeではなくhelloとUNIXのように指定すると、allを指定してmakeするとエラーとなってしまいます。下記参照。

ex2$ make
g++ -O2 -fno-strength-reduce    -I/usr/X11R6/include   -D__i386__ -DWIN32_LEAN_A
ND_MEAN -DX_LOCALE -D_X86_ -D__CYGWIN__ -D_XOPEN_SOURCE -D_POSIX_C_SOURCE=199309
L -D_BSD_SOURCE -D_SVID_SOURCE -D_GNU_SOURCE -DNO_ALLOCA       -c -o hello.o hel
lo.cpp
gcc   hello.o      -o hello
hello.o(.text+0x30):hello.cpp: undefined reference to `std::cout'
hello.o(.text+0x35):hello.cpp: undefined reference to `std::basic_ostream<char,
std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_o
stream<char, std::char_traits<char> >&, char const*)'
hello.o(.text+0x40):hello.cpp: undefined reference to `std::basic_ostream<char,
std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_os
tream<char, std::char_traits<char> >&)'
hello.o(.text+0x45):hello.cpp: undefined reference to `std::basic_ostream<char,
std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<c
har> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))'
hello.o(.text+0x93):hello.cpp: undefined reference to `std::ios_base::Init::Init
[in-charge]()'
hello.o(.text+0x87):hello.cpp: undefined reference to `std::ios_base::Init::~Ini
t [in-charge]()'
hello.o(.eh_frame+0x11):hello.cpp: undefined reference to `___gxx_personality_v0
'
collect2: ld returned 1 exit status
make: *** [hello] エラー 1
ex2$

エラーは、リンク時にコマンドg++ではなくgccが使用されているために発生しています。

ここで生成されたMakefileの一部を見ると

Makefile
SRCS = hello.cpp

all:: hello

hello.exe:  hello.o
    $(RM) $@
    $(CXXLINK) -o $@ $(CXXLDOPTIONS)  hello.o   $(LDLIBS)   $(EXTRA_LOAD_FLAGS)

となっています。ターゲット無し(all)でmakeすると、ターゲットhelloに依存していますが、ターゲットhelloはMakefile中には記述がないため(hello.exeは文字列が異なるため別ターゲット)、暗黙のhello.oへの依存となります。hello.oは、デフォルトでは$(CC)すなわちgccによってリンクされるのでエラーとなってしまいます。
原因は、AllTarget(hello)と指定した場合、all:: hello.exe と生成されずに all:: hello と生成されることにあります。

Imakefileの記述で回避するのでなければ、Imake.rulesを変更します。

Imake.rules
*** Imake.rules Fri Aug  1 13:04:16 2003
--- Imake.rules.modifiedDefineAllTarget Sun Sep  7 09:04:40 2003
***************
*** 3263,3269 ****
   */
  #ifndef AllTarget
  #define AllTarget(depends)                                            @@\
! all:: depends
  #endif /* AllTarget */

  #ifdef DefineOldLibraryRules
--- 3263,3269 ----
   */
  #ifndef AllTarget
  #define AllTarget(depends)                                            @@\
! all:: ProgramTargetName(depends)
  #endif /* AllTarget */

  #ifdef DefineOldLibraryRules

Imakefileの記述

実行ファイルの作成

SimpleProgramTarget

C++の場合は、SimpleCplusplusProgramTargetとなります。一つのソースから一つの実行ファイルを構築するときに使用できます。

Imakefile
DEPLIBS =
SimpleCplusplusProgramTarget(hello)

このImakefileによって、ソースファイルhello.cpp(拡張子は.cc/.Cでも可)から実行ファイルhelloをビルドするためのMakefileを生成します。Simple(Cplusplus)ProgramTargetによって以下のmakeターゲットが生成されます。

エラー

日本語環境では

make: *** $s` $s' に必要なターゲット ` $s'
を make するルールがありません $s。中止。

英語環境では

make: *** No rule to make target `hello.man', needed by `hello._man'.  Stop.

のようなエラーが発生するかもしれません。日本語のエラーは意味不明ですが英語にすると分かります。hello.manがないが、これを生成するルールが分からないとおっしゃっています。もっともです。ちゃんとmanファイルを書けばよいのですが、回避策としてはtouchコマンド等で空のmanファイルを作成しておきます。

ComplexProgramTarget

C++の場合は、ComplexCplusplusProgramTargetとなります。複数のソースから一つの実行ファイルを構築するときに使用できます。

SRCSシンボルにソースファイルの名前をリストします。
OBJSシンボルにソースファイルからコンパイルされる中間オブジェクトファイルの名前をリストします。
LOCAL_LIBRARIESに指定したライブラリがそのままリンク時に指定されます。
SYS_LIBRARIESには、システムライブラリの中でリンクするものを指定します。そのため、-l形式で指定しています。
DEPLIBSは、ComplexProgramTargetで指定したターゲットを構築するのに依存するライブラリファイル名を指定します。デフォルトではXlib、Xext、Xt、Xmu、Xawが設定されているので不要な場合は空定義しておきます。
Complex(Cplusplus)ProgramTargetは、OBJSに指定したファイルに依存するターゲットを実行するルールを生成します。

Imakefile
SRCS = add.cpp sub.cpp mul.cpp div.cpp
OBJS = $(SRCS:.cpp=.o)
LOCAL_LIBRARIES = $(TOP)/lib/libabc.a
SYS_LIBRARIES = -lxyz
DEPLIBS =
ComplexCplusplusProgramTarget(cal)

以下のmakeターゲットが生成されます。

ComplexProgramTarget_N

C++の場合は、ComplexCplusplusProgramTarget_Nとなります。ComplexProgramTargetでは、一つのMakefileの中で一つのプログラムしかビルド指定することができません。そこで、複数のプログラムをビルドする際にこのマクロルールを使用します。Nには1から10の値を指定することができます。

Imakefile
PROGRAMS = prog1 prog2 prog3

SRCS1 = prog1.cpp add.cpp
OBJS1 = $(SRCS1:.cpp=.o)
DEPLIBS1 = $(DEPXLIB)
SRCS2 = prog2.cpp sub.cpp
OBJS2 = $(SRCS2:.cpp=.o)
DEPLIBS2 =
SRCS3 = prog3.cpp mul.cpp div.cpp
OBJS3 = $(SRCS3:.cpp=.o)
DEPLIBS =
ComplexCplusplusProgramTarget_1(prog1,$(XLIB),NullParameter)
ComplexCplusplusProgramTarget_2(prog2,NullParameter,NullParameter)
ComplexCplusplusProgramTarget_3(prog3,NullParameter,-lm)

NormalProgramTarget

C++の場合は、NormalCplusplusProgramTargetとなります。一つのMakefileでいくつでも指定できますが、指定がやや面倒なことと、生成されるターゲットが少ないので欲するならば手動でターゲットを生成するマクロを記述しなくてはなりません。

Imakefile
SRCS1 = prog1.cpp add.cpp
OBJS1 = $(SRCS1:.cpp=.o)
SRCS2 = prog2.cpp sub.cpp
OBJS2 = $(SRCS2:.cpp=.o)
SRCS3 = prog3.cpp mul.cpp div.cpp
OBJS3 = $(SRCS3:.cpp=.o)

AllTarget(prog1)
NormalCplusplusProgramTarget(prog1,$(OBJS1),$(DEPXLIB),$(XLIB),NullParameter)
InstallProgram(prog1,$(BINDIR))
InstallManPage(prog1,$(MANDIR))

AllTarget(prog2)
NormalCplusplusProgramTarget(prog2,$(OBJS2),NullParameter,NullParameter,NullParameter)
InstallProgram(prog2,$(BINDIR))
InstallManPage(prog2,$(MANDIR))

AllTarget(prog3)
NormalCplusplusProgramTarget(prog3,$(OBJS3),NullParameter,NullParameter,-lm)
InstallProgram(prog3,$(BINDIR))
InstallManPage(prog3,$(MANDIR))

DependTarget()
LintTarget()

make all、make install、make install.man、make depend、make lint は生成しないので、Imakefile中に手動でそれぞれのターゲット生成用マクロを記述しています。

ライブラリファイルの作成

要調査ターゲット

NormalLibraryTarget

Imakefile
SRCS = add.cpp sub.cpp mul.cpp div.cpp
OBJS = $(SRCS:.cpp=.o)
NormalLibraryObjectRule()
NormalLibraryTarget(calc,$(OBJS))
InstallLibrary(calc,$(USRLIBDIR))
DependTarget()

サブディレクトリを再帰的に起動

※要検証

#define IHaveSubdirs
#define PassCDebugFlags
SUBDIRS = proga progb ...

C++の場合、#define PassCPlusPlusDebugFlags(ないので他にあるか調査)

サブディレクトリも対象にMakefileを生成するには

    $ make Makefiles

を実行する