Zenn
㊙️

Package.swiftのヒミツ

に公開
1

Package.swift、めちゃくちゃ便利ですよね。Package.swiftはSwift Package Manager(SPM)で使うマニフェストファイルです。SPMからPackage.swiftは扱われるわけですが、SPMがどのようにPackage.swiftを扱うか気になった事はありませんか?実際にSPMの実装を読むといろんな驚きがあります。この記事ではSPMの実装から分かったPackage.swiftのヒミツについて紙芝居形式で紹介します。


まずは、Package.swiftについておさらいします。Swift Package Managerでは、パッケージの情報をPackage.swiftというSwiftのファイルで定義します。スライドにあるように、packageという名前の変数で、パッケージ名やプロダクト、ターゲットなどパッケージの内容を指定しています。

そんなPackage.swiftですが一つ不思議に思うことがあります。SPMは事前にコンパイルされたコマンドラインツールなので、実行時にPackage.swift内のpackageという変数に直接アクセスすることはできません。SPMから見るとPackage.swiftはただのテキストファイルです.私達が普段作っているiOSやmacOSのアプリケーションも、実行時に新しいソースコードを読み込んでその中に定義されたシンボルを参照したりできませんよね。では、SPMはどうやってPackage.swiftに書かれたパッケージ情報を読み取っているのでしょうか?

この不思議に迫るためにPackage.swiftの仕組みを探ってみます。このメカニズムを理解しておくと、将来自分で似たツールを作るときに役立つ事間違いなし。それでは早速中身を見ていきます!

内部動作を知るために、swift package resolve —verboseというコマンドを試してみます。verboseなログからSPMがPackage.swiftを読み取る際に何をしているか推測する作戦です。

これが実行結果です.なお、(今回に限らず)出力は分かりやすくしたりユーザネームを隠すために、一部編集しています。意味が変わらない程度に留めているのでその点は安心してください。

2つのコマンドの実行ログが出力されていますね。順に見ていきましょう。

まず最初にswift-frontendというコマンドが動いていることが分かります。入出力のパラメータからSPMがPackage.swiftをオブジェクトファイルに変換している事が推測できます。

次にclangが実行されているのが確認できます。PackageDescriptionライブラリなど必要なライブラリが組み合わされ、manifestという実行可能ファイルが作られています.

この実行ファイルは何を行うんでしょうか?実際にその生成された実行ファイルをターミナルで実行してみましょう

結果は…何も起こりません。

なぜでしょうか?ここで改めてPackage.swiftの中身を思い出してみます。Package.swiftには変数の定義しか無くprint等の出力する処理は書かれていません。ですから、何も表示されなかったのは当然といえば当然ですね。

しかし、このままではSPMがパッケージ情報を取得する仕組みがわかりません。ここからはSPMの実装を直接調べてみる事にします。

SPMのソースコードはオープンソースになっており、GitHubで誰でも見ることができます。ええやん!

まず注目したのは、SPMが提供するPackageDescriptionというコードです。この中に今回の謎を解く鍵がありそうです。スライドに映っっているのは,Package構造体のイニシャライザです.

そして,initializerの中ではregisterExitHandlerという見慣れないメソッドが呼ばれています.initializerの中でこのようなside effectを起こしそうなメソッド呼び出しがあるのは、少し気になりますよね。謎を解くオイニーがします。

registerExitHandlerを調べてみると、このイニシャライザにはfilenoというオプションが与えられた時だけ動く隠しロジックが含まれていました。何も出力されなかったのはfilenoを渡していないからかもしれません.

では、その-filenoには何を渡せばいいのでしょうか?SPMの実装をさらに追って、ManifestLoader.swiftというファイルを見てみます。そこでは、一時的なJSONファイルを用意し、そのファイルディスクリプタの番号を先ほどの-filenoオプションに指定しています.

この仕組みを検証するために、再びmanifest実行ファイルを手動で実行してみましょう。一時ファイルを作るのが面倒だったので、今回はfilennoにJSONファイルではなく標準出力を使ってみます。

するとどうでしょう、JSONの情報が出力されました!スライドに小さく表示されていますが、中身を読むと、パッケージの情報がJSONオブジェクトとして書かれています。

ではSPM本体はそのJSONをどうするのでしょうか?実は、SPMのManifestLoaderの実装には、先ほど生成されたJSONを読み込んでパースする処理も書かれています。

結論です。SPMはPackage.swiftをコンパイル・実行してJSONを生成し、それをパースしてパッケージ情報を得ています。Package.swiftを実行してJSONを得られるのは奇妙に思えますが、PackageのinitializerにはJSONを出力する処理が隠されているので,このような仕組みが実現できているんですね。


いかがだったでしょうか。普段使っているツールの内部を知ることは、知識を得られる他、より親しみを持てるようになる機会になりました。皆さんが持っているSPMのヒミツ情報もぜひコメントで教えて下さい。

1

Discussion

ログインするとコメントできます