Xcode12 で feather をビルドするまでの奮闘記

Xcode 12 (iOS14) がでましたね.

弊社のアプリ feather は7〜8年開発が続いている熟成されたプロジェクトなので今回も色々問題が出ると思っています。

今回は無事ビルドできるまでの奮闘記です。

f:id:mironal:20201029114619p:plain

Carthage

まず Xcode 12 を入れてから carthage で管理しているライブラリをビルドするとエラーが発生してビルドできません。

この問題は以下の issue で議論されており、回避策が提案されています。

Carthage builds fail at xcrun lipo on Xcode 12 beta (3,4,5...) · Issue #3019 · Carthage/Carthage · GitHub

おそらくこのスクリプト(下のもの)を使ってビルドを行うようにするのが今は良いと思います。

Carthage builds fail at xcrun lipo on Xcode 12 beta (3,4,5...) · Issue #3019 · Carthage/Carthage · GitHub

feather では fastlane を使って依存関係を解決する lane があるのでそこを書き換えました。

Fastfile のコード的には元々は

sh("mint run Carthage/Carthage@#{carthage_version} carthage bootstrap --platform iOS --configuration Release --cache-builds --project-directory ../")

だったものを、先の回避策のスクリプトを少し修正したものを carthage-build.sh という名前でリポジトリに追加して以下のように変えました。

sh("../carthage-build.sh")

これで、ひとまず Carthage のビルドができました。

無事アプリはビルドできました

上記の修正ぐらいでひとまずアプリをビルドして実行することはできました。

CocoaPods で管理しているライブラリの警告

多くのライブラリは IPHONEOS_DEPLOYMENT_TARGET8.0 に設定されているため、ビルド時に警告がでていました。 もうメンテナンスされていないライブラリもあるため、このあたりが勝手に修正されることは考えにくいと思い Post Hook を使って 9.0 に書き換えるようにしました。

post_install do | installer |
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # IPHONEOS_DEPLOYMENT_TARGET が 8 のライブラリが多くて
      # 警告がたくさん出るので 9 に書き換えてしまう。
      # 8.x は 8.0 で指定されているものしかなかったので単純に比較する.
      if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] == '8.0'
        config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
        puts "Change IPHONEOS_DEPLOYMENT_TARGET to 9.0 in #{config.build_settings['PRODUCT_MODULE_NAME']} #{config}"
      end
    end
  end
end

テストターゲットをビルドすると Cycle inside というエラーが発生

アプリのビルドはできたのですが、テストを実行しようとするとこんなエラーが出てビルドできませんでした(実際はもっと長いエラーです)。

Cycle inside feather liteTests; building could produce unreliable results. This usually can be resolved by moving the shell script phase '[CP] Embed Pods Frameworks' so that it runs before the build phase that depends on its outputs.

これの解決には非常に長い時間がかかりました。 ずっと作業していたわけではなく他のことをやりながらでしたが3週間ほどかかりました。

Cocoapods の RC 版

Cocoapods の RC 版を使うとなにか変わるかな?と思いましたが直りませんでした。

Release 1.10.0.rc.1 · CocoaPods/CocoaPods · GitHub

XcodeGen の設定ファイル(project.yaml)を色々いじる

feather では xcodegen を使っていてこちらのブログ記事と状況が似ていたので同じように対応してみましたが、また別のエラーが発生して結局ビルドできませんでした...

XcodeGen + Xcode12 error: Cycle inside Appと戦った

その後も Carthage で管理しているライブラリだけ設定を変えてみたりと色々してみましたが少しエラーメッセージが変わるだけでダメでした..

Build phase を無理やり入れ替える

最終的にはこの方法で行くことにしました。あまりエレガントではないのでできればやりたくなかったのですが、これ以上ずっとビルドができない状態は避けたいのでとりあえず開発できる状態にすることが先決と判断しました。

Cycle inside "YOUR_APPS"; building could produce unreliable results. This usually can be resolved by moving the target's Headers build phase before Compile Sources. - Qiita

この方法は xcodegen & cocoapods を使っている場合はそのままだと他の環境で反映されないので工夫が必要です。 今回は fastlane で project を setup するときに叩く lane にbuild phase を入れ替える以下のようなコードを呼び出すことにしました。

require 'xcodeproj'

EmbedPodsFrameworksPhaseName = "[CP] Embed Pods Frameworks"

# "[CP] Embed Pods Framework" の phase を含む target かどうか
def has_cocoapods_embed_pods_frameworks_phase(target)
    target.build_phases.any? { |bp|
        bp.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && bp.name == EmbedPodsFrameworksPhaseName
    }
end

project_path = "#{p __dir__}/feather.xcodeproj"
project = Xcodeproj::Project.open(project_path)

project.targets
    .filter { |target| has_cocoapods_embed_pods_frameworks_phase(target) }
    .each do | target |
        # build_phases の中の "[CP] Embed Pods Frameworks" の phase を
        # Compile Sources の前に移動する

        phase = target.build_phases.find { |bp|
            bp.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && bp.name == EmbedPodsFrameworksPhaseName
        }

        to_index = target.build_phases.find_index { |bp|
            bp.is_a?(Xcodeproj::Project::Object::PBXSourcesBuildPhase)
        }

        p "Target: #{target.name}"
        p "  Phase: #{phase}"
        p "  Move to index: #{to_index}"

        target.build_phases.move(phase, to_index)
end

project.save()

まとめと今後

今回は以下のようなビルドエラー(テスト実行時に出ます)に悩まされました。 最初の PR を出したのが9/25 でマージされたのが10/23で時間がかかりました....

Cycle inside feather liteTests; building could produce unreliable results. This usually can be resolved by moving the shell script phase '[CP] Embed Pods Frameworks' so that it runs before the build phase that depends on its outputs.

おそらく依存関係で良くないことが起きているのですが、 feather はアプリのコードを framework に分割していたり、 Cocoapods と Carthage を使っていたりしていて原因の特定がちょっとやそっとじゃ分からない状態になっていました(Xcode 11 では普通にビルドできていたのに...)。

Carthage もビルド周りで今回色々な問題が発生しているので、 Xcode 12 の変化が大きかったのだと思います。

今後は先の Build phase を入れ替えるスクリプトがなくてもちゃんとビルドできるようにすることを目指して時間をとって調査してみたいと思います。

(コレをやっている間に Cocoapods の 1.10.0 がリリースされていました)