Open14

Swift Package Manager の実装を追ってみる (`swift package resolve` 編)

Yutaka TajikaYutaka Tajika

swift package resolve は resolve というサブコマンドなので、 SwiftPackageTool の Configuration で対応するサブコマンドがあ確認する。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/Commands/PackageTools/SwiftPackageTool.swift#L31-L70

Resolve 型が定義されていそう。
ファイルは Sources/Commands/PackageTools/Resolve.swift

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/Commands/PackageTools/Resolve.swift

Yutaka TajikaYutaka Tajika

Resolve 型を見ていく。

こちらも Default 型のコマンド同様に SwiftCommand に準拠している。
いくつか引数やオプションを取ることができるようだが、今回はそれらは置いておいて、引数もオプションもなしで呼び出した時の挙動を見てみる。

実際の処理は run(_ swiftTool: SwiftTool) を見ていけば良い。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/Commands/PackageTools/Resolve.swift#L42-L61

早速処理の分岐があるが、 if の処理は引数 <package-name> を指定しているなので、今回は else の方を見る。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/Commands/PackageTools/Resolve.swift#L59

SwiftTool 型に生えている resolve メソッドを呼び出している様子。

Yutaka TajikaYutaka Tajika

SwiftTool.resolve() の定義を確認する。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L455-L470

早速 1行目の let workspace = try getActiveWorkspace() から分からないので追ってみる。

getActiveWorkspace() の定義はここ。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L346-L387

_workspace に値があればこれを返すようになっています。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L347-L349

_workspace は同じく getActiveWorkspace() で workspace を取得できた場合にその値を保持しておくもののようです。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L164-L169

今回の swift package resolve では _workspace は保持されていない状態で処理を通るので、この早期 return よりも先の処理を見ていく必要がありそう。

Yutaka TajikaYutaka Tajika

getActiveWorkspace() の続き。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L351

self.workspaceDelegateProvider がどこからくるか?

追っていくと、どうやら↓にて SwiftTool の extension で定義されている様子。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/Commands/ToolWorkspaceDelegate.swift#L184-L188

ちなみに WorkspaceDelegateProvider 型はいくつかの引数を取って WorkspaceDelegate 型を返す closure の typealias になっている。
https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L65

ToolWorkspaceDelegateWorkspaceDelegate に準拠しているので、 WorkspaceDelegateProvider では ToolWorkspaceDelegate を返している。
次はこの ToolWorkspaceDelegate を見ていく。

Yutaka TajikaYutaka Tajika

定義はこのファイル。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/Commands/ToolWorkspaceDelegate.swift

ざっとメソッドを眺めてみるとパッケージの取得やチェックアウト、binary のダウンロード周りに関するデリゲートになっている。

さらに initializer で渡している outputHandler, progressHandler については、それぞれ標準出力のためのハンドラと取得中の進捗状況等を返すハンドラであることが分かる。

Yutaka TajikaYutaka Tajika

printprogress が使われていたので、それらを順に見ていく。

print はここ。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L36-L39

outputHandler をラップしているだけのようなので、 outputHandler.print を見てみる。
定義はすぐ下にある。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L102-L110

write を呼び出しているのでそっちも見る。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L132-L139

必要に応じて改行を加えて writer.write を呼んでいる。

writer.write も見てみる。
writer はすぐ下にある InteractorWriter 型のよう。
https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L147

InteractorWriter.write はこちら。
https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L160-L168

term の有無で処理が変わっている。
term は TerminalController のオプショナル型。
https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L149

TerminalController は swift-tools-support-core モジュールで定義されているもので、ターミナルへの出力を扱うクラスになっている様子。
https://github.com/apple/swift-tools-support-core/blob/main/Sources/TSCBasic/TerminalController.swift

これがオプショナルなのは、 TerminalController の initializer が failable initializer になっているから。
https://github.com/apple/swift-tools-support-core/blob/main/Sources/TSCBasic/TerminalController.swift#L79-L106

Swift Package Manager ではこの Initializer の OutputByteStream として、TSCBasic.stderrStream を渡している。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L202

TSCBasic.stderrStream はこちら。

https://github.com/apple/swift-tools-support-core/blob/adefcdf81df8f300bd718df593091d564fbad323/Sources/TSCBasic/WritableByteStream.swift#L795

これで TerminalController は nil になっていないようなので、 最終的には term.write が呼ばれてターミナルへの標準出力がされる様子。

Yutaka TajikaYutaka Tajika

次に progress も見ていく。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L41-L44

これも同じく outputHandler をラップしているだけ。

OutputHander.progress はこちら

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L112-L121

progressAnimation.update を呼んでいるので、 progressAnimation を見てみる。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift#L68-L70

progressAnimation 自体は ProgressAnimationProtocol という swift-tools-support-core の TSCUtility モジュールで定義されている型になっている。

https://github.com/apple/swift-tools-support-core/blob/main/Sources/TSCUtility/ProgressAnimation.swift

MultiLineNinjaProgressAnimationNinjaProgressAnimation も同じく swift-tools-support-core のもの。

実際の挙動はこの辺が参考になりそう。

https://www.fivestars.blog/articles/executables-progress/

verbose する場合は MultiLineNinjaProgressAnimation を使って進捗を全て表示しているということらしい。それ以外はよしなに適切な表示をしてくれる様子。

Yutaka TajikaYutaka Tajika

これで SwiftTool.getActiveWorkspace() に戻って来れるので、続きを見ていく。

Yutaka TajikaYutaka Tajika

Workspace の initializer に渡しているものを一つずつみていく。

fileSystem

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L354

self.fileSystem を渡しており、これは SwiftTool.initlocalFileSystem を入れていることが分かる
https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L207

localFileSystem は swift-tools-support-core で定義されているもの。
https://github.com/apple/swift-tools-support-core/blob/main/Sources/TSCBasic/FileSystem.swift#L1181-L1193

Yutaka TajikaYutaka Tajika

次に location

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L355-L364

Workspace における諸々のディレクトリ/ファイルの場所を表現している型のように見える。

scratchDirectory

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L356

これは initializer でこんな感じで設定されている。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L295-L298

環境変数から取得するか、オプションから取得するか、デフォルトの .build を使用するかになる様子。
オプションの内容や .build というディレクトリから見ても、ビルド成果物を格納するディレクトリを示すものであることがわかる。

editsDirectory

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L357

これは swift package edit <package-name> としたときにローカルにコピーされるパッケージの場所を表すものらしい。

getEditsDirectory を見てみる。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L389-L395

--multiroot-data-file オプションをつけていればそのディレクトリの下の Packages ディレクトリ、なければパッケージのルート下の Packages ディレクトリが設定されるようになっている。

せっかくなので getPackageRoot() も見ておく。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L123-L128

packageRoot は initializer で findPackageRoot(fileSystem:) を通して設定される。
https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L291-L294

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/CoreCommands/SwiftTool.swift#L744-L757

Manifest.filenamePackage.swift のこと。

https://github.com/apple/swift-package-manager/blob/swift-5.8-DEVELOPMENT-SNAPSHOT-2023-03-17-a/Sources/PackageModel/Manifest.swift#L19-L25

Package.swift が見つかるまで current directory からファイルシステムのルートディレクトリまで親のディレクトリを遡って探している様子。
つまり、 Package.swift があるディレクトリの子孫ディレクトリにいる場合は swift package コマンドは機能しそう。