automake & autoconf 続き

さて、例として簡単なQt/EmbeddedをリンクするDLLを作ってみることにする。組込み向けっぽくクロスコンパイルする。

ソース類はこんな感じ。


$ cat src/hoge.cpp
#include
#include "fuga.h"

QPushButton *
hoge()
{
QPushButton *button = new QPushButton("hoge", 0);

return button;
}

$ cat src/foo.cpp
#include
#include "fuga.h"

QPushButton *
foo()
{
QPushButton *button = new QPushButton("foo", 0);

return button;
}

$ cat src/include/fuga.h
#ifndef __FUGA_H__
#define __FUGA_H__

//dummy
#define LIBNAME FOOHOGE
#ifdef __cplusplus
extern "C" {
#endif

extern QPushButton *foo();
extern QPushButton *hoge();

#ifdef __cplusplus
}
#endif

#endif

とても適当な感じだが、ようするに関数fooとhogeをexportするということである。

初期状態でのディレクトリ構成はこのように。


$ find
.
./src
./src/foo.cpp
./src/include
./src/include/fuga.h
./src/hoge.cpp

上のツリーにはMakefileの雛形が全く含まれていない。当然必要となるので、Makefile.am、src/Makefile.amを書く


$ cat src/Makefile.am
lib_LTLIBRARIES = libhogefoo.la
libhogefoo_la_SOURCES = foo.cpp hoge.cpp
libhogefoo_la_CPPFLAGS = -Iinclude
最終的にlibhogefoo.soを作りたいのである。

$ cat Makefile.am
SUBDIRS = src
トップディレクトリにはとりあえずこれだけを書いておく。

Makefile.amの準備ができたらautoscanする。


$ autoscan
autom4te: configure.ac: no such file or directory
autoscan: /usr/bin/autom4te failed with exit status: 1
$ ls
autoscan.log configure.scan src
autoscanすると、configure.scanができているのでconfigure.acの雛形として使う。failしているようだがとりあえず気にしない。
configure.acに書き足す内容としては、

  • automakeを使うので、AM_INIT_AUTOMAKEが、
  • libtoolを使うので、AC_PROG_LIBTOOLが

それぞれ必要である。


$ cat configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)
AC_INIT(libfoo, 0.1, no-define)
AC_CONFIG_SRCDIR([src/foo.c])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC
AC_PROG_LIBTOOL

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT

autoreconfでconfigureをgenerate!

configure.acを書き終えたら、autoheader、aclocal、libtoolize、automake、autoconfをそれぞれ走らせるわけ
だが、最近のautotoolsではautoreconfでまとめてやってくれるようだ。


$ autoreconf --install
configure.ac: installing `./install-sh'
configure.ac: installing `./missing'
src/Makefile.am: installing `./compile'
src/Makefile.am: installing `./depcomp'
$ ls
Makefile.am Makefile.in aclocal.m4 autom4te.cache autoscan.log compile
config.guess config.h.in config.sub configure configure.ac
configure.scan depcomp install-sh ltmain.sh missing src
これで一応の体裁は整った。しかし、当然ながらこれだとQt/Embeddedを探しに行ってくれない。ということで以下をconfigure.ac
に追加する。

# Checks for libraries.
AC_ARG_WITH([qte],
[AS_HELP_STRING([--with-qte=/path/to/qte], [point to location where Qte exists])],
[LDFLAGS=-L$withval/lib
CFLAGS="$CFLAGS -I$withval/include -DQT -DQWS -fno-rtti -D_GNU_SOURCE"
CXXFLAGS="$CXXFLAGS -I$withval/include -DQT -DQWS -fno-rtti -D_GNU_SOURCE"
AC_CHECK_LIB([qte], [qt_get_screen], [],
[AC_MSG_ERROR([Qte was not found!])])

],
[AC_MSG_ERROR([Qte is required!])])

AC_ARG_WITH([qtthreaded],
[AS_HELP_STRING([--with-qtthreaded], [Using thread functions of Qt])],
[CFLAGS="$CFLAGS -DQT_THREAD_SUPPORT"
CXXFLAGS="$CXXFLAGS -DQT_THREAD_SUPPORT"],
[])

--with-qte=[Qteの場所]で、Qt/Embeddedのインストールされている場所を指定することにする。
--with-qtthreadedでQteのスレッドサポートの可否も選べるようにしておく。
また、g++を使うのと、libtoolがstatic libraryも作るためにranlibとarを使うことから以下も追加。前述のように今回はクロスコンパイルなので、configure時にCC、CXXなどの環境変数を適宜設定する必要がある。

AC_PROG_CXX
AC_PROG_RANLIB
AC_CHECK_TOOL([AR], [ar], [:])
完成したconfigure.acはこのようになった。

$ cat configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ(2.59)
AC_INIT(libfoo, 0.1, no-define)
AC_CONFIG_SRCDIR([src/foo.cpp])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC
AC_PROG_CXX
AC_PROG_RANLIB
AC_CHECK_TOOL([AR], [ar], [:])
AC_PROG_LIBTOOL

# Checks for libraries.
AC_ARG_WITH([qte],
[AS_HELP_STRING([--with-qte=/path/to/qte], [point to location where Qte exists])],
[LDFLAGS=-L$withval/lib
CFLAGS="$CFLAGS -I$withval/include -DQT -DQWS -fno-rtti -D_GNU_SOURCE"
CXXFLAGS="$CXXFLAGS -I$withval/include -DQT -DQWS -fno-rtti -D_GNU_SOURCE"
MOC_CMD=$withval/bin/moc
AC_CHECK_LIB([qte], [qt_get_screen], [],
[AC_MSG_ERROR([Qte was not found!])])

],
[AC_MSG_ERROR([Qte is required!])])
AC_SUBST([MOC_CMD])

AC_ARG_WITH([qtthreaded],
[AS_HELP_STRING([--with-qtthreaded], [Using thread functions of Qt])],
[CFLAGS="$CFLAGS -DQT_THREAD_SUPPORT"
CXXFLAGS="$CXXFLAGS -DQT_THREAD_SUPPORT"],
[])


# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT

configure.acに変更を加えた際には、autoreconfすると変更が反映され、configureが新しくなる。ビルドするにはまずconfigureスクリプトを実行するわけだが、今回はターゲットがpower pc linuxである。コンパイラにつくprefixはppc_405-である。(ppc_405-gccppc_405-g++など)
./configureにはデフォルトでクロスコンパイルターゲットを指定する--hostオプションがあるが、config.subが知っているバリエーションでないとうまくいかない。
結局今回は

CC=ppc_405-gcc CXX=ppc_405-g++ RANLIB=ppc_405-ranlib AR=ppc_405-ar ./configure --host ppc-linux --build i586-pc-linux-gnu --with-qte=/opt/qt-2.3.2
とした。

automake & autoconf & libtoolではまった/はまりそうな点

AC_FUNC_MALLOC

ソース中にmallocがある場合、autoscanの結果、configure.acにはAC_FUNC_MALLOCが定義される。このAC_FUNC_MALLOCは、autoconfで展開された結果、mallocの挙動を調査するコードとなってconfigureに含まれる。
その調査の内容だが、malloc(0)がvalidなポインタを返すかどうかをチェックし、もしvalidなポインタを返さない場合には、mallocがrpl_mallocとしてdefineされてしまう。ついでにAC_LIBOBJにmallocが追加される。autoconfの意図としては「malloc.cにて、rpl_mallocという名前で正しい挙動をするmallocラッパーを実装せよ」ということなんだろうが、Makefileを書くのが面倒だからautotoolsを使っている身分としては大きなお世話である。ということで、そもそもmallocに0なんか渡らないと確信できる現実主義者はAC_FUNC_MALLOCをconfigure.acから削ってよし。

Conditional Sources for libraries

サウンド関係など、プラットフォームによって差異が大きいものは、#ifdefで分けるよりもファイルをプラットフォーム毎に用意した方がすっきりする。こういう場合、automakeのマニュアルにはEXTRA_hogehoge_SOURCESを使うといいよと書いてある。


8.1.3.1 Conditional compilation using _LDADD substitutions

Automake must know all the source files that could possibly go into a program, even if not all the files are built in every circumstance. Any files that are only conditionally built should be listed in the appropriate EXTRA_ variable. For instance, if hello-linux.c or hello-generic.c were conditionally included in hello, the Makefile.am would contain:

bin_PROGRAMS = hello
hello_SOURCES = hello-common.c
EXTRA_hello_SOURCES = hello-linux.c hello-generic.c
hello_LDADD = $(HELLO_SYSTEM)
hello_DEPENDENCIES = $(HELLO_SYSTEM)

You can then setup the `$(HELLO_SYSTEM)' substitution from configure.ac:

...
case $host in
*linux*) HELLO_SYSTEM='hello-linux.$(OBJEXT)' ;;
*) HELLO_SYSTEM='hello-generic.$(OBJEXT)' ;;
esac
AC_SUBST([HELLO_SYSTEM])
...

In this case, the variable HELLO_SYSTEM should be replaced by either hello-linux.o or hello-generic.o, and added to both hello_DEPENDENCIES and hello_LDADD in order to be built and linked in.

これをDLLのビルドに当てはめた場合は以下のように記述されている。

8.3.4 Libtool Libraries with Conditional Sources

Conditional compilation of sources in a library can be achieved in the same way as conditional compilation of sources in a program (see Conditional Sources). The only difference is that _LIBADD should be used instead of _LDADD and that it should mention libtool objects (.lo files).

So, to mimic the hello example from Conditional Sources, we could build a libhello.la library using either hello-linux.c or hello-generic.c with the following Makefile.am.

lib_LTLIBRARIES = libhello.la
libhello_la_SOURCES = hello-common.c
EXTRA_libhello_la_SOURCES = hello-linux.c hello-generic.c
libhello_la_LIBADD = $(HELLO_SYSTEM)
libhello_la_DEPENDENCIES = $(HELLO_SYSTEM)

And make sure configure defines HELLO_SYSTEM as either hello-linux.lo or hello-generic.lo.

Or we could simply use an Automake conditional as follows.

lib_LTLIBRARIES = libhello.la
libhello_la_SOURCES = hello-common.c
if LINUX
libhello_la_SOURCES += hello-linux.c
else
libhello_la_SOURCES += hello-generic.c
endif

さて、EXTRA_libhello_la_SOURCESの方法を使ったとしよう。ここで、hello-common.cもhello-linux.cもhello-generic.cもincludeディレクトリにあるhello.hをincludeする場合にはこのようにMakefile.amを書くことになる。

lib_LTLIBRARIES = libhello.la
libhello_la_SOURCES = hello-common.c
libhello_la_CPPFLAGS = -Iinclude
EXTRA_libhello_la_SOURCES = hello-linux.c hello-generic.c

libhello_la_LIBADD = $(HELLO_SYSTEM)
libhello_la_DEPENDENCIES = $(HELLO_SYSTEM)
configure.ac側は普通に考えると以下のようになるだろう。LIBTOOLを使うのでオブジェクトの拡張子は.loである。

...
case $host in
*linux*) HELLO_SYSTEM='hello-linux.lo' ;;
*) HELLO_SYSTEM='hello-generic.lo' ;;
esac
AC_SUBST([HELLO_SYSTEM])
...
これでやってみるとなぜかうまくいかない。hello-generic.cをコンパイルする際に-IincludeがCPPFLAGSから抜け落ちるのだ(筆者の環境はautomake 2.59)。生成されたMakefileをよーく眺めてみると、hello-generic.loではなくてlibhello_la-hello-generic.loに対するルールが書かれているではないか!

libhello_la-hello-generic.lo: hello-generic.c
if $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES)$(INCLUDES) $(libhello_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libhello_la-hello-generic.lo -MD -MP -MF "$(DEPDIR)/libhello_la-hello-generic.Tpo" -c -o libhello_la-hello-generic.lo `test -f 'hello-generic.c' || echo '$(srcdir)/'`hello-generic.c; \
then mv -f "$(DEPDIR)/libhello_la-hello-generic.Tpo" "$(DEPDIR)/libhello_la-hello-generic.Plo"; else rm -f "$(DEPDIR)/libhello_la-hello-generic.Tpo"; exit 1; fi
と、いうことでconfigure.acでの正しい記述は以下のようになる。

...
case $host in
*linux*) HELLO_SYSTEM='libhello_la-hello-linux.lo' ;;
*) HELLO_SYSTEM='libhello_la-hello-generic.lo' ;;
esac
AC_SUBST([HELLO_SYSTEM])
...
しかしちょっとこれはいただけない(それ以前に非常に面倒だ)。たとえば、HELLO_SYSTEMの値を異なるDLLをビルドする二つのMakefile.amで参照しようとしたら破綻する。さらに、このようにlibhello_la-というprefixがつくという動作がどこまで規定されたものかがよくわからない。libtoolのマニュアルに書いてあるのかもしれないが。。面倒なことを気にしたくないのであれば、DLLのビルドの場合はAutomake conditionalsを使用するほうが吉であるようだ。

8.1.3.2 Conditional compilation using Automake conditionals

An often simpler way to compile source files conditionally is to use Automake conditionals. For instance, you could use this Makefile.am construct to build the same hello example:

bin_PROGRAMS = hello
if LINUX
hello_SOURCES = hello-linux.c hello-common.c
else
hello_SOURCES = hello-generic.c hello-common.c
endif

In this case, configure.ac should setup the LINUX conditional using AM_CONDITIONAL (see Conditionals).