😊

Xcodeの『building for iOS Simulator, but linking …』エラーに対処するツールを作ったのでご紹介

2022/04/18に公開

こんにちは、あらさん(@arasan01_me)です。
食洗機に分岐水栓つけたら快適すぎて唐揚げ食べる頻度が高くなりました。

何を作ったのか

Apple Silicon Mac(M1 Mac)のXcodeを利用してプロジェクトを実機向けとシミュレータ向けにビルドすると,実機向けにはビルドが成功するのにシミュレータ向けにビルドする場合には『building for iOS Simulator, but linking in object file built for iOS, for architecture arm64』というエラーによりビルドが失敗します。
これは内部で利用しているライブラリがApple Silicon MacのXcodeで利用されるSimulatorに対応していないためです。

具体的に言うとRosetta2で動かしたXcodeではIntel Macと同様にXcodeとSimulatorがx86_64のアーキテクチャの上で動くのに対して,通常の起動方法ではApple Silicon Macの場合はXcodeとSimulatorはarm64のアーキテクチャの上で動きます。内部で利用しているライブラリを見るとarm64の実機向けビルドとx86_64のシミュレータ向けビルドが同梱されている場合に上記のエラーが発生します。Rosetta2を使うとx86_64のシミュレータ向けビルドが使えるためシミュレータ向けビルドを使えるのに対して,通常の起動ではarm64のシミュレータ向けビルドが見つからないため失敗してしまいます。

これに対処する方法はarm64のシミュレータ向けビルドを用意することなのですが,これがかなり難しいです。何かのツールの依存関係でビルド済みライブラリが存在している場合はこれをarm64のシミュレータ向けビルドにする必要があります。ここで今までのワークフローではarm64の実機向けビルドとx86_64のシミュレータ向けビルドを同梱していたのですが,arm64の実機向けとarm64のシミュレータ向けビルドは同梱できません。xcframeworkを利用して同梱する必要があります。

また,ソースコードが存在していればarm64のシミュレータ向けビルドを手元で作成すれば良いのですが,もしない場合にはビルド元にシミュレータ向けビルドも提供してもらわなければなりません。利用する際にはシミュレータ向けと実機向けを選択するときに都度2つのライブラリどちらかを見るようにワークフローを組み直す,または提供元にxcframworkを提供してもらってプロジェクトに組み込む必要があります。

これに対して,xcframeworkをframeworkから生成するツールが存在していました。ただし,ワークフローの組み換えが必要で煩雑です。そこでライブラリ自体を動的に選択できるツールを作成しました。

https://github.com/dmm-com/ranasa

使い方はarm64のシミュレータ向けビルドと実機向けビルドを切り替えてほしいバイナリを設定ファイルに記述して,プロジェクトのBuild Phase Scriptに追加するだけです。
設定ファイルにはバイナリが静的リンクか動的リンクのどちらなのかを指定する必要があります。確認する場合にはfile -b <binary path>を利用して確認できます。複数のアーキテクチャが混合している場合には色々表示されますが,以下の内容が含まれるはずなのでそちらを参考にしてください。

$ file -b StaticBinary
current ar archive random library

$ file -b DynamicBinary
Mach-O 64-bit dynamically linked shared library arm64

$ lipo -archs StaticBinary
arm64

$ lipo -archs DynamicBinary
arm64

使い方はMintかSwiftPMでのビルドを利用してください。

Mintの場合

$ mint install dmm-com/Ranasa@0.1.5
$ mint run dmm-com/Ranasa@0.1.5 <command argument>

セルフビルドの場合(ビルドしたあとはシングルバイナリになるため任意の場所に移動可能)

$ git clone https://github.com/dmm-com/ranasa; cd ranasa; swift build -c release
$ .build/release/ranasa <command argument>

このツールではどのバイナリをターゲットにするかjsonファイルを用いて管理します。今回はプロジェクトトップにFrameworkを配置したため以下のように記述します。

[
    {
        "linking": "dynamic",
        "path":  "DynamicLib/DynamicBin"
    },
    {
        "linking": "static",
        "path":  "StaticLib/StaticBin"
    },
]

Build Phase Scriptにranasaを呼び出すコードを記述します。

  • -sオプションを利用することで自動生成したシミュレータ向けビルドと実機向けビルドの保管場所を指定できます。
  • -xオプションをBuild Phase Scriptで利用してranasaが自動でシミュレータなのか実機向けのビルドなのかを判断して最適なビルドを選択します。
  • -vオプションでは詳細なログを出力します。動作に影響はありません。

詳細なログはコマンドのヘルプを参照するか,リポジトリのREADMEを参照してください。

ranasa/.build/release/ranasa -s .ranasa -x -v arm64sim.json

おわりに

swiftmoduleがあるとコケるとかたまに挙動が不安定とか色々ありますが自由にForkしてプロジェクトに使えるように利用してみると良いかなと思います。
Static Libraryだと普通に動くことは確認しています,それ以外は特に利用する目標がなかったのでデバッグが不足しているかもしれないです。

Discussion