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], [dayflower@example.com]) 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-apr
で APR の場所を指定しなかった場合,インストールされている 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.m4
や find_apr.m4
として添付されているのですが,RHEL の apr-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 (配布物むけに不要なファイル――
Makefile
やconfig.h
――も削除する clean; より強力な clean として使ってらっしゃる方も多いかと)
などがあります。
まとめ
おわりに
あ,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 にあれこれ書いてあったんですが,ちょっと意味がわかりませんでした。