クロス開発でlibtoolを使用すると、あの忌々しい疑似ライブラリ(.laファイル)に正しくない絶対パスが埋め込まれてしまい、コンビニエンスライブラリが不便なライブラリになってしまう。ウェブで検索しても、あんまり回避方法を見つけられなかったので、ここにまとめておく。

問題の詳細

例えば、libfoo.laというライブラリをlibtoolとautomakeでクロスコンパイルしてインストールする場合、ホストシステムのライブラリをインストールする先は、ビルドシステムのインストール先(/usr/local/lib)とは異なる場所になる。ここではそれを/w/usr/local/libとしておく。ただし、./configure --prefix=/w/usr/localとするのではなく、make DESTDIR=/w all installでビルド、インストールする。

このようにインストールすると、.laファイルの中のlibdirは次のようになる。

libdir='/usr/local/lib'

また、foo_la_LDFLAGSにライブラリのサーチパスを指定したり、foo_la_LIBADDに依存するライブラリを指定してコンビニエンスライブラリを作成すると、dependency_libsの値には、次のようにいろいろな環境の絶対パスが埋め込まれる(注: LDFLAGSには-R$(libdir) -L$(DESTDIR)$(libdir)を、LIBADDには$(top_builddir)/libbar.la -lbazを指定した)。

dependency_libs='-R/usr/local/lib -L/w/usr/local/lib /usr/local/lib/libbar.la -lbaz'

まずlibdirの値はホストシステムでは正しいが、ビルドシステムでは正しくない。そのため、インストールされたlibfoo.la-L/w/usr/local/lib -lfooでリンクしようとすると、「libtool: warning: library `libfoo.la' was moved.」と警告が出る。つまり、この値はビルドシステムからみたときに/w/usr/local/libでなければならない。もちろん、ホストシステムでセルフコンパイルするときは/usr/local/libでなければならない。

警告は無視すればいいが、dependency_libsの方はもっと深刻である。-Lで指定するサーチパスはビルドシステムのものなので、ホストシステムでは正しくない。また、依存するライブラリlibbar.laのパスはホストシステムでは正しいが、ビルドシステムでは正しくない。そのため、このインストールされた疑似ライブラリlibfoo.laをビルドシステムでリンクすると、「/usr/local/lib/libbar.laがみつからない」とエラーになってしまう。libbar.la/w/usr/local/libにあるのだが...。

すべてはMontaVistaのツールチェインから...

ウェブで検索してもよくわからず、コンビニエンスライブラリの使用は中止、警告も無視することで回避しようと考えていた。しかし、ある日MontaVistaのツールチェインを眺めていたとき、.laファイルのlibdirの行が次のようになっていることに気付いた。

libdir=$CROSS_COMPILE_PREFIX'/usr/lib'

ん。これはどうやるんだ。CROSS_COMPILE_PREFIXで検索してSmoother cross compilation of 1.3.12を発見する。このなかの次の部分で目が覚めた。

Problem B) autoconf

All the new code and much of the higher end code (gnome, etc) depend on
libtool. My personal feelings about this obscure shell script aside,
it's got problems in a cross compilation environment - typically libtool
will fail on a link step because it decodes a .la file, then does magic
parsing of a -lsomething into a /usr/lib/something.so - which is
typically a native host library and not what I want.

It would be really great to just turn off -l parsing in libtool and let
the gcc compiler figure it out. I couldn't figure it out.

so I wrote a script that postprocesses and demangles the libtool
generated .la file.

It gets rid of /usr/lib/*.la references, the -L references, any
hardcoded paths to -l in the dependency_libs variable, and makes
libdir=$CROSS_COMPILE_PREFIX'/usr/lib', etc.

Is there a better solution?

そう、要は自分でスクリプトを書けばいいのだ。

疑似ライブラリを変換するスクリプト

変換スクリプトは簡単に書ける。.laファイル自体はシェルスクリプトからそのまま.コマンドで入力できるので、特にパースの処理も不要である。libdirの処理は上記の引用をそのまま適用した。dependency_libsについては、forコマンドで複数の引数を順にみていく。絶対パスで指定された疑似ライブラリ/*/libBAR.la-lBARに変換する。また、-L/w/PATH-L'$CROSS_COMPILE_PREFIX'/PATHに変換する。

具体的には、次のような変換スクリプトを用意すればよい。なお、libtoolが.laファイルの2行目の「libtoolのバージョン」を参照するため、.laファイルの先頭2行のコメントは削除してはならない。

#!/bin/sh
filename=$1
. $filename

new_dependency_libs=
for lib in $dependency_libs ; do
  case $lib in
  /*/lib*.la)
    lib=${lib##*/}
    lib=${lib#lib}
    lib=-l${lib%.la}
    ;;
  -L/*)
    lib=-L\'\$CROSS_COMPILE_PREFIX\'${lib#-L$DESTDIR}
    ;;
  *)
    ;;
  esac
  new_dependency_libs="$new_dependency_libs $lib"
done

cat > $filename << EOF
# ${filename##*/} - a libtool library file
# Generated by ltmain.sh - GNU libtool 1.5.26 (1.1220.2.493 2008/02/01 16:58:18)
dlname='${dlname}'
library_names='${library_names}'
old_library='${old_library}'
dependency_libs='${new_dependency_libs}'
current=$current
age=$age
revision=$revision
installed=$installed
shouldnotlink=$shouldnotlink
dlopen='$dlopen'
dlpreopen='$dlpreopen'
libdir=\$CROSS_COMPILE_PREFIX'${libdir}'
EOF

先ほどのlibfoo.laをこのスクリプトで変換すると、次のようになる。

dependency_libs='-R/usr/local/lib -L'$CROSS_COMPILE_PREFIX'/usr/local/lib -lbar -lbaz'
libdir=$CROSS_COMPILE_PREFIX'/usr/local/lib'

このスクリプトをdemangleとしてソースツリーに追加した。

インストール後フックのターゲット

ライブラリlibfoo.laのインストール後に、インストールされた疑似ライブラリを「デマングル」必要がある。これはMakefile.amで、次のようにinstall-exec-hookターゲットを利用した。

install-exec-hook:
    $(top_srcdir)/demangle $(DESTDIR)$(libdir)/libfoo.la

libtoolコマンドに環境変数を渡す

ここまでできれば、ほぼ完了。あとは、この「デマングられた」疑似ライブラリを(もちろんlibtoolで)リンクするときのことを考える。libfoo.laをリンクするプログラムfooをビルドするときに、環境変数CROSS_COMPILE_PREFIXを設定してからlibtoolを実行するようにしたい。これもfooを(クロスコンパイルで)ビルドするMakefile.amのなかで、次のように変数LIBTOOLをオーバライドした。

LIBTOOL = env CROSS_COMPILE_PREFIX=$(DESTDIR) $(SHELL) $(top_builddir)/libtool

libtool 1.5のバグ

libtool 1.5では疑似ライブラリをリンクするとき、リンクする疑似ライブラリのなかのdependency_libsに含まれる-Rフラグが捨てられてしまう、というバグがある。そのため、結局のところ、コンビニエンスライブラリに「依存するライブラリ」を埋め込んでも、そのライブラリをリンクするときに、「実行時に共有ライブラリを探すパス」をすべて指定しなければならない。つまり、せっせと-Rを書いても、最後には依存するすべての共有ライブラリを把握しておく必要がある。すべての共有ライブラリを同じところに置ければいいが、なんだかよくわからないサードパーティの共有ライブラリに限って、特定のパスに配置せよと求められることがよくある(クロス、というか組み込みでは)。libtoolのコンビニエンスライブラリは、共有ライブラリに関してはコンビニエンスではない。

libtoolがこのバグを抱え込んでしまった過程をみると、オープンソース開発の典型的な落とし穴にはまったことがわかる。そして、このようなバグを長く放置しているlibtoolがバベルの塔になってしまったことを露呈している。一部のユーザーが満足するための(そしてオリジナルの思想にも仕様にもマッチしない)パッチが提案され、なにも理解していないメインテナが「広く利用できるように」そのパッチを適用し、互換性も確認せずにリリースする。ユーザはよくわからないまま従来の機能が使えなくなり、おかしな回避策を選ぶ。

libtool 1.4.3でもdependency_libs-Rフラグはネイティブのリンカに渡されない。例えば、GNU ldには-rpathオプションに変換して渡す(直接ldを実行するわけではないので、gccの-Wlオプションを通して渡すが)。なのに、ネイティブのリンカに-Rが渡らないようにするために「えーい、dependency_libsに含まれる-Rは捨てちゃえ」と修正することが正しいわけがない。-Rをネイティブのリンカに渡すために変換するところを直すべきだろう(少なくても、お前のプラットフォームの場合だけ捨てろよ)。

こういうアホどもが開発に関わっている限り、libtoolは永遠にクソツールだろう。