SPMの1つのパッケージに複数のプロダクト・モジュールを含めたい!
[11/30, 2024] |
こんにちはトモです。
みなさん、Swift Package Manager(SPM)使ってますか?最近ちょっとずつ理解が進んで、ちょっぴり楽しくなってきました。そんな中1つのパッケージの中に複数のプロダクトを含めたいという欲求が出てきました。
やりたい事
Package Aをdependenciesに入れたらアプリ側で
import X
import Y
のように複数のimportができるようにしたい感じです。例えるならばfirebase-ios-sdkのリポジトリに「FirebaseCrashlytics」や「FirebaseAnalytics」が含まれている状態ですかね。(Firebaseはとても巨大で複雑なので厳密には違う可能性もあります...)
ここで疑問:各種nameの意味
またそれを実現するにあたり、このPackageのnameと.libraryのnameと.targetのnameのnameってどこで使われる名前なの?という疑問が湧きました。(そもそもPackage.swiftの書き方も雰囲気で書いているのですが。。。😅)
let package = Package(
name: "Name1", //<-これ
products: [
.library(name: "Name2", targets: []), //<-これ
],
targets: [
.target(name: "Name3", dependencies: []), //<-これ
]
)
なぜならば、これを理解しないとXとYの指定をどこにすればいいかわからなかったからです。
よくあるSPMのパッケージのつくりかたはName1〜3まで全部同じ名前になっていますよね?なんならパッケージのフォルダの名前も同じです。 なので自分は正直、思考停止で全部同じ名前を指定している状態でした。
調べてみた
今回試したのはBaseViewKitというフォルダの中にパッケージを作っています。
BaseViewKit/
Package.swift
Sources/
またローカルなSPMのパッケージという前提でやっていますが、リモートでも概ね同じだと思います。
フォルダー名は
フォルダー名は
File > Add Package Dependencies > Add Local...
の読み込み時に表示されます。
この画像の「Choose Package Products for BaseViewKit」の部分はName1が表示されると思っていました。
Name1は
Xcode上のPackageのListなどに表示されます。
Add PackageのRecently Usedにもフォルダー名が出ています。(なぜ?)リモートのパッケージの場合はちゃんとName1が表示される・・・?
Name2は
Frameworks, Libraries, and Embedded Contentの中などに表示されます。
なるほどまさにライブラリ名だったんですね。
余談ですが、ローカルのパッケージを追加したり削除したり名前を編集したり繰り返している際に、古いライブラリ名がここに残ってしまうことがありました。
すると、ビルド時にそんな名前のライブラリ見つからないよと怒られます。最初エラーの原因がわからずプロジェクトをクリーンしたり、DerivedDataを削除したりしましたが解決しませんでした。
よくよくみたらここに古い名前が残っていることに気づき、選択して「ー」ボタンを押して手動で削除したら解決しました。
ローカルのパッケージを削除する際に一緒に消えてくれないのはバグなのか?仕様なのか・・・?
Name3がimportの名前
そしてName3が大本命のimport XXXXに相当する名前でした。
なのでtargetを複数用意してName3の部分を理想の名前にすれば良さそうです。
つまりtargetsを複数作って好きな名前をつけるとよい
最終的には以下のようになりました。(最小限の記載です)
let package = Package(
name: "BaseViewKit",
products: [
.library(name: "BaseViewKit", targets: [
//Name3:targetsの名前を指定
"BaseViewV1",
"BaseViewV2",
]),
],
targets: [
.target(name: "BaseViewV1", dependencies: []),
.target(name: "BaseViewV2", dependencies: [], path:"Sources/V2"),
]
)
Name3(.target)の部分を2つ作り、importしたいnameにします。
こうすることでアプリ側では
import BaseViewV1
import BaseViewV2
と呼べるようになります。
フォルダ構成とpathの指定について
また余談ですがtargetのpathを指定しない場合はsourceのフォルダ名がnameと同じ必要があるようです。
targets: [
.target(name: "BaseViewV1", dependencies: []), <- 指定なし
.target(name: "BaseViewV2", dependencies: [], path:"Sources/V2"), <-指定あり
]
なので、「BaseViewV2」の方ではあえて指定する形もサンプルとして載せてみました。
BaseViewKit/
Package.swift
Sources/
BaseViewV1/ <- pathの指定なし = nameと同じ名前
V2/ <- pathの指定ありなので好きな名前でOK
これで1つのパッケージに複数のモジュール?を含めることができるようになりました。
機能ごとに分けてもいいですし、自分はBaseViewの破壊的変更があった際にBaseView2として切り出したいニーズがあったのでそう言った使い方もできるのかなーと思っています。
また今回調べてみて
products: [ .library() ]
ここを複数用意したい場合はどういうようとの時なんだろう?🤔という新しい疑問も湧いてきました。
ひとまずやりたいことができたので、めでたしめでたし🎉