APR を利用したプログラムの autotools 化

APR を利用したプログラムを書いていて,それを配布したい。

という素晴らしいテキストがあるんだけど,これはソースディストリビューションAPR library を同梱する前提でかかれています。さいわいにして,最近の Linux ディストリビューションでは Apache をインストールすると APR library が共有ライブラリとしてインストールされますし,開発者向け APR library パッケージも用意されています。なので,システムにインストールされている APR library を利用する前提でやってみようと思いました。

と思ったら,

というリソースがありました。かなり参考にしつつ,一挙手一投足で書いてみます。

なお,これは autotools 入門ではありません。APR むけにステップが増えているので,これを参考にすると混乱すると思います。

サンプルプログラム

現在日時を取得して表示するプログラムです。

唯一のソース sample.c はこんな感じ。

/* sample.c */
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <apr.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_file_io.h>
#include <apr_time.h>

static apr_pool_t *pool;
static apr_file_t *apr_stdout, *apr_stderr;

int
main(int argc, const char * const *argv, const char * const *envp)
{
    char *str_ctime;
    char *str;

    if (apr_app_initialize(&argc, &argv, &envp) != APR_SUCCESS) {
        fputs("apr_app_initialize() failed", stderr);
        exit(1);
    }
    atexit(apr_terminate);

    apr_pool_create(&pool, NULL);

    apr_file_open_stdout(&apr_stdout, pool);
    apr_file_open_stderr(&apr_stderr, pool);

    str_ctime = apr_palloc(pool, APR_CTIME_LEN);
    apr_ctime(str_ctime, apr_time_now());

    str = apr_pstrcat(pool, "Current time is ", str_ctime, ".\n", NULL);
    apr_file_puts(str, apr_stdout);

    return 0;
}

やや非効率な書き方もしてますが,サンプルということで。

  • apr_app_initialize() の部分は apr_initialize() でもいいと思いますが,いちおう CLI なのでこのようにしました。Windows の場合,このほうがロケールの初期設定とかもやってくれるそうですし。
  • わざわざ apr_file_open_stdout() などで標準入出力を取得していますが,ふつーに Unix で動くプログラムを書くなら stdio の stdout とか fprintf() を使って問題はないです。やはり Windows 環境を考慮する場合,このほうがベターらしい。
  • apr_pstrcat()strcat() とインタフェースが異なるのでやや注意です。最後の NULL を忘れてハマったことがあります。仕様としては LL 系の join() に近く,便利便利。

んで,Makefile.am がこちら。とりあえずこれさえ用意すれば,さまざまなファイルの雛形をよしなに作成してくれるのが automake のうれしいところ。

ACLOCAL_AMFLAGS = -I m4

bin_PROGRAMS = sample

sample_SOURCES = sample.c

ACLOCAL_AMFLAGS という部分は,後述する aclocal のフラグです。といっても,ここで指定したフラグが自動的に aclocal にわたされるわけではありません。

autotools には,ビルド時に configure.ac などの変更を検出して,configure 系のスクリプト等を自動的に再生成(autoreconf)する機能があるのですが,そのような自動再生成の際に aclocal を駆動するときのオプションです。あとで書きますが,手で aclocal を実行する時は aclocal -I m4 のように明示的に指定する必要があります。

bin_PROGRAMS などは通常の automake の記述なので説明は省略します。

configure.ac の雛形をつくる

Makefile.am(と,ソース)から configure.ac の雛形を autoscan コマンドで生成することができます。

$ autoscan

autom4te: configure.ac: no such file or directory
autoscan: /usr/bin/autom4te failed with exit status: 1

初めて実行した際にはこのようなエラーがでますが,無視して構いません。autoscan が,既存の configure.ac のチェック「も」行うためエラーとなっています。

これで雛形となる configure.scan というファイルが生成されます。今回のソースだと下記のような内容です。

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

AC_PREREQ(2.59)
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([sample.c])
AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST

# Checks for library functions.
AC_CHECK_FUNCS([atexit])

AC_CONFIG_FILES([Makefile])
AC_OUTPUT 

この雛形ファイルを configure.ac というファイル名にコピーして,そちらを修正していきます。

$ cp configure.scan configure.ac

元来 AC_INIT の部分を修正すれば最低限の configure.ac となるのですが,今回は APR を利用しているので,その環境をうまくとりこむためのマクロなども書いていきます。

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

AC_PREREQ(2.59)
AC_INIT([sample], [0.1], [[email protected]])
AM_INIT_AUTOMAKE([foreign no-installinfo dist-bzip2 no-dist-gzip])
AC_CONFIG_SRCDIR([sample.c])
AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC

APR_FIND_APR([], [], 1, 1)
APR_SETIFNULL(CC, `$apr_config --cc`)
APR_SETIFNULL(CPP, `$apr_config --cpp`)
APR_ADDTO(CPPFLAGS, `$apr_config --cppflags --includes`)
APR_ADDTO(LDFLAGS, `$apr_config --ldflags`)
APR_ADDTO(LIBS, `$apr_config --libs --link-ld`)

# Checks for libraries.

# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.
AC_C_CONST

# Checks for library functions.
AC_CHECK_FUNCS([atexit])

AC_CONFIG_FILES([Makefile])
AC_OUTPUT 

変更したのは

  • AC_INIT() のプロジェクト名とかあれこれ
  • AM_INIT_AUTOMAKE()
  • APR_* まわり

だけになります。

まず AM_INIT_AUTOMAKE() ですが,autoscan をおこなっただけではこのマクロを追加してくれないので,手でいれておきます。AM_INIT_AUTOMAKE() についていくつかの書き方の流儀があるのですが,現在では AC_INIT() にパッケージ名やバージョンを指定しておいて,無引数の AM_INIT_AUTOMAKE を書くやり方が推奨されるようです。

上記の例では無引数ではなく,

AM_INIT_AUTOMAKE([foreign no-installinfo dist-bzip2 no-dist-gzip])

のようにいくつかの automake のためのフラグを指定しています。

foreign というのは,このディストリビューションGNU などの標準ソースディストリビューションと異なるということを明示するものです。これがないと gnu モードとなり,COPYING や INSTALL などのファイルを要求されてしまいます。

dist-bzip2 というのは,配布物を tar.bz2 として固めるという意味になります。これを記述するだけだと,tar.gz と tar.bz2 の双方が生成されるので no-dist-gzip というフラグを追加して tar.gz を生成しないようにしています。

これらのフラグについて詳細は Changing Automake's Behavior - automake を参照してください。

APR まわりのマクロについて上記から抜粋します。

APR_FIND_APR([], [], 1, 1)
APR_SETIFNULL(CC, `$apr_config --cc`)
APR_SETIFNULL(CPP, `$apr_config --cpp`)
APR_ADDTO(CPPFLAGS, `$apr_config --cppflags --includes`)
APR_ADDTO(LDFLAGS, `$apr_config --ldflags`)
APR_ADDTO(LIBS, `$apr_config --libs --link-ld`)

APR_FIND_APR() というマクロで apr-(1-)config コマンドがどこにあるか,とか,APR library のインストール状況について調べています。また,第一引数と第二引数で空文字列([][][])を指定していますが,これは今回 APR library を添付せずに,システムにインストールされているものをそのまま使うため,このような指定になっています。

第3引数の「1」は,configure 時に --with-aprAPR の場所を指定しなかった場合,インストールされている apr-(1-)config を探してきてくれるようにするためのスイッチです。まあつまり,今回のようなケース(APR をバンドルしない場合)では 1 を指定しておけってこった。

第4引数の「1」は要求する APR library のメジャーバージョン番号です。RHEL 5 では 1.2 系列なので「1」にしておきました。

このマクロの仕様について詳しくは後述する find_apr.m4 を覗いてみてください。

APR_SETIFNULL()APR_ADDTO()コンパイラコンパイルフラグなどを APR むけに調整しています。apr-(1-)config コマンドを使ったことがあるのなら,意味はわかると思います。といいつつ,実は APR とか CFL とか - odz buffer からまるっとコピーしました。

APR をハンドリングするための configure 用 m4 マクロを準備する

と,ここまで当然のように APR_HOGEHOGE などのマクロを利用してきましたが,これは automake / autoconf で標準的に使われるマクロではありません。これらのマクロを使うための「ライブラリ」を別途インストール(添付)する必要があります。

小山さんの例では sinclude() 関数で configure.ac から直接インクルードしていますが,最近の autotools では m4/ というディレクトリを用意してそこに拡張マクロ用ライブラリを添付し,aclocal の引数で -I m4 とすることで,そこからもマクロライブラリをロードするようにするのが一般的なようです*1

今回使ったマクロライブラリは APR library に apr_common.m4find_apr.m4 として添付されているのですが,RHELapr-devel には残念ながら含まれていませんでした。

なのでわたしは下記の SVN レポジトリからファイルを取得しました。

(バージョンが 1.2.7 なのは,RHEL 5 の APR が 1.2.7 なのでそれにあわせたためです。もっと新しいものを利用してもいいと思います)

$ mkdir m4 && cd m4

$ wget http://svn.apache.org/.../apr_common.m4
$ wget http://svn.apache.org/.../find_apr.m4

$ cd ..

上記レポジトリには apr_common.m4, find_apr.m4 のほかに apr_hints.m4, apr_network.m4, apr_threads.m4 なども用意されていますが,お好みで。入れておいても害はありません(バグがあったらストップしてしまいますけど)。

configure で利用される aclocal.m4 を生成する

さて,これで m4 用の環境がととのったので,今回のプロジェクトのための aclocal.m4 マクロファイルを aclocal コマンドで生成します。

さきほど述べたように,手で生成する場合は -I m4 オプションを加える必要があります(おっと,もちろん,APR など利用していなくて通常の autotools を利用したいだけなら,オプションなしで実行して構いません)。

$ aclocal -I m4

m4/apr_common.m4:25: warning: underquoted definition of APR_CONFIG_NICE
m4/apr_common.m4:77: warning: underquoted definition of APR_MKDIR_P_CHECK
...... snip snip snip ......

結構たくさんの warnings が出力されましたが,現在のところ無視して構いません*2

ともかく,これで configure スクリプトの生成に必要となる aclocal.m4 が無事生成されました((autom4te.cache なるフォルダも生成されます。What is autom4te.cache? - Autoconf 参照。))。

config.h.in を生成する

C 向けにアーキテクチャごとの差異を表現してくれる config.h のもととなる config.h.in も生成します。これは autoheader コマンドを実行すれば生成されます。

$ autoheader

ポータビリティを吸収する自分オリジナルの #define を書きたくなる誘惑にかられますが,autoreconf でも再生成されるようですし,これを手でいじるのは得策ではないようです。

どうしても,という場合は AH_* マクロを configure.ac に書けばいいのかな。

Makefile.am (と configure.ac)から Makefile.in を生成する

Makefile の雛形となる Makefile.in も生成しなくてはなりません。これは automake コマンドで生成できます。

$ automake -a -c

configure.ac: installing `./install-sh'
configure.ac: installing `./missing'
Makefile.am: installing `./depcomp'

-a--add-missing と同義で,一般的に必要なファイル(ちらりと触れた gnu スタイルだと COPYING とか)や configure システムで必要となるサブプログラム(install-sh とか)を追加してくれます。

-c--copy と同義で,追加するファイルをシンボリックリンクではなく実体コピーしてくれます。

configure.ac から configure を生成する

最後に configure スクリプトautoconf コマンドで生成しましょう。

$ autoconf

記述に問題がなければ configure スクリプトが生成されるはずです。長い道のりでした。

configure して build

やっとこ configure できるようになりました。

$ ./configure

checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes

...... snip snip snip ......

checking dependency style of gcc... gcc3
checking for APR... yes
  setting CPP to "gcc -E"
  setting CPPFLAGS to " -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE
                        -D_LARGEFILE64_SOURCE -I/usr/include/apr-1 "
  setting LDFLAGS to " "
  setting LIBS to "  -lpthread -ldl -lapr-1"
checking how to run the C preprocessor... gcc -E
checking for egrep... grep -E
checking for ANSI C header files... yes

...... snip snip snip ......

configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands

APR についても無事検出してくれたようです。APR_* マクロはもっと後段に書けばよかったかな?

配布物を生成する

以上で

  • make all
  • make clean
  • make install
  • make uninstall

など,「ありがち」なターゲットをサポートしたポータブルな Makefile が完成しました。

わずか5行(実質2行)の Makefile.am ファイルから,ここまでのものが出来上がるのはちょっぴり感動……てほどでもないですか,最近は。

あと,ご存知のかたも多いかと思いますが,make dist とすると,配布物を tar ボールで固めて生成してくれます。

$ make dist

...... snip snip snip ......

$ ls *tar*

sample-0.1.tar.bz2

automake のオプションに dist-bzip2 no-dist-gzip を指定しておいたので,tar.bz2 が配布物になっています。

この dist 系のターゲットは

  • make dist (tar ボールで固めた配布物を生成)
  • make distdir (tar ボールで固める前の,展開されたディレクトリを生成)
  • make distclean (配布物むけに不要なファイル―― Makefileconfig.h ――も削除する clean; より強力な clean として使ってらっしゃる方も多いかと)

などがあります。

まとめ

  • APR を利用したプロジェクトの autotools 化には,APR library に添付されている m4 マクロを利用すると便利
  • RHEL 5 の apr-devel には,この m4 マクロは添付されていない
  • 外部 m4 マクロを configure で利用したい場合,同一階層におかず,m4/ などディレクトリを掘ってそこに配置したほうがよい
    • そして aclocal の際に aclocal -I m4

おわりに

あ,libtoolize について触れてなかった。個人的に好きくないので触れませんでした。

autoconf だの automake だのややこしいですが,

  • どのような環境でもビルドできるようにするための configure スクリプト
  • configure スクリプトを生成するための autoconf ツールスイート
    • おもに m4 を利用している
  • configure.in とか Makefile.in をいちから手で書くのが面倒なので automake ツールスイートができた

と思えばいいのかな。TeX に対する LaTeX のように,autoconf に対する automake。違ってたらすいません。


実際には Linux 限定ならわざわざ autotools 化する必要はなくて,apr-config や pkg-config を使って Makefile をごりごり書いてもいいと思います。とくにインハウスツールなら。


そもそも論として APR 人気ないというのもありますね。GLib のほうが人気?

みたいな話もあるし。あと,APR を使った「ライブラリ」というのは作りにくい気がする。apr_initialize() は誰が発行するの?とか。

*1:Handling Local Macros - automake 参照

*2:Writing your own aclocal macros - automake にあれこれ書いてあったんですが,ちょっと意味がわかりませんでした。