LoginSignup
1

ライブラリ管理をCarthageからSwiftPackageManagerに移行する

Last updated at Posted at 2022-12-06

この記事はand factory.inc Advent Calendar 2022 6日目の記事です。
昨日は @nabetaro_jp さんの [Flutter] あの赤いエラー画面をカスタマイズする でした。

概要

先日、自分が参画しているプロダクトでライブラリ管理ツールをCarthageからSwift Package Manager(以下、SPM)に移行するという機会に遭遇したので、その過程で行ったことをまとめます。

背景

該当のiOSアプリのプロジェクトではライブラリ管理にCarthageとCocoapodsを併用しており、Carthageはビルド時間の短縮に大きく寄与していたものの、ライブラリによってはビルドや依存解消に失敗するケースが頻発していた(特にXcode12以降)こと、CI/CDの連携でエラーが起こる、XCFrameworksのサポートが怪しいなどの課題がこれまで存在していました。

そこでApple公式のパッケージマネージャーであるSPMがiOS開発者の間で広まってきたこともあり、今回Carthageで管理していたライブラリを思い切ってSPMとCocoapodsに移行する運びになりました。

やったこと

project.yml、Podfileの更新

弊アプリでは.xcodeprojファイルの生成にXcodegenを使用しているため、まずはproject.ymlにSPMに移行するライブラリを追記していきます。

project.yml
...
packages:
  # コミット指定の場合
  FSPagerView:
    url: https://github.com/WenchaoD/FSPagerView.git 
    revision: a8c14cfb2c07b91ccf6a19dfe126b86103f56f74
  # バージョン指定の場合
  Reusable:
    url: https://github.com/AliSoftware/Reusable.git
    from: 4.1.2
  # ブランチ指定の場合
  Lottie:
    url: https://github.com/airbnb/lottie-ios.git
    branch: master
  ...

Xcodegenを使わずXcode上で設定したい場合は、XcodeのFile > Add packagesなどから簡単に追加できます。

ライブラリの移行先をSPMにするかCocoapodsにするかは、当該ライブラリのリポジトリにパッケージ構成を定義するPackage.swiftがあるかどうか、(SPM対応しているように見えても導入できないケースがあるので)実際に導入して正しく依存解消&ビルドできるか否かで判断しました。

Cocoapodsに移行する場合はPodfileを更新します(説明は割愛)

Carthage関連のリソース削除

これでSPMからライブラリをインポートすることができるようになりました🎉

それに続き、不要になったCarthage関連のファイルを削除します。
整理対象はCartfile、Cartfile.resolvedと、後はproject.ymlのCarthage用のコードです(環境に合わせて要対応)

CIのビルドキャッシュ管理の設定

ここまでやれば特に問題なくビルドできるようになったのですが、我々のプロジェクトで使用しているCI上でビルドする際にキャッシュ管理をするためには別の設定が必要でした。

例としてCircleCIでSPMのキャッシュ管理するためには.circleci/config.ymlを下記のように更新します。

config.yml
  ...
  run_fastlane:
    description: "Deploy fastlane"
    steps:
      - restore_cache: # SPM関連のキャッシュ対応のため
          keys: 
            - v1-spm-cache-{{ checksum "ourproject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" }}
            - v1-spm-cache-
      - run: 
          name: Run fastlane
          command: bundle exec fastlane "$CIRCLE_BRANCH"
          no_output_timeout: 1h
      - save_cache:
          key: v1-spm-cache-{{ checksum "ourproject.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" }}
          paths:
            - SourcePackages
  ...

やってることとしては、キャッシュを保存するパスを指定(ここではSourcePackages)し、キーからこれを保存&呼び出しできるようにするという設定になります。

同様にCircleCI上でのビルドに使用するfastlaneのFastfileにも、SPMのキャッシュファイルのパスを指定するオプションであるcloned_source_packages_pathを追記します。

Fastfile
desc "build develop"
    lane :develop do
        fix_version(build_configuration: "Debug")
        gym(
            scheme: SCHEME,
            configuration: "Debug",
            skip_package_ipa: true,
            skip_archive: true,
            skip_codesigning: true,
            cloned_source_packages_path: "SourcePackages"  # SPMキャッシュ用ディレクトリ
        )
    end

ビルド時間の壁を越えたい

これでCI上でも一度ビルドしたパッケージはキャッシュすることでビルド時間をある程度短縮できるようになりました。
しかし、それでもCarthage時代より30%ほどビルド時間が長い、って点で引っ掛かかりがありました。

原因を探ってみると、どうやらデータベース管理のためのライブラリであるRealmがビルド時間の足を引っ張っていたことが判明。

そこでRealmのみSPMではなくXCFrameworkでプロジェクトに導入する流れになりました。

XCFrameworkでRealmを入れる

XCFrameworkとは、ビルド済みのSwiftパッケージを配布するためのフォーマットのこと。

これまでCarthageを使っていた時はあらかじめライブラリをframework化してキャッシュすることでビルド時間を短縮していたのですが、残念なことにSPMではライブラリのソースコードはキャッシュしてくれるが、これをビルドしたバイナリはCIでキャッシュできない(キャッシュしても機能しない)という事実が調べていて分かりました。

では実際の導入ですが、Realmは公式リポジトリで最新版のxcframeworkを配布してくれているので、やるこことしてはこれを落としてプロジェクトにリンクするだけです。簡単ですね。

上記ページから欲しいバージョンのCarthage.xcframework.zipをダウンロードします。

ダウンロードが完了したらこれを解凍し、xcframeworks管理用の任意のディレクトリに移し、これをプロジェクトにリンクするための設定をproject.ymlに追記します。

project.yml
    ...
    dependencies: 
      - framework: xcframeworks/Realm/Realm.xcframework       # 追加
      - framework: xcframeworks/Realm/RealmSwift.xcframework  # 追加
      - package: APIKit
      - package: CryptoSwift
    ...

実際のプロジェクトではこの辺りの処理を自動化するためのスクリプトを下記記事参考に書いています。

ここまでやって、漸くSPM+Cocoapods+XCframeworksの編成でビルド時間をほぼ以前の水準まで戻すことに成功しました🎉

補足(XCFrameworks導入後に実機ビルドで失敗した件)

XCFrameworkの導入作業の途中、シミュレーターやCIではビルドできるのに実機ではビルドに失敗するという事象に見舞われました。

色々原因を調べた結果、project.yml上でRealmに依存するTarget(下記でTargetB)だけにdependenciesとframeworkの設定をしていたことが原因らしく、このTargetに依存する別のTarget(下記でTargetA)にも同じ設定をしてあげる必要がありました。

project.yml
targets:
  TargetA:
    ...
    dependencies:
      - target: TargetB
      ...
      - framework: xcframeworks/Realm/Realm.xcframework  # TargetAが直接依存していなくても設定しないと実機ビルドで失敗
      - framework: xcframeworks/Realm/RealmSwift.xcframework  # (同様)
     ...
  TargetB:
    ...
    dependencies: 
      - framework: xcframeworks/Realm/Realm.xcframework
      - framework: xcframeworks/Realm/RealmSwift.xcframework
      ...

これはXCFrameworkがそういう仕様なのか、Xcodegenがそういう仕様なのかよく分かっていないので、今後の要調査課題として残っております。何かご存知の方がいらっしゃれば教えてください。

まとめ

CarthageからSPMへの移行が一通り終わってみての感想ですが、重い腰を上げるのが中々大変でしたが、いざやってみるとすぐに終わったという感じです。
ただビルド時間の側面ではまだまだ改善ポイントがありそうなので、引き続き色々模索していこうと思います。

明日のAdvent Calendarもお楽しみにしてください!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1