Danger-Swift のプラグインを SwiftPM で導入する手引き

# 前書き

以前 [Danger-Swift 移行の手引き](https://qiita.com/lovee/items/1a0d8884963a9e3330b0)記事を書きましたが、その記事には残念ながら一つ大きな問題点があります：[プラグインの利用](https://qiita.com/lovee/items/1a0d8884963a9e3330b0#プラグインの利用)は [Marathon](https://github.com/JohnSundell/Marathon) というすでに Deprecated 済みのツールに依存しています。もちろん Danger-Swift 公式はちゃんとした SwiftPM を利用した方法も紹介していますが、微妙に面倒だったので自分はこれまで避けてきました。

でもやはり Deprecated なものにいつまでも依存してはリスクが高いです；また場合によってプライベートなプラグインを利用したり、最新リリースではなく特定のバージョンやブランチを利用したいこともありますが、そんな時は SwiftPM の利用が必須です。というわけで、この記事でちゃんと SwiftPM を利用したプラグインの導入の仕方を紹介したいと思います。

ただし、残念ながら**現在 M1 Mac の場合、[ローカルで実行できないことがマシンによってあるようです](https://github.com/danger/swift/issues/464)**。CI 環境が Intel Mac であれば問題ないですが、M1 なら注意が必要です。

# ローカル環境の整備

## Danger 用のフォルダーを作成

まず手始めに、Danger 用のフォルダーを作っておきます。もちろんこれを作らなくても、動作させる分には問題ないですが、その場合ルートディレクトリーに `Package.swift` ファイルを置くことになります。このやり方は少なくとも 2 つのデメリットがあります：

1. `xed .` コマンドの動作が変わってしまいます。
    ターミナルから Xcode を起動させるのに [`xed`](https://www.unix.com/man-page/osx/1/xed/) というコマンドがあります。このコマンドに今のディレクトリー `.` を渡してあげれば、`xed` は自動的に適切なファイルを開いてくれますが、残念ながら優先順位としては `*.xcworkspace` よりも、`Package.swift` ファイルの方が優先されます、つまりルートに `Package.swift` を置いておくと、`xed` はこのディレクトリーを Swift Package のプロジェクトとして認識してしまいます。

1. 人間の目にも優しくないです。
    `xed` を使わなくても、結果としてルートディレクトリーに `Package.swift` を置いていると、人間がぱっと見これが正規なプロジェクトに関係する設定と認識してしまいますので、フォルダー構成としても非常に紛らわしいです。おまけに Danger-Swift に関係するファイルはこれだけでなく、他にも `Dangerfile.swift` や `Sources` などがありますので、ルートディレクトリーが非常に煩雑になってしまいやすいです。

というわけで、Danger 用のファイルは全部 `Danger` フォルダーにぶち込んでおきましょう。

## Packages.swift を作成

Marathon を利用しないので、代わりに依存を Packages.swift で指定する必要があります。ここで必要なのは Danger-Swift 自身と、利用するプラグインのパッケージです。パッケージ構成としては、プロダクトとして `DangerDepsProduct` というライブラリーを作る必要があります、そしてこのプロダクトが含まれるターゲットは `DangerDependencies` で、このターゲットには Danger-Swift と各プラグインが含まれています。公式でも[大まかなサンプル](https://github.com/danger/swift#swift-package-manager-more-performant)が用意されていますが、ここでより詳細なサンプルも上げておきます：

```swift:Package.swift
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription

let package = Package(
    name: "Dangerfile",
    platforms: [.macOS("10.12")],
    products: [
        .library(name: "DangerDepsProduct",
                 type: .dynamic,
                 targets: ["DangerDependencies"]),
    ],
    dependencies: [
        // Danger
        .package(name: "danger-swift", url: "https://github.com/danger/swift.git", from: "3.0.0"),
        // Danger Plugins
        .package(name: "DangerXCodeSummary", url: "https://github.com/f-meloni/danger-swift-xcodesummary.git", from: "1.2.0"),
        .package(name: "DangerSwiftCoverage", url: "https://github.com/f-meloni/danger-swift-coverage.git", from: "1.2.0"),
        .package(name: "DangerSwiftEda", url: "https://github.com/yumemi-inc/danger-swift-eda.git", from: "0.1.0"),
    ],
    targets: [
        .target(
            name: "DangerDependencies",
            dependencies: [
                .product(name: "Danger", package: "danger-swift"),
                "DangerXCodeSummary",
                "DangerSwiftCoverage",
                "DangerSwiftEda",
            ]
        ),
    ]
)
```

ここで注意が必要なのは Danger-Swift 本体の取り入れ方でしょうか、これはパッケージアドレスとパッケージ名とプロダクト名がそれぞれ全部違うため、`dependencies` の宣言では文字列そのままが使えず、必ず `.product(name:package:)` で導入する必要があります[^package-declaration]。他のプラグインも、もしパッケージ名とプロダクト名が違ったら、このように導入する必要があります。

[^package-declaration]: 正確にはこのファイルの一番先頭に書かれてある `// swift-tools-version:` のバージョンによって違いますが、少なくとも 5.3〜5.5 のツールバージョンはこのような書き方が必要です。Danger-Swift の実行は作業プロジェクト自体のバージョンには全く依存しないのである程度の自由は効きますが、とは言え古すぎるバージョンはやはり避けるべきでしょう。

そのほかに、プロダクトの宣言で `type` は必ず `.dynamic` にしておく必要があります。また、`platforms` 指定も、空欄にしておくとデフォルトでは `.macOS("10.10")` が使われますが、利用するライブラリーによってそれより高いバージョンが必要になることがあるので、その場合はそれに合わせて `platforms` の指定も必要です。

## Dangerfile.swift を修正

Marathon を使わなくなったので、`Dangerfile.swift` に書かれてある各種アドレスも必要なくなるから、それらを削除しておきましょう。

# ローカルでの動作確認

Danger-Swift を動かすのに、Danger-JS も必要です。[以前の手引き記事](https://qiita.com/lovee/items/1a0d8884963a9e3330b0)ですでに `brew` などでインストール済みであれば問題ないですが、もしまだであれば `npm` で Danger-JS をインストールしましょう。また `npm` 自体のインストールは `brew install node` でできます。`npm` がインストールされたら、`npm install danger` で Danger-JS をインストールできます。

Danger-JS がインストールされたら、今度はターミナルで Danger のディレクトリーに移動して、`swift run danger-swift edit` をすれば、Danger-Swift の実行に必要なものを Swift が全部落としてきてビルドして、`Dangerfile.swift` の編集画面を開いてくれます。ただし今ターミナルのディレクトリーはプロジェクトではなく `Danger` というフォルダにあるので、`pr` や `ci` コマンドには `--cwd ../`[^cwd] [^package-path] パラメーターを付与する必要があります。なので例えばローカルで PR チェックを実行したい場合は、`swift run danger-swift pr <url/to/pull-request> --cwd ../` を実行すれば OK です。

[^cwd]: `cwd` は `current working directory`、そして `../` は親パスを指します。この仕様は[公式ドキュメント](https://github.com/danger/swift#current-working-directory)からも確認できます。

[^package-path]: Swift パッケージの実行には他にも `Danger` フォルダーに移動せず、プロジェクトにいたまま例えば `swift run --package-path Danger danger-swift <command>` を実行する方法も本来ならありますが、残念ながら `danger-swift` の実行にはそれでは `libDanger` のライブラリーのパス解決ができず、失敗してしまいますので、大人しく `cd Danger` して `--cwd ../` パラメーターを使いましょう。

# CI での導入

こちらもローカルの動作確認と同じように `npm` で Danger-JS をインストールしておく必要があります、そして Danger-Swift を実行するときもローカルの時と同じように、先に `Danger` に移動してから、`swift run danger ci --cwd ../` で実行する必要があります。

また、せっかく SwiftPM で導入したので、Marathon の時と違ってキャッシュが取りやすいから、それも CI にキャッシュさせておきましょう。キャッシュしておきたいパスは `Danger/.build` です、ここには Danger-Swift の実行に必要なものが大体揃ってあります。これの生成は一度 `swift run danger-swift <command>` を実行させるとできますが、もしキャッシュするタイミングが Danger の実行前であれば、先に `swift build` しておくと大抵のものが先に生成されますので、実行時の処理がだいぶ軽くなります。ただし注意しないといけないのは `Danger/.build` ディレクトリーはパッケージが実行やビルドされるたびに内容に差分ができてしまいますので、キャッシュのフラグとして `Danger/Package.resolved` ファイルを指定しましょう、そうすればこのファイルが変更された時だけ `Danger/.build` フォルダーがキャッシュされます。

# SwiftLint についての TIPS

ここまでしたら、Danger-Swift の導入がプラグイン含めて全て SwiftPM でできるようになりましたが、実際のシナリオとして SwiftLint の利用も Danger-Swift 内でよくあります。ところでせっかくなので SwiftLint も SwiftPM で導入したい気持ちありますよね。というわけでここで Danger-Swift 向けの SwiftLint の導入についても少し TIPS を書かせてください。

まず、Danger-Swift 用の SwiftLint なので、依存も Danger-Swift と同じ `Package.swift` 内で宣言しておきましょう。そして `Package.swift` を開いた時にワーニングが出ないように、ダミーのターゲットも作っておきましょう：

```swift:Package.swift
let package = Package(
    // ...
    dependencies: [
        // ...
        // SwiftLint
        .package(name: "SwiftLint", url: "https://github.com/realm/SwiftLint", from: "0.45.0"), // Only for Danger-Swift runtime
    ],
    targets: [
        // ...
        .target(
            name: "SwiftLintDependencies", // Only for Danger-Swift runtime
            dependencies: [
                .product(name: "swiftlint", package: "SwiftLint"),
            ]
        ),
    ]
)
```

そうすれば SwiftLint の導入も SwiftPM でできます；また利用する際は、`Dangerfile.swift` の `SwiftLint` コマンドで `swiftlintPath` を指定しましょう：

```swift:Dangerfile.swift
// ...
SwiftLint.lint(/* ... */, swiftlintPath: .swiftPackage("$(pwd)/Danger"))
```

ちなみにここの `$(pwd)` は本来なら現在いるディレクトリー、つまり `Danger` になるはずですが、おそらく `--cwd ../` の影響で、Danger-Swift の実行が親ディレクトリーに移動されたため、`$(pwd)` が実際取れてるのはプロジェクトのパスになります。そのため、Danger のディレクトリーにある SwiftLint を指定させるには、`$(pwd)/Danger` にする必要があります。

# 後書き

ところで気づいた人がいるのでしょうか。そう、この記事に登場した `Package.swift` のサンプルに、実は弊社が公開したプラグインもあります：[DangerSwiftEda](https://github.com/yumemi-inc/danger-swift-eda.git) です。このプラグインは一体どんなものか、そしてそもそも Danger-Swift のプラグインはどう作ればいいのかーーDanger-Swift のプラグイン作成編を乞うご期待！