はじめに
MultipartFormDataParserというテスト用のライブラリを公開しています。
このライブラリのテストでいくつか別のpackageを使用しているのですが、これが理由でちょっとした課題を発見しました。
この記事では、その課題と解決策をご紹介します。
概要
MultipartFormDataParserとAlamofireのみを使ったサンプルプロジェクトを作成してみました。
以下のようなPackage.swiftを作成しています。
import PackageDescription
let package = Package(
name: "Dependencies",
products: [
.library(
name: "Dependencies",
targets: ["Dependencies"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.4"),
.package(url: "https://github.com/417-72KI/MultipartFormDataParser.git", .exact("1.4.0")),
],
targets: [
.target(
name: "Dependencies",
dependencies: ["Alamofire"]),
.testTarget(
name: "DependenciesTests",
dependencies: ["Dependencies", "MultipartFormDataParser"]),
]
)
XcodeでこのPackage.swiftを含むプロジェクトを開くと依存性解決の結果をProject Navigatorに表示してくれます。
さて、このProject Navigatorに表示された依存パッケージを見ると、Package.swiftに書いた Alamofire
と MultipartFormDataParser
以外にも意図していないパッケージがfetchされていることが分かります。
また、Issue Navigatorを開くと
意図しない警告1が出てしまっています。
原因
当たり前といえば当たり前なんですが、使用しようとしているライブラリが別のpackageに依存している場合、それも取ってくる必要があります(というかそのための依存性解決ですw)
その際Package Managerが見に行くのは dependencies
の Array
であり、 それらが target
で使われるか testTarget
で使われるかは関係ありません。
そのため、各ライブラリのテストでしか使われないpackageが余計にfetchされ依存性解決の実行時間に影響を及ぼす結果となっています。
当初MultipartFormDataParserのPackage.swiftは以下のようになっていました。
let package = Package(
name: "MultipartFormDataParser",
platforms: [
.macOS(.v10_14),
.iOS(.v11),
.tvOS(.v11)
],
products: [
.library(
name: "MultipartFormDataParser",
targets: ["MultipartFormDataParser"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.4"),
.package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", from: "9.1.0"),
.package(url: "https://github.com/ishkawa/APIKit.git", from: "5.2.0"),
.package(url: "https://github.com/Moya/Moya.git", from: "15.0.0")
],
targets: [
.target(
name: "MultipartFormDataParser",
dependencies: []
),
.testTarget(
name: "MultipartFormDataParserTests",
dependencies: [
"MultipartFormDataParser",
"Alamofire",
"APIKit",
"Moya",
"OHHTTPStubsSwift"
]
),
]
)
Alamofire
、 APIKit
、 Moya
、 OHHTTPStubs
が dependencies
に指定されており、いずれもテストでしか使われていないことが分かります。
また、 Moya
で RxSwift
と ReactiveSwift
が指定されているようでした2。
解決策
Package.swiftもSwiftファイルなので、Swiftの基本的な機能が使えます。
そこで、テストでだけ使用しているdependenciesを変数として切り出し、リリース時にはそれを含めないようなアプローチを考えました。
import PackageDescription
let isRelease = false // リリース時(タグを打つ時)だけ `true` にする
let testDependencies: [Package.Dependency] = isRelease
? []
: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.4"),
.package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", from: "9.1.0"),
.package(url: "https://github.com/ishkawa/APIKit.git", from: "5.2.0"),
.package(url: "https://github.com/Moya/Moya.git", from: "15.0.0"),
]
let testTargetDependencies: [Target.Dependency] = isRelease
? []
: [
"Alamofire",
"APIKit",
"Moya",
"OHHTTPStubsSwift",
]
let package = Package(
name: "MultipartFormDataParser",
platforms: [
.macOS(.v10_14),
.iOS(.v11),
.tvOS(.v11)
],
products: [
.library(name: "MultipartFormDataParser", targets: ["MultipartFormDataParser"]),
],
dependencies: testDependencies,
targets: [
.target(name: "MultipartFormDataParser", dependencies: []),
.testTarget(
name: "MultipartFormDataParserTests",
dependencies: [
"MultipartFormDataParser",
] + testTargetDependencies
),
]
)
タグを打つときにだけ isRelease = true
とする3所がポイントで、この時Package Managerからは testDependencies
と testTargetDependencies
も空として扱われるようになります。
その証拠に、このバージョンをサンプルプロジェクトに組み込むと、
サンプルプロジェクトで指定している Alamofire
と MultipartFormDataParser
以外のパッケージがfetchされなくなっていることが分かります。
また、Issue Navigatorからも警告が消えました🎉
2024/09/02追記
Package
はclassなので、各propertyを後から弄ることができます。
定義した後のpackage
を弄るように変更することで可読性が上がります。
import PackageDescription
let isDevelop = true // リリース時(タグを打つ時)だけ `false` にする
let isApplePlatform: Bool = { // Linuxで扱えないPackageを除外する
#if canImport(Darwin)
true
#else
false
#endif
}()
let package = Package(
name: "MultipartFormDataParser",
platforms: [
.macOS(.v10_14),
.iOS(.v11),
.tvOS(.v11)
],
products: [
.library(name: "MultipartFormDataParser",
targets: ["MultipartFormDataParser"]),
],
dependencies: testDependencies,
targets: [
.target(name: "MultipartFormDataParser"),
.testTarget(name: "MultipartFormDataParserTests",
dependencies: ["MultipartFormDataParser"]),
]
)
// MARK: - develop
if isDevelop {
if isApplePlatform {
package.dependencies.append(contentsOf: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.7.0"),
.package(url: "https://github.com/ishkawa/APIKit.git", from: "5.7.0"),
.package(url: "https://github.com/Moya/Moya.git", from: "15.0.0"),
])
package.targets
.filter(\.isTest)
.forEach {
$0.dependencies.append(contentsOf: [
"Alamofire",
"APIKit",
"Moya",
])
}
}
}
余計な型宣言や空配列混じりの三項演算子が無くなったことで読みやすくなっているはずです。
まとめ
テストでしか使わないパッケージが残っていると、それも一緒にfetchしてしまうため、利用者側に余計なコストをかけてしまうことがあります。
利用者としては、なるべく依存するライブラリは最小限にとどめておきたいものです。
test-only-dependenciesについてはForums4でも議論されているようですが、2年前で止まっているためしばらくはこういったワークアラウンドが必要だと考えています。
-
Issue Navigatorの画像を見ての通り、Moyaに起因する警告でした ↩
-
Quick
とNimble
も指定されていますがなぜかこれらはfetchされておらず、この原因は分かっていません(末尾の// dev
を見てたりする?) ↩ -
https://github.com/417-72KI/MultipartFormDataParser/blob/1.4.1/Package.swift ↩