クロス開発で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_
に変換する。
具体的には、次のような変換スクリプトを用意すればよい。なお、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
に含まれる-R
フラグが捨てられてしまう、というバグがある。そのため、結局のところ、コンビニエンスライブラリに「依存するライブラリ」を埋め込んでも、そのライブラリをリンクするときに、「実行時に共有ライブラリを探すパス」をすべて指定しなければならない。つまり、せっせと-R
を書いても、最後には依存するすべての共有ライブラリを把握しておく必要がある。すべての共有ライブラリを同じところに置ければいいが、なんだかよくわからないサードパーティの共有ライブラリに限って、特定のパスに配置せよと求められることがよくある(クロス、というか組み込みでは)。libtoolのコンビニエンスライブラリは、共有ライブラリに関してはコンビニエンスではない。
libtoolがこのバグを抱え込んでしまった過程をみると、オープンソース開発の典型的な落とし穴にはまったことがわかる。そして、このようなバグを長く放置しているlibtoolがバベルの塔になってしまったことを露呈している。一部のユーザーが満足するための(そしてオリジナルの思想にも仕様にもマッチしない)パッチが提案され、なにも理解していないメインテナが「広く利用できるように」そのパッチを適用し、互換性も確認せずにリリースする。ユーザはよくわからないまま従来の機能が使えなくなり、おかしな回避策を選ぶ。
libtool 1.4.3でもdependency
の-R
フラグはネイティブのリンカに渡されない。例えば、GNU ldには-rpath
オプションに変換して渡す(直接ldを実行するわけではないので、gccの-Wl
オプションを通して渡すが)。なのに、ネイティブのリンカに-R
が渡らないようにするために「えーい、dependency
に含まれる-R
は捨てちゃえ」と修正することが正しいわけがない。-R
をネイティブのリンカに渡すために変換するところを直すべきだろう(少なくても、お前のプラットフォームの場合だけ捨てろよ)。
こういうアホどもが開発に関わっている限り、libtoolは永遠にクソツールだろう。