SSブログ

macOSからPi Picoを使う - その34 [Pi Pico]

とりあえずいまのところPi Pico側は2コアでも休み休み動かす、macOS側はlibusbの同期転送を専用threadで回すか、非同期転送をNSTimerで頻繁に呼ぶか、というなんとも中途半端なworkaroundだけど、なんとか使えるようになった。

いずれはPi Picoは2コアをぶん回せるようにして、macOS側はlibusbからIOUSBHostに移行したい。とはいうもののいつになることやら。

ということで集中的にPi Picoの話を続けてきたけど今日でひと段落。最後はuniversal binary版のlibusbが欲しい、という話...

17.8  Homebrewでのuniversal binary

Homebrewでインストールされるバイナリはすべてsingle binaryになっている。libusbもHomebrewでインストールするとインストールされたマシンのアーキテクチャ(x86_64か、arm64あるいはarm64e)のバイナリのみになる。これはHomebrewでソースからコンパイルしてもデフォルト設定では同じ結果になる。

それがあって僕はhomebrewのライブラリを使うときにはmacOS側のアプリはsingle binaryでコンパイルするような設定にしていた。1台だけしか使わなくてそこでコンパイルしてそこで実行する場合はそれでも問題ないけど、intelとApple silicon混在の環境でprojectファイルごと使いまわそうとすると、アーキテクチャごとにXcodeの設定(Target設定)を変更した上に、homebrewのデフォルトパスがアーキテクチャごとに違っているので、それを書き換えた上で、アーキテクチャの違うマシンでそれぞれコンパイルする必要が出て非常に煩わしい。

そこで、x86_64とarm64のuniversal binaryのlibusbが欲しくなった。しかしHomebrewでインストールしたものすべてをソースからコンパイルするのは非常に面倒である。Homwbrewがuniversal binaryに対応すればいいんだけど、少なくとも今のところは難しいようである(なぜできないかはイマイチよく理解できないけど)。

最初はlibusb.dylibを、intelとApple siliconの両方でHomebrewでインストールして、それを片方のマシンにコピーしてlipoで結合してみた。つまり
$ lipo -create /usr/local/Cellar/libusb/1.0.24/lib/libusb-1.0.0.dylib\
    /opt/homebrew/Cellar/libusb/1.0.24/lib/libusb-1.0.0.dylib\
    -output /usr/local/lib/libusb-1.0.0.dylib
などとした。ちなみにintelとApple siliconとではインストールされる場所が違うので、片方のディレクトリツリーをまるごとコピーすれば別の場所に同じ構造で異なるアーキテクチャのバイナリが手に入る(上の例ではintelマシンの上でHomebrewが作った/usr/local/libへのシンボリックリンクを破壊して書き換えている)。

lipoはMacがアーキテクチャを乗り換えるたびに表に出てくるユーティリティで、バイナリを一つにまとめたり分けたりできる。これはNextStepの時代からあるらしい。PPCからintelや64ビット化のときとかにlipoにはお世話になったが、完全移行してからは存在を忘れていた(680x0からPPCのときはどうだったっけかなあ。あのときもFat binaryと言ってひとつのアプリに複数のアーキテクチャのコードが含まれていた)。

さらに場合によってはさらに他のライブラリを参照している場合があって、それがアーキテクチャごとに違うパスになっていたりするのを書き換える必要がある。これはまたinstall_name_toolというツールで直接バイナリを書き換えないといけない。手動でやろうとすると結構めんどくさい。

その上lipoやinstall_name_toolでバイナリを変更すると、変更後のバイナリをcodesignしないといけない。ちゃんとバイナリをembedできれば(アプリバンドル内にコピーできれば)Xcodeで自動的にcodesignするようにできるけど、参照先をまたinstall_name_toolでバンドル内部にコピーされたものに書き換えた上でないと実行時にクラッシュする。

とりあえずこれでintelとApple siliconのuniversal binaryはできる。ただし、lipoやinstall_name_toolでの操作が失敗することはないけど、そうやって苦労して作ったuniversla binaryをembedしたアプリが、実行時にクラッシュすることがある。この方法がHomebrewでインストールされたすべてのバイナリで通用するわけではない(というか、Homebrewではこれが通用するバイナリの方がずっと少ない)ようである。アーキテクチャ以外に設定の異なる条件でコンパイルされているとダメなのかもしれない。難しい。

17.8.1  libusbのuniversal binary

上のやり方でinstall_name_toolを使ってああでもないこうでもない、とちまちまやってたんだけど、libusbではもっと簡単な方法があった。homebrewからではなく、本家のソースからコンパイルするのである。それも全然難しくなかった。

libusbのサイトの「Downloads」タブの「Latest Source(tar ball)」を選択するとlibusb-1.0.24.tar.bz2がダウンロードされる。これをダブルクリックして解凍してできたlibusb-1.0.24ディレクトリの中に
libusb-1.0.24/Xcode/libusb.xcodeproj
というXcodeのプロジェクトファイルがある。このプロジェクトファイルにはmacOSに必要なファイルだけがNavigatorに登録されていて、これをXcodeで開いてArchitectureを「Standard Architectures」にしてコンパイルするだけでいい。configureとかcmakeとかmesonとかninjaとか...そういうのに煩わされずにできるようにしてくれている。ありがたい。

これでできたバイナリは
$ lipo -info libusb-1.0.0.dylib 
Architectures in the fat file: libusb-1.0.0.dylib are: x86_64 arm64 
となっていて、これをアプリ側のXcodeのTarget設定の「General」の「Framework and Libraries」に「Embed & Sign」に指定すればいい。

こうすればアプリのプロジェクトファイルはどちらのアーキテクチャでも読み書きできるし、できたアプリはどのアーキテクチャのマシンでも実行できる。

libusbはもともとmulti-platformで、他のライブラリの依存を最小限にするように書かれているので簡単になった。つまり、macOSでのlibusb本体はmacOSに付随したI/OkitやFoundationに依存しているだけで、homebrewでインストールされるライブラリのようにglibなどの特定のプラットフォーム固有のライブラリに依存しないようになっているので、実行時ライブラリのパスを書き換える必要はない。これは単なるラッキーではなく、そういうふうに書いてくれた人のおかげである。感謝。

ありがたい。
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。