SwiftPackageManagerで依存グラフを書く
記事を書くのが久しぶりすぎて、記事の書き方を忘れましたTOSHです。
iOS開発におけるライブラリ管理ツールはCocoaPods, Carthage, SPMといったようにいろんな歴史と共に移り変わってきました。
自分もCocoaPods信者ではありましたが、CocoaPodsがメンテナンスモードに入ったこと、Xcode16以降でファーストパーティであるSPMもだいぶ安定してきたこともりありSPMに徐々に移行していきました。
本記事ではその中で、SPMにおけるライブラリの依存関係を可視化する方法を紹介しようと思います。
前提条件
Package.swiftが必要です。
XcodeのGUI上でぽちぽち追加をしていると、Package.swiftは生成されず、ライブラリの依存情報は.pbxprojファイル
に保存されていきます。この場合だと今回紹介する方法では、依存関係のグラフを作成することができないので、こちらの記事を参考にPackage.swiftを導入してみましょう。
TL;DR
Package.swiftが一番上の階層であるディレクトリまで移動して下記のコマンドを実行してあげてください。
swift package show-dependencies --format dot | dot -Tpdf -o graph.pdf
やり方
Package.swiftを作成する
こちらの記事を参考にPackage.swiftを作成しました。。
ディレクトリ構成に追加は下記のようになっています。
.
└── SampleProject/
├── Dependencies/
│ ├── Sources
│ ├── Package.swift
│ └── Package.resolved
├── SampleProject
├── SampleProject.xcodeproj
└── SampleProject.xcworkspace
Package.swift
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Dependencies",
platforms: [.iOS(.v16)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "Dependencies",
targets: ["Dependencies"]
),
],
dependencies: [
.package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.29.0"),
.package(url: "https://github.com/googleanalytics/google-tag-manager-ios-sdk", from: "7.4.0"),
.package(url: "https://github.com/google/GoogleDataTransport", from: "9.4.0"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "Dependencies",
dependencies: [
.product(name: "FirebaseAnalytics", package: "firebase-ios-sdk"),
.product(name: "GoogleTagManager", package: "google-tag-manager-ios-sdk"),
.product(name: "GoogleDataTransport", package: "GoogleDataTransport"),
]
),
]
)
Package.resolved
上記のPackage.swiftをresolveすると、下記のようにresolveされます。
{
"originHash" : "e4c7f43051438a4d0246859880214c62630b38cba31d95dbe6dffe6a108d3748",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27",
"version" : "1.2024011602.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "3b62f154d00019ae29a71e9738800bb6f18b236d",
"version" : "10.19.2"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "eca84fd638116dd6adb633b5a3f31cc7befcbb7d",
"version" : "10.29.0"
}
},
{
"identity" : "google-tag-manager-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleanalytics/google-tag-manager-ios-sdk",
"state" : {
"revision" : "6883594bd2db065aac641851abd1f24f0f8910fb",
"version" : "7.4.6"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "fe727587518729046fc1465625b9afd80b5ab361",
"version" : "10.28.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport",
"state" : {
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
"version" : "9.4.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6",
"version" : "7.13.3"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359",
"version" : "1.62.2"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
"version" : "3.5.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
"version" : "100.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "ebc7251dd5b37f627c93698e4374084d98409633",
"version" : "1.28.2"
}
}
],
"version" : 3
}
これは余談ですが、Package.swiftの// swift-tools-version
を6.0
にするとresolvedのpinsのバージョンは3が適用され、それ以下でセットすると2が適用される。
SPMのPackage.resolvedはCocoapodsのPodfile.lockとは違い、それぞれのライブラリのdependencyの依存関係までは読み解くことができず、最終的なresolveした結果しか載っていません。
これだと何のバージョンが何に依存しているかがわかりませんよね。
ではこの依存関係を読み解いていきましょう。
show-dependenciesコマンドを使用する
swift packageコマンドにはshow-dependencies
というコマンドがあります。これを使用すると、下記のようなtree構造で出力されます。
$ cd <プロジェクトpath>/Dependencies
$ swift package show-dependencies
.
├── firebase-ios-sdk<https://github.com/firebase/firebase-ios-sdk@10.29.0>
│ ├── promises<https://github.com/google/promises.git@2.4.0>
│ ├── swift-protobuf<https://github.com/apple/swift-protobuf.git@1.28.2>
│ ├── googleappmeasurement<https://github.com/google/GoogleAppMeasurement.git@10.28.0>
│ │ ├── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ │ │ └── promises<https://github.com/google/promises.git@2.4.0>
│ │ └── nanopb<https://github.com/firebase/nanopb.git@2.30910.0>
│ ├── googledatatransport<https://github.com/google/GoogleDataTransport@9.4.0>
│ │ ├── nanopb<https://github.com/firebase/nanopb.git@2.30910.0>
│ │ ├── promises<https://github.com/google/promises.git@2.4.0>
│ │ └── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ │ └── promises<https://github.com/google/promises.git@2.4.0>
│ ├── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ │ └── promises<https://github.com/google/promises.git@2.4.0>
│ ├── gtm-session-fetcher<https://github.com/google/gtm-session-fetcher.git@3.5.0>
│ ├── nanopb<https://github.com/firebase/nanopb.git@2.30910.0>
│ ├── abseil-cpp-binary<https://github.com/google/abseil-cpp-binary.git@1.2024011602.0>
│ ├── grpc-binary<https://github.com/google/grpc-binary.git@1.62.2>
│ │ └── abseil-cpp-binary<https://github.com/google/abseil-cpp-binary.git@1.2024011602.0>
│ ├── leveldb<https://github.com/firebase/leveldb.git@1.22.5>
│ ├── interop-ios-for-google-sdks<https://github.com/google/interop-ios-for-google-sdks.git@100.0.0>
│ └── app-check<https://github.com/google/app-check.git@10.19.2>
│ ├── promises<https://github.com/google/promises.git@2.4.0>
│ └── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ └── promises<https://github.com/google/promises.git@2.4.0>
├── google-tag-manager-ios-sdk<https://github.com/googleanalytics/google-tag-manager-ios-sdk@7.4.6>
│ ├── firebase-ios-sdk<https://github.com/firebase/firebase-ios-sdk@10.29.0>
│ │ ├── promises<https://github.com/google/promises.git@2.4.0>
│ │ ├── swift-protobuf<https://github.com/apple/swift-protobuf.git@1.28.2>
│ │ ├── googleappmeasurement<https://github.com/google/GoogleAppMeasurement.git@10.28.0>
│ │ │ ├── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ │ │ │ └── promises<https://github.com/google/promises.git@2.4.0>
│ │ │ └── nanopb<https://github.com/firebase/nanopb.git@2.30910.0>
│ │ ├── googledatatransport<https://github.com/google/GoogleDataTransport@9.4.0>
│ │ │ ├── nanopb<https://github.com/firebase/nanopb.git@2.30910.0>
│ │ │ ├── promises<https://github.com/google/promises.git@2.4.0>
│ │ │ └── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ │ │ └── promises<https://github.com/google/promises.git@2.4.0>
│ │ ├── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ │ │ └── promises<https://github.com/google/promises.git@2.4.0>
│ │ ├── gtm-session-fetcher<https://github.com/google/gtm-session-fetcher.git@3.5.0>
│ │ ├── nanopb<https://github.com/firebase/nanopb.git@2.30910.0>
│ │ ├── abseil-cpp-binary<https://github.com/google/abseil-cpp-binary.git@1.2024011602.0>
│ │ ├── grpc-binary<https://github.com/google/grpc-binary.git@1.62.2>
│ │ │ └── abseil-cpp-binary<https://github.com/google/abseil-cpp-binary.git@1.2024011602.0>
│ │ ├── leveldb<https://github.com/firebase/leveldb.git@1.22.5>
│ │ ├── interop-ios-for-google-sdks<https://github.com/google/interop-ios-for-google-sdks.git@100.0.0>
│ │ └── app-check<https://github.com/google/app-check.git@10.19.2>
│ │ ├── promises<https://github.com/google/promises.git@2.4.0>
│ │ └── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ │ └── promises<https://github.com/google/promises.git@2.4.0>
│ └── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
│ └── promises<https://github.com/google/promises.git@2.4.0>
└── googledatatransport<https://github.com/google/GoogleDataTransport@9.4.0>
├── nanopb<https://github.com/firebase/nanopb.git@2.30910.0>
├── promises<https://github.com/google/promises.git@2.4.0>
└── googleutilities<https://github.com/google/GoogleUtilities.git@7.13.3>
└── promises<https://github.com/google/promises.git@2.4.0>
これでPodfile.lockのようなdependency情報を出すことができました。
PDFへと変換する
ただ、まだ見にくいですよね、これをPDFにしていきましょう。一度Graphvizのdot言語にするために
--format
をいうoptionを使いましょう。
swift package show-dependencies --format dot
これをさらに、PDFに変換します。コマンドをまとめるとこんな感じです。
swift package show-dependencies --format dot | dot -Tpdf -o graph.pdf
すると下記のように書き出されます。おっと、zennはPDFガゾウガハレナイミタイエデスネ。
仕方がないですね、ではPNGで書き出しましょう。
$ swift package show-dependencies --format dot | dot -Tpng -o graph.png
できました!
Discussion