my routeアプリにSwift Package Managerがやってきた!

KINTOテクノロジーズで my route(iOS) を開発しているRyommです。
my routeアプリのライブラリ管理ツールがついに!CocoaPodsからSwift Package Manager(以下SPM)に移行しました!
はじめに
my routeではCocoaPodsを使用していました。
しかし2024年夏ごろ、CocoaPodsから以下のお知らせが発布されました。
CocoaPodsがメンテナンスモードに移行するというお知らせです。
使えなくなるわけではありませんし、セキュリティの問題などには対応してくれます。ただし、GitHub issueにおけるサポートや新機能の追加などは行われなくなります。
そうすると今後、新しいライブラリを入れたい時にCocoaPodsがサポートされなくなっていたり、CocoaPods自体に何か問題が起こっても直すことができない、といった場面が起こり得ます。
以前からmy routeではSPMへの移行の話は持ち上がっていましたが、このCocoaPodsのお知らせが追い風となり、満を持してSPMへの移行に着手しました。
Swift Package Managerとは
SPMとは、Appleが提供する純正ライブラリ管理ツールです。
iOSでライブラリ管理ツールといえば、他にはCocoaPodsやMintなどのサードパーティツールがあります。これらと最も大きく違うのは、Xcodeに統合されているという点です。
これにより、必要なタイミングで Package.resolved
を更新するだけで、プロジェクトファイルを開いた時やビルド時に最新の指定バージョンのライブラリを取り込んでくれます。
バージョン更新をするたびにチームへ pod install
を実行してくれ〜と声をかける必要がなくなるのです。
移行したよ
ひたすら移していくのみですが、いくつか躓いたポイントや工夫点があるので紹介します。
指定したバージョンのライブラリが降ってこない
使用するバージョンを変更する際、Xcodeで「Reset Package Caches」や「Update to Latest Package Versions」を実行しても、Package.swiftで指定しているバージョンが降ってこない問題がありました。DerivedDataを消しても直りません。
これは、 ~/Library/Caches/org.swift.swiftpm
や ~/Library/org.swift.swiftpm
などに深淵のキャッシュが残っているためです。これらを消すと正しいバージョンのライブラリが降ってくるようになります。
テストの時だけビルドが通らない
my routeは複数のスキーマを持っており、それぞれ依存するライブラリが少し異なります。
そのため以下のようにプロジェクト自体のPackage.swiftとは分離した構成になっています。
プロジェクトファイルと同じワークスペース配下にDependenciesパッケージを作成し、このパッケージ内で各スキーマと1対1になるproduct(フレームワーク)となるようにライブラリを管理しています。
これらのproductを右側のスクリーンショットのように、それぞれのターゲットの Frameworks, Libraries, and Embedded Content
にて紐付けています。
しかしこの構成にしていると、 xcodebuild build
は通りますが、 xcodebuild build-for-testing
が通らないという問題が起こりました。XcodeのGUI上ではRunとTestに対応するコマンドです。
本来、パッケージのものはパッケージ内でテストします。
しかし上記の構成では、プロジェクト本体のメインのターゲットでテストを実行します。つまり、パッケージの外でテストしているということです。
ということはこれは...違法建築...なのですが、ゆくゆくは同じパッケージ内にテストを詰めるように直していくということで、一旦構成は維持したままテストできるようにします。
RunとTestではLinkerあたりの挙動が異なるらしいことが原因でした。
Runのビルド生成物を①、Testのビルド生成物を②とします。
①にはDependenciesのSPMが含まれていますが、②には含まれていません。
そのため、MainTarget内のテストがDependenciesのライブラリに依存しているとビルドに失敗してしまいます。
簡単な解決策としては、MainTarget内のSPMに持たせるとRunでもTestでもビルドに含まれるため、そちらにテストで必要なライブラリを移します。
根本的に解決するには、先述のように1つのパッケージ内でテストが簡潔するようにすると良いでしょう。
SwiftLintやLicensePlistをSPMに乗せる
SwiftLintやLicensePlistはプロジェクトのBuild Phaseに含めてビルド時に実行されるようにしたいため、workspaceから独立した場所に別のパッケージを作成します。
Project/
├── Test.xcworkspace
├── Test.xcodeproj
├── Test/
│ └── ...
├── ...
└── tools // <- this!
├── Package.swift
└── Package.resolved
新しく作成したtoolsパッケージにSwiftLintやLicensePlistなどBuild Phaseに含めたいライブラリを入れます。
そして以下のようなシェルを用意し、初回だけ実行してローカルにライブラリを落としておきます。
SPM_PACKAGE_PATH=tools
SPM_BUILD_PATH=tools/.build/release
echo "swiftlint" && $SPM_BUILD_PATH/swiftlint --version || swift run -c release --package-path $SPM_PACKAGE_PATH swiftlint --version
echo "license-plist" && $SPM_BUILD_PATH/license-plist --version || swift run -c release --package-path $SPM_PACKAGE_PATH license-plist --version
すると tools/.build/release/swiftlint
で実行可能になるので、これをBuild Phaseに入れます。
LicensePlistも同様です。
Bitriseなどから実行する際も、シェルを実行してからプロジェクトをビルドすると実行されることが確認できます。
おわりに
移行してから数ヶ月経ち、まだまだいくつかの問題もありますが、ライブラリの入れ替えは楽になったと思います。
またライブラリ起因の問題かどうか判断したい時に、アプリ版のPraygroundsでサクッと確認できることが個人的には楽に感じます。
SPMがプロジェクトで使えるようになったことで、SPMでしか提供されていないApple製ライブラリも使えるようにもなりました。
今後はこれらのライブラリも使ってより良い実装にしていきたいです。
関連記事 | Related Posts
We are hiring!
【プロジェクトマネージャー】モバイルアプリ開発G/大阪
モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。
【iOSエンジニア】モバイルアプリ開発G/東京
モバイルアプリ開発GについてKINTOテクノロジーズにおける、モバイルアプリ開発のスペシャリストが集まっているグループです。KINTOやmy routeなどのサービスを開発・運用しているグループと協調しながら品質の高いモバイルアプリを開発し、サービスの発展に貢献する事を目標としています。