otool と install_name_tool を使ったライブラリのパス変更

Mac 用のバンドルを作る時に使う方法です。

dylibbundler というプログラムもあるのですが、古いライブラリだと /opt/local/@executable_path に変更するときに、長さが足りずエラーになることがあります(GTK 関連にそういう問題児がいる)。

正確には install_name_tool -change を実行したときの症状ですが、この場合、-headerpad_max_install_names をつけて再コンパイルしなくてはいけないので非常に面倒です。現在、JHBuild の方では改善されているようですが、MacPorts の方は対応していないみたいです(+quartz バリアントをつけた場合は試してないけど)。

まぁその話はおいといて、otoolinstall_name_tool を使った依存関係の取得とパスの変更方法です。ここでは MacPorts からインストールした X11 用の libpango-1.0.0.dylib (compatibility version 3001.0.0, current version 3001.1.0) を例にします。


otool -L で依存関係をチェックします。

bash
$ otool -L /opt/local/lib/libpango-1.0.0.dylib

/opt/local/lib/libpango-1.0.0.dylib:
    /opt/local/lib/libpango-1.0.0.dylib (compatibility version 3001.0.0, current version 3001.1.0)
    /opt/local/lib/libgobject-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /opt/local/lib/libgthread-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /opt/local/lib/libffi.6.dylib (compatibility version 7.0.0, current version 7.0.0)
    /opt/local/lib/libgmodule-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /opt/local/lib/libglib-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 41.1.0)
    /opt/local/lib/libintl.8.dylib (compatibility version 10.0.0, current version 10.1.0)
    /opt/local/lib/libiconv.2.dylib (compatibility version 8.0.0, current version 8.1.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)
    /System/Library/Frameworks/Carbon.framework/Versions/A/Carbon (compatibility version 2.0.0, current version 152.0.0)
    /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 751.63.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 550.44.0)

1行目がそのファイルの名前で、二行目は *.dylib なら ID ですし、*.so なら依存関係のファイルだったりします。*.dylib の場合 install_name_tool -change を使っても二行目は ID なので書き換わりません。変更する場合は install_name_tool -id を使います。

まず情報を整理しましょう。1行目はそのファイル名なので必要ないですね。2行目は *.dylib であれば tail +3 なんか消してしまってもいいのですが、*.so の場合は2行目も依存関係なので消してしまうと困ります。(まぁ id 部分に -change 使ってもエラーが出力されるだけでそれほど問題はないので無理して省く必要はないかもしれません)

2行目までの書式は otool -D の書式と同じです。なので grep コマンドで引き算すればいいわけです。*.so の場合は id が無いのでファイル名しか表示されません。

bash
/opt/local/lib/libpango-1.0.0.dylib:
/opt/local/lib/libpango-1.0.0.dylib

grep -v で削ります。(※$_ は前のコマンドの最後の引数)

bash
$ otool -L /opt/local/lib/libpango-1.0.0.dylib | grep -v "$(otool -D $_)"

    /opt/local/lib/libgobject-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /opt/local/lib/libgthread-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /opt/local/lib/libffi.6.dylib (compatibility version 7.0.0, current version 7.0.0)
    /opt/local/lib/libgmodule-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /opt/local/lib/libglib-2.0.0.dylib (compatibility version 3201.0.0, current version 3201.4.0)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 41.1.0)
    /opt/local/lib/libintl.8.dylib (compatibility version 10.0.0, current version 10.1.0)
    /opt/local/lib/libiconv.2.dylib (compatibility version 8.0.0, current version 8.1.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)
    /System/Library/Frameworks/Carbon.framework/Versions/A/Carbon (compatibility version 2.0.0, current version 152.0.0)
    /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 751.63.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 550.44.0)

これで依存関係だけを残すことができました。次はシステムにあるものを除外し、「()」部分を取り除きます。

この段階で、システム外ライブラリの場所が /opt/local/lib 固定なら

bash
$ otool -L /opt/local/lib/libpango-1.0.0.dylib | grep -v "$(otool -D $_)" | grep -o "/opt/local/.*dylib"

だけで済みます。

ただ、それ以外の場所の可能性もあるのでちょっとコマンドが増えますが細かく仕分けていきます。正規表現を使ったほうが楽なので grep にオプションを付けるか egrep を使います。

bash
$ otool -L /opt/local/lib/libpango-1.0.0.dylib | grep -v "$(otool -D $_)" | egrep -v "/(usr/lib|System)"

最後にファイルパスだけを残します。ここは cut -d' ' -f1 で1フィールド目だけを取り出す方法でもいいかも。

bash
$ otool -L /opt/local/lib/libpango-1.0.0.dylib | egrep -v "$(otool -D $_)" | egrep -v "/(usr/lib|System)" | grep -o "/.*dylib"

/opt/local/lib/libgobject-2.0.0.dylib
/opt/local/lib/libgthread-2.0.0.dylib
/opt/local/lib/libffi.6.dylib
/opt/local/lib/libgmodule-2.0.0.dylib
/opt/local/lib/libglib-2.0.0.dylib
/opt/local/lib/libintl.8.dylib
/opt/local/lib/libiconv.2.dylib


この結果をパイプで繋いで while へ渡します。書き換え対象のファイル名は継続して使うので変数に入れておきます。${REPLY##*/}basename コマンドと同意です。(※変数部分のダブルクオートはシンタックスハイライトのエラー防止用です)

bash
FILE=/opt/local/lib/libpango-1.0.0.dylib
$ otool -L ${FILE} | egrep -v "$(otool -D $_)" | egrep -v "/(usr/lib|System)" | grep -o "/.*.dylib" | while read
do
    install_name_tool -change "${REPLY}" "@executable_path/lib/${REPLY##*/}" ${FILE}
done

install_name_tool 部分の書式は

bash
install_name_tool -change /opt/local/lib/libgobject-2.0.0.dylib @executable_path/lib/libgobject-2.0.0.dylib /opt/local/lib/libpango-1.0.0.dylib

になります。


この方法は install_name_tool だけじゃなく、cp などにも使えます。

otool はファイル名にワイルドカードを使うことも出来ますし、$()find コマンドと併用することもできます。同じ依存関係を持つものもあると思うので、その場合は sort -u を使えばそれらを1つにまとめてくれます。(find -execxargs でもできます)

bash
$ otool -L /opt/local/lib/libpango*.dylib | grep -o "/.*dylib" | sort -u
bash
$ otool -L $( find /opt/local/lib/libpango*.dylib ) | grep -o "/.*dylib" | sort -u

上の結果を cp に直接入れてもいいし、ループに入れてもOK。書き方は色々です。


ざっと書いたので後々修正します。

Trackback