Swiftコンパイラ開発用の環境構築

この記事では、swiftコンパイラ開発における作業環境構築の知見を紹介します。

# リポジトリ構成

swiftコンパイラプロジェクトは、複数のリポジトリから構成されています。その中で主役となるのが[apple/swift](https://github.com/apple/swift)です。これら複数のリポジトリの管理は、git submoduleなどの一般的な仕組みではなく、swiftコンパイラプロジェクトで独自に提供される管理操作用のスクリプトを用いて行います。

複数のリポジトリは、同一のディレクトリに列挙されます。そのため、swiftコンパイラ開発の作業用ディレクトリを1つ用意して、その中に主役のswiftリポジトリをチェックアウトし、その他のリポジトリもそのディレクトリに列挙されるようにします。作業用ディレクトリの名前はなんでも良いですが、記事中では`swift-source`とします。

# チェックアウト

## 初回構築

初めて作業環境を構築するときは、まずはじめにswiftコンパイラ開発作業用のディレクトリを作成し、その中に主役のswiftリポジトリをチェックアウトします。

```
$ mkdir swift-source
$ cd swift-source
$ git clone https://github.com/apple/swift.git
```

次に、主役のリポジトリに含まれる管理用スクリプトの`utils/update-checkout`を使用して、その他のリポジトリをチェックアウトします。初回実行時には、`--clone`オプションが必要です。

```
$ cd swift-source/swift
$ utils/update-checkout --clone
```

初回のチェックアウトはめちゃくちゃ時間がかかり、進捗率が全然変化しないため、フリーズしているか不安になりがちですが、よくあることなので多分大丈夫です。

## ウィルス対策ソフトは無効化する

プロジェクトの中にはwindows向けのexeファイルなども含まれており、ウィルス対策ソフトがこれを削除する事があります。しかし、削除が発生するとリポジトリに変更が発生し、その後のリポジトリ操作に問題が起きます。そのようなことがないように、ウィルス対策ソフトは無効化するか、swiftコンパイラ開発作業用のディレクトリはホワイトリストに登録しましょう。

## チェックアウトスクリプトのヘルプ

チェックアウトスクリプトは、`--help`オプションでヘルプが出ます。オプションの指定方法がわからなくなったらこれを参照しましょう。

```
$ cd swift-source/swift
$ utils/update-checkout --help
```

## 2回目以降のチェックアウト

2回目以降の実行では、`--clone`オプションは必要ありません。オプションなしで以下のように実行すれば良いです。

```
$ cd swift-source/swift
$ utils/update-checkout
```

普通にgitで差分を取得するだけなので、現実的な時間で完了します。

## ブランチスキーマでのチェックアウト

通常のチェックアウトでは各リポジトリのmasterをチェックアウトします。リポジトリごとにチェックアウトするブランチを定めた情報をブランチスキーマと呼び、update-checkoutにはあらかじめ定義したスキーマが指定できます。使用できるスキーマは`utils/update-checkout-config.json`に定義されています。

以下に`swift-4.1-branch`スキーマをチェックアウトする例を示します。

```
$ cd swift-source/swift
$ utils/update-checkout --scheme swift-4.1-branch
```

## スナップショットのチェックアウト

swiftコンパイラプロジェクトでは、masterは壊れている事があります。壊れていないコミットをチェックアウトしたい場合には、デイリーのCIが安定版のスナップショットを作成しているため、それをチェックアウトするのが便利です。

スナップショットはswiftリポジトリのgitのtagとして発行されているため、以下のようにしてスナップショット名を取得できます。

```
$ cd swift-source/swift
$ git tag -l
```

update-scriptには、`--tag`オプションでチェックアウトするタグを指定できます。以下に、2018/03/02のスナップショットをチェックアウトする例を示します。

```
$ cd swift-source/swift
$ utils/update-checkout --tag swift-DEVELOPMENT-SNAPSHOT-2018-03-02-a
```

# ビルド

ビルドはswiftリポジトリの`utils/build-script`という専用の管理スクリプトを用いて行います。このスクリプトは、下請けのビルドツールが指定できます。ビルドツールはninjaやxcodeなどが使用できます。また、ビルドバリアントとして、Debug, Release, デバッグ情報付きのReleaseなどがあります。
ビルド成果物は、こうしたビルドツール、バリアント、プロジェクト、ターゲット環境のの組み合わせごとに、`<tool>-<variant>/<project>-<target>`という形式の名前の別々のディレクトリに隔離されて生成されます。以下はそれらのディレクトリの例です。

```
$ cd swift-source/build
$ ls -1
Ninja-RelWithDebInfoAssert
Ninja-ReleaseAssert
Ninja-DebugAssert
Xcode-RelWithDebInfoAssert
Xcode-ReleaseAssert
Xcode-DebugAssert
$ cd Ninja-RelWithDebInfoAssert
$ ls -1
cmark-macosx-x86_64
llvm-macosx-x86_64
swift-macosx-x86_64
```

これらのディレクトリを、この記事では「プロジェクトごとのビルドディレクトリ」と呼びます。例えば、`swift-source/build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64`は、「swiftのビルドディレクトリ」と呼びます。

## マシン環境

macOSでは最新のXcodeとcmakeとninjaが必要です。xcodeはapp storeから、cmakeとninjaはhomebrewで入れましょう。なお、リポジトリの説明ではXcodeの最新ベータを使うように書かれているときもありますが、少しぐらい前のバージョンでも大抵は問題ないので無理にベータ版を入れたりしなくても大丈夫です。

## ビルド構成の選択

ビルドツールについて説明します。ninjaでのビルドは早くて安定していてコマンドライン作業との相性も良いです。xcodeでのビルドは遅くて不安定ですが、プロジェクトごとのビルドディレクトリにxcodeprojが生成されるのでソースコードの編集作業や、デバッグ作業には向いています。

xcodeでソース編集を行い、ninjaでビルドした成果物を、xcodeでデバッグする方法もあります。それについては後述します。

ビルドバリアントについて説明します。Debugモードはデバッグ用のモードで、ビルド時間が長く、生成物の動作速度が遅いです。Releaseモードはリリース用のモードで、生成物の動作速度が早いですが、デバッグ情報が付いていないのでXcodeのデバッガをアタッチできません。デバッグ情報付きのReleaseモードはデバッガがアタッチできます。

## ビルドスクリプトのヘルプ

ビルドスクリプトは`--help`でヘルプが出ます。ビルド構成の指定方法がわからなくなったら参照してください。また、ショートスタイルのオプションなどについても書かれているので、慣れてきたら参照してください。

```
$ cd swift-source/swift
$ utils/build-script --help
```

## ninjaでのビルド

ninjaでのビルドはswiftリポジトリにある`utils/build-script`を使用して行います。引数でビルドバリアントを与えます。Debugビルドは`--debug`、Releaseビルドは`--release`、デバッグ情報付きのReleaseビルドは`--release-debuginfo`です。

```
$ cd swift-source/swift
$ utils/build-script --release-debuginfo
```

## xcodeでのビルド

xcodeでのビルドは、`utils/build-script`に、`--xcode`オプションを指定します。その他のことはninjaのときと同じです。

```
$ cd swift-source/swift
$ utils/build-script --xcode --release-debuginfo
```

実行が終わるとプロジェクトごとのビルドディレクトリにxcodeprojが生成されます。これをXcodeで開いてソースコード編集、ビルド、デバッグ等の作業ができます。ただし、デバッグ情報のないReleaseビルドではデバッガをアタッチする事はできません。

生成されたxcodeprojを初めて開くときに、スキーマの自動生成を行うか尋ねるダイアログが出てきますが、拒否しましょう。許可してしまうと異常な量のスキーマが生成されて不便です。

だいたいのコンパイラ開発作業においては、`swift`ターゲットのスキーマを作るのがおすすめです。このターゲットは普段使っている`swift`コマンドの実行ファイルターゲットで、xcodeからビルド、実行できます。

スキーマの作成は、Xcodeのスキーマ選択メニューをクリック→Manage Schemesをクリック→左下の「+」ボタンをクリック→Targetを選択→OKボタンをクリック、という手順でできます。

スキーマが作成できたら、ビルドや実行をする前に、スキーマのBuild Configurationをビルドバリアントと同じものに設定する必要があります。
これをきちんと設定しないとビルドが失敗します。

Build Configurationの設定は、該当スキーマを選択している状態で、Edit Schemeをクリック→RunタブのInfoタブを開く→Build Configurationを選択、という手順でできます。

生成されたxcodeのプロジェクトツリーは、実際のディレクトリ構造とは異なっていて、ターゲットの構造に基づいたツリーになっています。特定のファイルを探すときには、ツリーペインの下部にある検索ボックスを使って、ファイル名やターゲット名で絞り込むのが便利です。

デバッグがちゃんとできているかを確認するときは、`swift/tools/driver/driver.cpp`の中の`main`関数の先頭にブレークポイントを貼るのが確実でおすすめです。デバッグ情報の無いReleaseモードではできないので注意しましょう。

ビルドする際の中間生成物や最終生成物はプロジェクトのビルドディレクトリに出力されますが、Xcodeから編集できるようになっているソースファイルはリポジトリの中のソースファイルを参照しています。

swiftコマンドのデバッグにあたっては、通常のコマンド実行と同じように引数を与えます。典型的な使用方法、コンパイルと実行をさせたいswiftソースファイルを引数に与えての実行でしょう。この実行時引数の設定は、スキーマの編集ウィンドウ→Runタブ→Argumentsタブ→Arguments Passed On Launchのセクションを編集して行います。複数のオプションを設定する際は、このリストコンポーネントにおいて個別の要素にしましょう。1つのエントリに複数のオプションを書いても、スペース文字で結合された1つのオプションとして扱われてしまいます。

## ninjaの生成物をXcodeでデバッグする

Xcodeは任意の実行ファイルに対してデバッガをアタッチしてデバッグする事ができるので、これを使ってninjaで生成された実行ファイルをXcodeでデバッグすることができます。

この方法の場合は、ビルドはninjaで行い、Xcodeでは行わないため、Xcodeでビルドする必要はありません。上述したビルドスクリプトの実行方法では、xcodeprojの生成の際にも実はxcodebuildによるビルドが実行されているのですが、これは今回不要です。スクリプトの`--skip-build`オプションでそのビルドの実行をしないように指定できます。

```
$ cd swift-source/swift
$ utils/build-script --xcode --release-debuginfo --skip-build
```

ninjaで生成した実行ファイルをデバッグするために、次の手順でスキーマを作成します。
スキーマ選択メニューをクリック→左下の「+」をクリック→TargetはNoneを選択する→Nameはなんでもいいが、ここでは「ninja-swift」にする。→OKをクリック。
スキーマが生成されると、即座にスキーマ編集ウィンドウが表示される→RunタブのInfoタブを開く→Executableの選択ボックスをクリック→「Other...」をクリック→ファイル選択ダイアログが開くので、ninjaのビルドディレクトリの中のswiftコマンドを選択する。例えば、`swift-source/build/Ninja-RelWithDebugInfoAssert/swift-macosx-x86_64/bin/swift` に配置されている。ここではBuild Configurationは変更不要。
これでninjaでビルドしたswiftコマンドがデバッガとともに実行できます。ただし、デバッグ情報の無いReleaseモードではできないので注意しましょう。また、これはただの実行ターゲットで、ビルドは実行されないので、ソースコードを書き換えたときには、一度ターミナルを使ってninjaでビルドしてから実行する必要があるので注意してください。