LoginSignup
9

More than 1 year has passed since last update.

posted at

updated at

swift-driver を知る

はじめに

みなさん、こんにちは。freddi と申します。福岡在住のiOSエンジニアで、たまにSwiftコンパイラの勉強をしていたり、Swiftコンパイラに関する記事の執筆や登壇をしています。

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

Swiftコンパイラとドライバ

swiftc コマンド

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

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

$ swiftc hello.swift

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

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

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

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

ドライバ とは

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

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

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

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

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

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

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

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

swiftc/swift コマンドの正体

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

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

実は、最新のSwiftコンパイラのブランチでは、swiftswiftc はほぼ同じコマンドであり、それらは swift-frontend という名前のコマンドのシンボリックリンク3となっています。 4 5

なぜ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 です。swift-driver はすでに Swift Package Manager 6 の実装にも取り入れられているなど、登場後すぐに活用はされていますが、そもそもなぜ登場したのでしょうか?

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言語で実装したものが こちら で、README.md どおりに進めると、これをまず動かすことになります。説明通りビルドすると、swift-driver という名前のコマンド(プログラム)が生成され、これが本家 swift-frontend と同じ動きをするようになっています。

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

個人的には、この swift-frontend は所謂サンプルコードのようなものであると思っており、ここでの SwiftDriver などのライブラリの使い方が肝になっていると思います。

なぜ Swift言語でドライバを再実装するのか

このドライバのメリットに関する詳しい議論などはなかなか見つからないのですが、かなり参考になるものとして、Javiさんという方がTwitterで質問し、Doug Gregor さんというAppleのエンジニアが答えたTwitterのスレッドがあります。要約すると、

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

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

  • 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) を利用して実装したもの を読んで理解を深めるのも良いと思います。

この実装に関しては、私が以前 わいわいswiftc という勉強会で解説した のでこちらもご一読すると良いかもしれません。

SwiftDriver に少々入門する

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

Swift Package Manager が SwiftDriver を利用している部分を見てみましょう。フォーカするコードのURLは こちら です。その中から、いくつか特徴的なものを抜粋します。

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

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

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 型はコンパイラのタスクを表しており、こちら を見るとどのような物があるかを Job の派生型として知ることができます。例えば、コンパイルは CompileJob、リンクは LinkJob とその他の Link という単語のついた Job であることがわかります。

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

// 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コンパイラ本体の実装と合わせて深く覗いてみて、ぜひコントリビュートチャレンジしてみてください。


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

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

  3. シンボリックリンクの説明としては、 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 ここがわかり易いです 

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

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

  6. https://github.com/apple/swift-package-manager/pull/2736 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
9