swift-driver を知る

# はじめに
みなさん、こんにちは。[freddi](https://twitter.com/___freddi___) と申します。福岡在住のiOSエンジニアで、たまにSwiftコンパイラの勉強をしていたり、Swiftコンパイラに関する記事の執筆や登壇をしています。

本日、Swiftアドベントカレンダー 2020の12/15の記事として、AppleがOSSとして出しているSwift製のSwiftコンパイラのドライバ [swift-driver](https://github.com/apple/swift-driver) の紹介と、その `swift-driver` が、どこで、どのよう使われているかについて軽く解説します。

# Swiftコンパイラとドライバ
## `swiftc` コマンド

皆さん、Xcodeを使わずにTermnal上で `swiftc` コマンドを使って、Swiftコードをコンパイルしたことはありますか？

例えば `hello.swift` という名前のSwiftで書かれたソースファイルをコンパイルして、Terminal上などで実行できる形式にしたい場合、

```shell
$ swiftc hello.swift
```

というふうに書きます。これで、同じディレクトリに `hello` という名前の、実行可能な形式のファイルが出来上がります。

私達は、このように `swiftc` コマンドを使ってプログラムをコンパイル/ビルドしています。なので、普段 `swiftc` というコマンド（プログラム）そのものがコンパイラ本体だと思われがちかもしれません。

例えば、C言語をコンパイルするための `gcc` コマンドも、同じようなノリで `gcc` そのものがコンパイラプログラム全てとして捉えられてしまうことが多々あると思います。

ですが、みなさんが触っている `swiftc` や `gcc` といったコマンドは、実はコンパイラすべてを包括したものではない、**コンパイラドライバ** と呼ばれるプログラムです。さて、ドライバとは一体何でしょうか？

## ドライバ とは
まず、**ドライバ** について一体何かを解説します。ここでいうドライバとは、ハードウェア等の導入に必要なドライバとはまた別のものを指しています[^driver-source]。ドライバと調べても、多分こちらのハードウェアのほうが多く出るので、この記事の解説対象になっている方の意味を知ることは、なかなか少ないかもしれません。

ドライバプログラムとは、一言で言えば、**いくつかのパーツとしてのプログラムのを適切に参照・呼び出しをするプログラム** を指します。さて、この意味について、`gcc` コマンドを例に掘り下げていきましょう。[^driver-source]

まず、`gcc` でC言語のソースファイルを実行可能な形式のものにする際に、いくつかフェーズを踏むのはご存知でしょうか。順に大まかに列挙すると、

- プリプロセス
- コンパイル
- アセンブル
- リンク

という風になっています。

実は、それぞれのフェーズは別のコマンド（プログラム）が行っており、全て `gcc` が担っているわけではありません。こちらも列挙すると、

- プリプロセス - [cppコマンド](http://x68000.q-e-d.net/~68user/unix/pickup?cpp)
- コンパイル - [cc(1)コマンド](http://www2.scphys.kyoto-u.ac.jp/BIRDnet/manuals/A1_BIRDintro/cc1.html)
- アセンブル - [asコマンド](https://kazmax.zpp.jp/cmd/a/as.1.html)
- リンク - [ld など](https://kazmax.zpp.jp/cmd/l/ld.1.html)

`gcc` は単に **細かいオプション引数と言ったユーザーの要求を元に、それらのプログラムをフェーズ順に呼び出ている** だけです。これが コンパイラと呼ばれるもののドライバプログラムに当たります。これは `swiftc` についても同様です。

ドライバに関する詳しい働き等は、この項目を執筆する際に情報元になった http://nenya.cis.ibaraki.ac.jp/TIPS/compiler.html を見てもらえればと思います。

## `swiftc`/`swift` コマンドの正体
ここでは、`swiftc`/`swift` コマンドのドライバとしての機能について少々フォーカスします

前項目では、`swiftc` はコンパイラそのものではなくドライバプログラムであることと、ドライバプログラムとは一体何かについて説明しました。また、ご存じの方もいると思いますが、`swiftc` だけでなく、`swift` コマンド [^repl] も存在しますがこちらもドライバです。

実は、最新のSwiftコンパイラのブランチでは、`swift` と `swiftc` はほぼ同じコマンドであり、それらは `swift-frontend` という名前のコマンドのシンボリックリンク[^sl]となっています。 [^swiftc-github1] [^swiftc-github2]

なぜ`swift` / `swiftc` に分かれているかというと、Swiftコンパイラのドライバとしての働き（実行可能形式なものを生成するか、REPLモード etc ...）は、付与されるオプション引数にもよりますが、`swift-frontend` のシンボリックリンクが、どのような名前で呼び出されるかによっても変わります。

https://github.com/apple/swift/blob/main/lib/Driver/Driver.cpp#L98-L105 をみると、どのコマンドとして呼ぶと、ドライバがどの様に働くようになるかがわかります。

# `swift-driver`

さて、ここまではSwiftコンパイラのドライバの話をしましたが、SwiftコンパイラはほとんどがC++で開発されており、ドライバ周りもC++で実装されています。

そのSwiftコンパイラのドライバプログラムをSwift言語で実装したものが、タイトルにもある **[swift-driver](https://github.com/apple/swift-driver)** です。`swift-driver` はすでに Swift Package Manager [^spm] の実装にも取り入れられているなど、登場後すぐに活用はされていますが、そもそもなぜ登場したのでしょうか？

## `swift-driver` とは
そもそも、`swift-driver` とは何でしょうか？

`swift-driver` は Apple製のOSSで、先程申し上げた `swift` / `swiftc` の働きをC++実装からSwiftになるべく移植したもの、及び、それを実現するためのライブラリ (`SwiftDriver` など) からなっています。理由は後述しますが、`SwiftDriver` という内部のライブラリがこのOSSの肝だと、私は思っています。

`swift-driver` はコンパイルの各フェーズをなるべくSwiftでラップしているだけで、最終的には裏で `swift-frontend` などを通じて各フェーズを呼び出したり、リンクする標準ライブラリの情報などが必要になるため、既存の Swiftコンパイラ一式 は事前に準備が必要です。

## `swift-driver` によるドライバ実装

`swift-frontend` の動きをSwift言語で実装したものが [こちら](https://github.com/apple/swift-driver/blob/main/Sources/swift-driver/main.swift) で、[README.md](https://github.com/apple/swift-package-manager/blob/main/README.md) どおりに進めると、これをまず動かすことになります。説明通りビルドすると、`swift-driver` という名前のコマンド（プログラム）が生成され、これが本家 `swift-frontend` と同じ動きをするようになっています。

こちらも、先程 `swift-frontend` で言及したやり方と同様、`swift` または `swiftc` という名前のシンボリックリンクを作って呼び出すなどして、呼び出し名による機能の分岐などを行ったりします。また本家同様のオプション引数も付けれます。

個人的には、この `swift-frontend` は所謂サンプルコードのようなものであると思っており、ここでの `SwiftDriver` などのライブラリの使い方が肝になっていると思います。
 
## なぜ Swift言語でドライバを再実装するのか
このドライバのメリットに関する詳しい議論などはなかなか見つからないのですが、かなり参考になるものとして、[Javiさんという方がTwitterで質問し、Doug Gregor さんというAppleのエンジニアが答えたTwitterのスレッドがあります](https://twitter.com/dgregor79/status/1260798039515488257)。要約すると、


- このドライバは Swift Package Manager の外側のビルドシステムからは不透明な、インクリメンタルビルドのための内部ビルドシステムを備えている
- このドライバを使うと、Swift Package Manager はドライバのビルドタスクを自身のビルドグラフに統合できるし、一つのシングルキューで並行タスクを管理できるようになる
- このドライバで動的スケジューリングができるようになる

ということをメリットに上げています。見ての通り少々難しいので、Swift Package Manager に `swift-driver` が導入されたときのPull Request の Description [^spm] も参考にしつつ、言い換えると、

- Swift言語製のビルド関係のツールで、Swiftコンパイラを、`swift` / `swiftc` といった外部のコマンド・プロセスとして扱うのではなく、できる限り内部のSwiftプログラムの一部として扱うことができる
- 各フェーズの呼び出しなども `swift-driver` を通して細かく扱えるので、今まで手の届かなかった上記の要約のような改善ができるようになる（なった）

というのが一番簡単な説明になると思います。

## `swift-driver` はSwiftのライブラリとして扱える
前項で言及したことや、Swift Package Manager の実装に取り入れられていることからわかりますが、`swift-driver` は、`SwiftDriver` という名前のSwiftのライブラリとして扱えます。なので、Swift言語製のSwiftに関するビルドツールを作るとき、より高度なビルド・コンパイルの設定・構築・管理を行う場合に役に立ちます。

しかし、うまく利用するには、ある程度のコンパイラの知識、Swiftコンパイラ本体のドライバの実装などについて知る必要があるかもしれません。もしくは、先程言及した　[swift-frontend の動きを swift-driver(SwiftDriver) を利用して実装したもの](https://github.com/apple/swift-driver/blob/main/Sources/swift-driver/main.swift) を読んで理解を深めるのも良いと思います。

この実装に関しては、私が以前 [わいわいswiftc という勉強会で解説した](https://speakerdeck.com/freddi/jin-ri-karashi-meruswift-driver) のでこちらもご一読すると良いかもしれません。

## `SwiftDriver` に少々入門する
さて、ここまで説明した中で、`SwiftDriver` に入門しないのはもったいないので、ついでにこのライブラリの使い方について、少々入門してみましょう。

Swift Package Manager が `SwiftDriver` を利用している部分を見てみましょう。フォーカするコードのURLは [こちら](https://github.com/DougGregor/swift-package-manager/blob/c818cdb7d9cbef9d8bff6e0d96cbce9bd77b6bd7/Sources/Build/ManifestBuilder.swift#L203-L275) です。その中から、いくつか特徴的なものを抜粋します。


注意ですが、このコードはSwift Package Manager が導入された当時のコードであり、現在はほぼ改変されています。この時期のコードを選んだのは、このときが一番 `SwiftDriver` をピュアにわかりやすく利用しているからです。

まず最初の２行ですが、これは一体何をしているのでしょうか。

```swift
var driver = try Driver(args: target.emitCommandLine())
let jobs = try driver.planBuild()
```

まず一行目は、`SwiftDriver` にある、`Driver` 型のオブジェクトを生成しています。こちらは、Swift Package Manager が構築した、本来 `swift-frontend` に渡すべきコマンドやオプション引数を `Driver` のイニシャライザに渡しています。分かり易い例として上げると `swiftc hello.swift` を渡しても構いません。

次の `try driver.planBuild()` ですが、こちらは `Driver` に先程渡された情報から、コンパイラのどのコマンドやプログラムを、どのような順序で呼び出すべきかを構築して、その情報を `Job` という型の配列を返します。

`Job` 型はコンパイラのタスクを表しており、[こちら](https://github.com/apple/swift-driver/tree/main/Sources/SwiftDriver/Jobs) を見るとどのような物があるかを `Job` の派生型として知ることができます。例えば、コンパイルは [CompileJob](https://github.com/apple/swift-driver/blob/main/Sources/SwiftDriver/Jobs/CompileJob.swift)、リンクは [LinkJob](https://github.com/apple/swift-driver/blob/main/Sources/SwiftDriver/Jobs/LinkJob.swift) とその他の `Link` という単語のついた `Job` であることがわかります。

どのような `Job` であるかは、`kind` という変数で知ることができます。下記は、先程の Swift Package Manager の 207行目以降のコードですが、先程生成された `jobs` をfor文で回して、それぞれのタスクの種類の情報から適当な処理を行おうとしています。（FIXMEがついていますが、現在はだいぶ改変されているので消えています）

```swift
// https://github.com/DougGregor/swift-package-manager/blob/c818cdb7d9cbef9d8bff6e0d96cbce9bd77b6bd7/Sources/Build/ManifestBuilder.swift#L207
for job in jobs {
    // Figure out which tool we are using.
    // FIXME: This feels like a hack.
    var datool: String
    switch job.kind {
    case .compile, .mergeModule, .emitModule, .generatePCH,
        .generatePCM, .interpret, .repl, .printTargetInfo,
        .versionRequest:
        datool = buildParameters.toolchain.swiftCompiler.pathString

    case .autolinkExtract, .generateDSYM, .help, .link, .verifyDebugInfo:
        datool = try resolver.resolve(.path(job.tool))
    }
...
```

このように、SwiftコンパイラのタスクをSwiftのプログラム上で簡単に扱うことができます。

## まとめ
本日は、SwiftUIやアプリ開発といった話題ではなくディープな話題になりましたが、いかがだったでしょうか？ 

`swift-driver` は見たところまだ発展途上のOSSであり、至るところに `FIXME:` といったコメントがあるので、コントリビュートのチャンスがたくさんあると思います。もし興味があれば、Swiftコンパイラ本体の実装と合わせて深く覗いてみて、ぜひコントリビュートチャレンジしてみてください。



[^driver-source]: 情報ソースはこちら(https://www.quora.com/What-is-a-driver-for-example-a-compiler-driver) こちらではハードウェア方面の意味のドライバも解説があります

[^repl]: ご存じの方が多いと思いますが、こちらは REPLモード(http://blog.andgenie.jp/articles/639) の呼び出しの目的で主に使われています

[^swiftc-github1]: https://github.com/apple/swift からSwiftコンパイラをビルドすると、以前のように `swiftc` および `swift` コマンドは生成されません。代わりに説明にある通り `swift-frontend` という名前のコマンドが生成されるようになります。https://github.com/apple/swift/pull/28003

[^swiftc-github2]: https://swift.org/download/#releases の Trunk Development (main) からToolchain をインストールすることでも確認ができます。ちなみに現在の公式リリースの Xcode12.* では、まだ `swift-frontend` コマンドはないです

[^sl]: シンボリックリンクの説明としては、 http://e-words.jp/w/%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AA%E3%83%83%E3%82%AF%E3%83%AA%E3%83%B3%E3%82%AF.html#:~:text=%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AA%E3%83%83%E3%82%AF%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%A8%E3%81%AF%E3%80%81%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0,%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B%E4%BB%95%E7%B5%84%E3%81%BF%E3%80%82 ここがわかり易いです

[^spm]: https://github.com/apple/swift-package-manager/pull/2736

[^spm-driver]: https://github.com/apple/swift-package-manager/pull/2736/files#diff-a71cb084c822ae5373c056ea92b805091ae57252c6cf2db3d97b7c7fad85ad07 の変更を見ると、