danger(kotlin)でプラグインを作ってみて学んだこと

### 背景
まず発端になる背景としては、私は主に Android と KMP を使って開発するエンジニアとして、主にKotlinを触っていまして、自分は普段から Pull Request に簡単にレポートのコメントを出してくれるツールである [danger](https://github.com/danger/danger) を使っていましたが、特にテストカバレッジ報告を出すためにこのツールを使うのはエンジニアあるあるだと思います。

そして、danger というツールは基本 Ruby ベースであったため、コミュニティーも大きく、当たり前っちゃ当たり前ですが、danger の再利用をするための単位であるプラグインも Ruby で書かれたことが多かったです。

そして、danger を使うためには Ruby と Bundler で danger と dangerプラグインのバージョンを管理をする必要があるところから不便を感じました。
理由としては、いろいろありました。

1. 元々 Ruby と仲良くないので、Bundler の使い方とか danger プラグインのコードを理解するに時間がかかった。特に、プラグインで不具合が発生した場合に調査が難しかった
1. 既存の有名な danger プラグインよりももっと優れた機能を入れたプラグインを使いたかった
1. 自分はなんとなく理解できたとしても、仕事の新米の Android エンジニアがまた同じことを繰り返し、学習コストが発生すると判断した
1. もし Ruby を手動でインストールする必要がある場合、CI/CD のコーストが1~5分増加したため、コストがもったいないと思った

この当時の私は、プロジェクトで[Jacoco](https://github.com/jacoco/jacoco)を使っていましたが、JacocoはJavaベースであるため、少し実際のカバレッジと乖離が発生することがわかりました。
KotlinのテストをKotlinのソースコードに合わせて正確的にテストしてくれるかつ、Kotlin側で直接サポートしてくれる[kover](https://github.com/Kotlin/kotlinx-kover)というカバレッジ計測ツールを使いたいと思いました。

ただ、その当時はdangerでkoverをちゃんとサポートするプラグインがなかっと思い、自分で作りたいと思いました。（後から知ったんですが、実は [danger-shroud](https://github.com/livefront/danger-shroud) はちゃんと最近も管理されているし、kover もサポートしていることが README に書かれていました。
後もう一個、また錯覚したことがありまして、Android のアプリをビルドできる環境なら絶対 Kotlin のコンパイラーもインストールされているはずだと判断しました。

danger は実は、danger/danger-js、danger/swift、danger/kotlin などいろんな言語でも実行できるように対応されています。

こういうことから私は、
**おっ、Kotlin のプラグインを作れば、Ruby バージョン管理しなくてもいいし、danger のためにいろいろ Ruby 周りをインストールする面倒くさいことはしなくてもいいじゃないか**
と思いまして、すぐ danger(kotlin) のプラグイン作成に着手します。

話が長くなりましたので、本論に入りたいと思います。

# danger(kotlin) とは？

**kotlin を使って**、danger/danger(ruby)と同じくGithub Pull Requestやローカルなどにレポートを出せるツールです。
これのバージョンは **安定(Stable)** になっており、Github Actions や Brew のインストールからコマンドの実行ができます。

## danger(kotlin)のコマンド

danger(kotlin) を brew などでインストールすると下記のコマンドを実行することで、レポートを出せます。

`danger-kotlin ci`: CI/CD で使う時にはこれを使います。（どこかで使える CI/CD プラットフォームのリストを過去に見た覚えがありますが、改めて調べたら、見つかれませんでした。とりあえず、Circle CI では使えます。）
`danger-kotlin pr https://github.com/{user}/{repository}/pull/{pr number}`
`danger-kotlin local`: ローカル環境でテストするならこっちを使えば良いです。プロジェクトに成果物としてjsonファイルが作成されます。

ref: https://github.com/danger/kotlin?tab=readme-ov-file#commands

## Dangerfile.df.kts

danger(kotlin) は `Dangerfile.df.kts` というファイルを作成し、処理を書くことで、実行可能になります。
danger/danger(ruby) とは何が違うかは、ここで具体的な説明は割愛とさせていただきたいですが、ざっくり説明すると、そこまでdanger/danger(ruby)の `Dangerfile` と差異はありません。

```kotlin
import systems.danger.kotlin.*

danger(args) {

    val allSourceFiles = git.modifiedFiles + git.createdFiles
    val changelogChanged = allSourceFiles.contains("CHANGELOG.md")
    val sourceChanges = allSourceFiles.firstOrNull { it.contains("src") }

    onGitHub {
        val isTrivial = pullRequest.title.contains("#trivial")

        // Changelog
        if (!isTrivial && !changelogChanged && sourceChanges != null) {
            warn(WordUtils.capitalize("any changes to library code should be reflected in the Changelog.\n\nPlease consider adding a note there and adhere to the [Changelog Guidelines](https://github.com/Moya/contributors/blob/master/Changelog%20Guidelines.md)."))
        }

        // Big PR Check
        if ((pullRequest.additions ?: 0) - (pullRequest.deletions ?: 0) > 300) {
            warn("Big PR, try to keep changes smaller if you can")
        }

        // Work in progress check
        if (pullRequest.title.contains("WIP", false)) {
            warn("PR is classed as Work in Progress")
        }
    }
}
```
ref: https://github.com/danger/kotlin?tab=readme-ov-file#what-it-looks-like-today

見ればわかりますが、`git.hoge` を呼び出して、Gitの 差分など、情報を持ってきて使うことができます。
そしてこれは、Github など プラットフォームになるところからは一つの Push 単位で発生した差分ではなく、Pull Request のマージ元とマージ先との差分になります。（便利ですよね）

## danger(kotlin)ってどう動くんだ？

ローカルでは brew でインストールしたのですが、brew 様が勝手に必要なものをいろいろインストールしてくれ他ので、気づけてなかったけど、実は danger(kotlin)って、中ではなんと、`danger-js`により使われます。
これが何を意味するかというと、PC に Node.js もインストールされていないと上記のコマンドを実行しても動かないってことです。
しかも、当たり前かもですが、danger-kover のコマンドを実行するとjavac ではなく、kotlinc でコンパイルされるため、Kotlin Compiler もインストールされないとダメでした。
これらは brew を使ってインストールすればそこまで問題にはなりませんが、linux 環境で danger(kotlin) をインストールして使おうとすると結構困難します。
私はこれをプラグインを作った後、これをローカルで確認して、最終的に CI/CD に適用する段階でわかったので、やっと Kotlin のエンジニアが幸せに kover のプラグインが使えると希望を持った私には結構、ハメられたと感じました。
しかし、Github Actions はこれらを意識しなくても普通に使えるので、まだまだ私が予測したメリットが効くかなと思います。

ref: https://github.com/danger/kotlin/blob/master/docs/tutorials/architecture.html.md

# danger(kotlin)のプラグインを作る

上記の話は一旦置いといて、また danger(kotlin) のプラグインを作成した経験から説明をしようと思います。
そもそもプラグインってなんなのか？
Android エンジニアだとすぐ Gradle のあれを思い出すかもしれませんが、danger においてプラグインは Dangerfile.df.kts で使うために書かれたライブラリーやパッケージ、Utility のような単位になります。
（余談ですが、Minecraft というゲームでもサーバーを立ち上げるために使う便利なロジックの塊をプラグインと呼んでましたね）
danger の プラグインは 下記のようなライブラリーを使うことで作れます。
```kotlin
implementation("systems.danger:danger-kotlin-sdk:{latest-version}")
```

## プラグインの実装
下記のように `DangerPlugin()`を extend することでプラグインが使えます。
そして、maven などで配布するのは他のライブラリと同じです。
```kotlin
package com.sample.plugin

import systems.danger.kotlin.sdk.DangerPlugin

object SamplePlugin : DangerPlugin() {
    override val id: String = javaClass.name
    
    ...
}
```
Class で定義するかどうかは、必要に応じてになりますが、基本的には object にするか、Global scope のメソッドを再度定義した方が使われる側としては嬉しいでしょう。
私は、Global Scopeでシンプルな関数とDSLを組んだものを用意したりしました。
DangerPlugin の中で どうやって Github Pull Request に出すような内容を書くかっていうと、`context`というものを使っていろいろ書き込むことができます。
例えば、`context.markdown("Hello Danger !")`、`context.warn("Oops!")`にように書けます。
こうやって何か良い情報を綺麗に書き込むまでいろいろする処理を書くのがプラグインであなたがやるべきことになりますね。
こういうことまでやりたい Kotlin オタクなら、この後のことは説明しなくても、皆うまくやっていくだろうと思います。

### プラグインを使おう
プラグインは、通常のものとは違ってGradleを使ってなく、上記で説明した、`Dangerfile.df.kts`の中で使うことになります。
`Dangerfile.df.kts`でプラグインを持ってくるためには下記のように書く必要があります。
```kotlin
@file:Repository("https://repo.maven.apache.org")
@file:DependsOn("com.sample.plugin:plugin:0.0.1")

import com.sample.plugin.SamplePlugin
import systems.danger.kotlin.*

register plugin SamplePlugin

danger(args) {
    SamplePlugin.hoge()
}
```

```kotlin
@file:Repository("https://repo.maven.apache.org")
@file:DependsOn("com.sample.plugin:plugin:0.0.1")
```
ref: https://github.com/danger/kotlin?tab=readme-ov-file#using-external-maven-dependencies-into-your-dangerfile
これは、どこから持ってくるかと、ライブラリーの module 名ですね。
ここで一個注意することがあります。
Credentialsのようなものは使えません。
つまりプライベートなところに置かれたものにアクセスするためには、MavenLocal(~/.m2)にjar、pomを置くといったトリッキーな方法じゃないといけないです。（mavenで配布した意味が、、ってなりますが）
もし社内だけで使うとしたらS3とかが心が楽になるかもしれません。
```kotlin
register plugin SamplePlugin
```
あとはここでわかるように、使いたいプラグインを登録する必要があります。
```kotlin
danger(args) { }
```
最後に、登録も完了したら、ここでさっき作ったプラグインの公開されたメソッドを呼び出せばOKです。

こういったことで、`danger-kotlin`のコマンドを打ったら、やっと danger(kotlin) で自分が作ったプラグインが使えるようになります。

# まとめ
- danger は ruby 以外の言語でも使える
- danger(kotlin) は内部的に danger-js で動くため、 Node.js が必要
- danger(kotlin) のプラグインは使うときに Credentials が使えない
- DangerPlugin は `context` を使って レポートを書き込む
- こういうことをやって、ruby とバイバイすることができる