ライブラリのpath指定がやっぱりよくわからん [aravis]
困ったことが起こった。自分用のframeworkを作って、自分で作ったアプリケーションバンドルに内包させた。そのアプリはビルドしたマシンでしか動かなかった。調べてみるとframeworkの中にさらにgnu由来のライブラリを埋め込んだんだけど、そのライブラリがさらに依存しているライブラリが見つからないらしい....
ややこしいので、具体的に書くと、 のようにA.appというアプリバンドルの中にFrameworksというディレクトリがあって、そこに自作のF.frameworkを内包している。F.frameworkはそいつが使うためのL1.dylibというライブラリをLibrariesというディレクトリに持っている。L1.dylibはL2.dylibという別のライブラリを参照しているので、同じLibrariesディレクトリにコピーした。
このL2.dylibというライブラリの参照が実行時に解決されないといけない。ビルドマシンではこれがLibrariesディレクトリの中のL2.dylibではなくて、もとのL2.dylibがあった位置を参照していた。otoolというコマンドで
しかし、いろいろなライブラリを同じように埋め込んで、依存関係を手動で切り替えるのは、ライブラリが増えてくると非常に煩わしい。そもそもライブラリは検索の優先順位があって本体に埋め込まれた絶対pathにないと他のところを探すはずだった。macOSではまた違っているようである。
stackOverflowやその他のサイトでは断片的な情報(わりとみんなworkaroundが得られればそれで良しとしてる)しか手に入らなかった。めんどくさいけどしょうがないのでreferenceを探す。Xcodeのそれらしいヘルプトピックがあったのでクリックするとアーカイブの方へ飛ばされる。リバイズされていないらしい。これしかないなら諦めて見てみるとThe Library Search Processに、ダイナミックローダがライブラリの場所を探すとき
最初、この環境変数のどれかにFrameworkのdylibがあるパスを設定すればいいのかな、と思って、XcodeのBuild Settingsのタブの中を探していた。
Build Settingsでそれらしい変数を探すとLD_RUNPATH_SEARCH_PATHSというのがある。このquick helpに
このquick helpにある「dyld」というのがダイナミックローダらしい(つい「dyld」を「ディルド」と発音してしまう。アメリカ人でダイナミックローダを知ってる人のうち上品な人ならきっと「ダイエルディ」とか発音してるだろうな)。しょうがないのでdyldのman pageを見ることにする。man pageにはreferenceにあった3つのうちDYLD_LIBRARY_PATHとDYLD_FALLBACK_LIBRARY_PATHの環境変数があって、ちゃんと読まれるようである。そこにはまずDYLD_LIBRARY_PATHを探して、みつからなければDYLD_FALLBACK_LIBRARY_PATHを探すとある。一応referenceの記述と一致している。
man pageの下の方に「DYNAMIC LIBRARY LOADING」という節があって
このうち@executable_pathはわかりやすい。アプリケーションバンドルの中の実行ファイル、例えば普通にインストールされたアプリなら
@loader_pathはもう少し難しい。man pageにはプラグインの場合が書いてある。プラグインの場合、それを使うアプリとは別の場所にインストールされることがある。その場合プラグインのバンドルにライブラリがあってリンクしたくても@executable_pathから辿ることはできない。@loader_pathはプラグインがロードされるときに解決される。プラグインもframeworkと同じバンドル構造になっていて、たとえばMyFilterプラグインの実行ファイル本体は
ローダブルバンドルの場合もdyldがロードするのかどうかよくわからないけど、少なくとも同じメカニズムでロードされるようである。
最後の@rpathはわかりにくい。man pageの記述によると@executable_pathや@loader_pathは単一の置き換え(同じ実行ファイルに対してはpathはひとつだけ)しかしないけど、@rpathは「run path list」というスタックになっていて複数設定できる。スタックへはリンカ(ld)へのオプションとして「-rpath/xxx」と指定する。従ってコンパイル時点では「-Wl,-rpath/xxx」みたいにすればいい。複数個設定するには環境変数のようなコロン「:」で区切るのではなくて、rpathオプションを複数つけないといけないらしい。
それはまあわかった。しかし@rpathはデフォルトで何になってるんだ?このman pageには何の記述もない。先のアーカイブにも記述があるけどなんだかよくわからない。
XcodeのBuild settingsに「Runpath Search Path」という項目があって、これには複数個設定できるようになっている。最初の方に書いたLD_RUNPATH_SEARCH_PATHSの設定である。ようするにここにsearch pathを書くと@rpathにプッシュされるらしい。
でもここに
webを見てるとたとえばこの人のようにinstall_name_toolでバイナリを書き換えるスクリプトをbuild phaseに追加しているひとに行き当たる。なんだかすごく難しいことをしているような気がする。でもそうしてるひとはひとりやふたりではない。それにApple製品以外のアプリケーションで、アプリバンドルの中にdylibがembedされているものでは、そのdylibをotoolでみてもだいたいそうなっている(embedされたdylib本体に@loader_pathなんかが埋まっている)。
さらには、DYLD_LIBRARY_PATHなんかの環境変数がmacOSのバージョンによっては設定できないようになってる、と言っているサイトも見られる。やっぱりinstall_name_toolでpathを埋め込まないとないといけないんだろうか。もしそうならなんでXcodeがそういう面倒を見ないのか。XcodeにとってはBuild Phaseの設定でなにを埋め込みたいのかわかってるはずだし、install_nameの変更が必ず必要ならXcodeがやるべきではないのか?
もうわけわからん。困った。
ややこしいので、具体的に書くと、 のようにA.appというアプリバンドルの中にFrameworksというディレクトリがあって、そこに自作のF.frameworkを内包している。F.frameworkはそいつが使うためのL1.dylibというライブラリをLibrariesというディレクトリに持っている。L1.dylibはL2.dylibという別のライブラリを参照しているので、同じLibrariesディレクトリにコピーした。
このL2.dylibというライブラリの参照が実行時に解決されないといけない。ビルドマシンではこれがLibrariesディレクトリの中のL2.dylibではなくて、もとのL2.dylibがあった位置を参照していた。otoolというコマンドで
$ otool -L L1.dylib L1.dylib: /usr/local/lib/L2.dylib (compatilibity version 1.0, current version 1.4)とかとなっているので、これをリンクしようとするんだろう。別のマシンではこのディレクトリにL2.dylibがないので、実行時にライブラリがみつからない、ということになったらしい。この、otoolで出力されるpathはライブラリそのもの(この場合L1.dylib本体)に埋め込まれたもので、install_name_toolというコマンドで書き換えることができるようである。
しかし、いろいろなライブラリを同じように埋め込んで、依存関係を手動で切り替えるのは、ライブラリが増えてくると非常に煩わしい。そもそもライブラリは検索の優先順位があって本体に埋め込まれた絶対pathにないと他のところを探すはずだった。macOSではまた違っているようである。
stackOverflowやその他のサイトでは断片的な情報(わりとみんなworkaroundが得られればそれで良しとしてる)しか手に入らなかった。めんどくさいけどしょうがないのでreferenceを探す。Xcodeのそれらしいヘルプトピックがあったのでクリックするとアーカイブの方へ飛ばされる。リバイズされていないらしい。これしかないなら諦めて見てみるとThe Library Search Processに、ダイナミックローダがライブラリの場所を探すとき
- LD_LIBRARY_PATH
- DYLD_LIBRARY_PATH
- The process’s working directory
- DYLD_FALLBACK_LIBRARY_PATH
$HOME/lib;/usr/local/lib;/usr/libが与えられている(fallbackというぐらいだからな)。ただしこの環境変数は「実行時」に設定されている必要がある。
最初、この環境変数のどれかにFrameworkのdylibがあるパスを設定すればいいのかな、と思って、XcodeのBuild Settingsのタブの中を探していた。
$ xcodebuild -showBuildSettings > settings.txt $ grep DYLD_LIBRARY_PATH settings.txt $ grep DYLD_FALLBACK_LIBRARY_PATH settings.txtなどとしてみても出てこない。そりゃそうだ、ビルド時に環境変数を設定しても意味がなかった。
Build Settingsでそれらしい変数を探すとLD_RUNPATH_SEARCH_PATHSというのがある。このquick helpに
This is a list of paths to be added to the `runpath` search path list for the image being created. At runtime, `dyld` uses the `runpath` when searching for dylibs whose load path begins with `@rpath/`. See [Dynamic Library Programming Topics]とあって、[Dynamic Library Programming Topics]のアドレスをみるとやっぱり同じアーカイブに飛ばされた。ひでぇ。
このquick helpにある「dyld」というのがダイナミックローダらしい(つい「dyld」を「ディルド」と発音してしまう。アメリカ人でダイナミックローダを知ってる人のうち上品な人ならきっと「ダイエルディ」とか発音してるだろうな)。しょうがないのでdyldのman pageを見ることにする。man pageにはreferenceにあった3つのうちDYLD_LIBRARY_PATHとDYLD_FALLBACK_LIBRARY_PATHの環境変数があって、ちゃんと読まれるようである。そこにはまずDYLD_LIBRARY_PATHを探して、みつからなければDYLD_FALLBACK_LIBRARY_PATHを探すとある。一応referenceの記述と一致している。
man pageの下の方に「DYNAMIC LIBRARY LOADING」という節があって
Unlike many other operating systems, Darwin does not locate dependent dynamic libraries via their leaf file name. Instead the full path to each dylib is used (e.g. /usr/lib/libSystem.B.dylib). But there are times when a full path is not appropriate; for instance, may want your binaries to be instal- lable in anywhere on the disk. To support that, there are three @xxx/ variables that can be used as a path prefix. At runtime dyld substitutes a dynamically generated path for the @xxx/ prefix.とある。「 leaf file name」というのはxxx.dylibの「xxx」のことだろうか。ようするに名前だけではライブラリを探す気は無い、と言っているらしい。それでは上の環境変数との関係はどうなってるんだ。とにかく絶対pathで指定しないといかん、しかし絶対pathがわからんときがある、そのために@xxxがある、と言って
- @executable_path
- @loader_path
- @rpath
このうち@executable_pathはわかりやすい。アプリケーションバンドルの中の実行ファイル、例えば普通にインストールされたアプリなら
/Applications/xxx.app/Contents/MacOS/xxxへのpath
/Applications/xxx.app/Contents/MacOSと置き換えられる。ここを起点にバンドルディレクトリの中を辿ればいい。例えば普通の埋め込みframeworkでは
/Applications/xxx.app/Contents/Frameworks/の下に置かれるので、たとえばyyy.frameworkのトップレベルは
@executable_path/../Frameworks/yyy.framework/としておいて、dyldに@executable_pathを展開してもらえばいい。
@loader_pathはもう少し難しい。man pageにはプラグインの場合が書いてある。プラグインの場合、それを使うアプリとは別の場所にインストールされることがある。その場合プラグインのバンドルにライブラリがあってリンクしたくても@executable_pathから辿ることはできない。@loader_pathはプラグインがロードされるときに解決される。プラグインもframeworkと同じバンドル構造になっていて、たとえばMyFilterプラグインの実行ファイル本体は
/some/path/Myfilter.plugin/Contents/MacOS/Myfilterにあって、@loader_pathはこの場合
/some/path/Myfilter.plugin/Contents/MacOSと置き換えられるようになっているので、@executable_apthと同じように、
@loader_path/../Frameworks/yyy.frameworkなどとすればいい、と書いてある。
ローダブルバンドルの場合もdyldがロードするのかどうかよくわからないけど、少なくとも同じメカニズムでロードされるようである。
最後の@rpathはわかりにくい。man pageの記述によると@executable_pathや@loader_pathは単一の置き換え(同じ実行ファイルに対してはpathはひとつだけ)しかしないけど、@rpathは「run path list」というスタックになっていて複数設定できる。スタックへはリンカ(ld)へのオプションとして「-rpath/xxx」と指定する。従ってコンパイル時点では「-Wl,-rpath/xxx」みたいにすればいい。複数個設定するには環境変数のようなコロン「:」で区切るのではなくて、rpathオプションを複数つけないといけないらしい。
それはまあわかった。しかし@rpathはデフォルトで何になってるんだ?このman pageには何の記述もない。先のアーカイブにも記述があるけどなんだかよくわからない。
XcodeのBuild settingsに「Runpath Search Path」という項目があって、これには複数個設定できるようになっている。最初の方に書いたLD_RUNPATH_SEARCH_PATHSの設定である。ようするにここにsearch pathを書くと@rpathにプッシュされるらしい。
でもここに
@loader_path/Frameworksなどと書いてビルドして、アプリケーションにembedして(ちゃんとビルドできた)別のマシンで動かすと、@loader_path/Frameworksのpathを探してくれなくて、dyldのエラーが
dyld: Library not loaded: /usr/local/Cellar/glib/2.60.5/lib/libgthread-2.0.0.dylibとなる。これは実行ファイルのバイナリに埋まっているpathである(homebrewでインストールした)。つまり優先順位の高いpathをまず探すんだけど、そこになかったらおしまいになってる。ダメじゃん。referenceもman pageもウソじゃん。LD_RUNPATH_SEARCH_PATHSの設定が悪いのか、それともdyldに渡っていないのか、全然わからない。
webを見てるとたとえばこの人のようにinstall_name_toolでバイナリを書き換えるスクリプトをbuild phaseに追加しているひとに行き当たる。なんだかすごく難しいことをしているような気がする。でもそうしてるひとはひとりやふたりではない。それにApple製品以外のアプリケーションで、アプリバンドルの中にdylibがembedされているものでは、そのdylibをotoolでみてもだいたいそうなっている(embedされたdylib本体に@loader_pathなんかが埋まっている)。
さらには、DYLD_LIBRARY_PATHなんかの環境変数がmacOSのバージョンによっては設定できないようになってる、と言っているサイトも見られる。やっぱりinstall_name_toolでpathを埋め込まないとないといけないんだろうか。もしそうならなんでXcodeがそういう面倒を見ないのか。XcodeにとってはBuild Phaseの設定でなにを埋め込みたいのかわかってるはずだし、install_nameの変更が必ず必要ならXcodeがやるべきではないのか?
もうわけわからん。困った。
2019-07-20 21:30
nice!(0)
コメント(2)
とても、参考になりました。Big Surのbeta 4あたりから、linkするLibraryのcodesignが必要になり、そのあたり、installのパッケージで今まで指定したLIBRARY_SEARCH_PATHからlibをロードするのではなく、otoolで出てくるパスを参照しているようなので、otoolの事知らなかったので助かりました。
by Kiwamu (2020-09-02 12:05)
もともとあまり理解しないまま使っていたのですが、自分のプログラムが本当に動かなくなっていろいろ調べました。で、このサイトにも来たわけですが、やはりちゃんとした情報はほとんどないようです。
今回やっと理解したのは、@rpathはdylib側に埋め込む、ということです。Xcodeであれば、ライブラリのビルド時にBuild Settingsの中の Deployment / Installation Directory に@rpath(という文字列)をセットします。すでにあるライブラリをmodifyするならinstall_name_toolを使うのだと思います。
App等、ライブラリを使う側のビルド時に Linking / Runpath Search Paths に@loader_pathその他のリストをセットするとリンク時にこのリストが -rpath オプションでAppのbinaryに組み込まれてdyldによって展開される、ということになります。つまり、@rpathは自動的に何かに展開されるわけではなく、自分でリストをセットするとリンクされたdylibに渡されるというメカニズムらしいです。一応自分の問題はこれで解決しました。
by koichi (2023-07-17 18:31)