Kotlinで書いたGradle Pluginからrepositoriesを変更する

こんな感じに書くとプロジェクトのrepositoriesを変更するが出来ます。
行儀が良さは一旦置いておきましょう。

class SamplePlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.repositories(closureOf<RepositoryHandler> {
            add(maven { repository ->
                repository.url = URI("https://maven.google.com")
            })
        })
    }
}

背景

このネタを考えるきっかけはWantedlyさんのテックブログです。カッコいいですね。

us.wantedly.com

記事を読んでもらうとわかるのですが、上記のエントリではkotlinの拡張関数を使ってwantedly()を実現されています。つまりbuild.gradleをkotlinで書く制約があります。
残念なことに私が関わっているプロジェクの多くはgroovyで設定ファイルを書いています。そこでgroovy版のbuild.gradleではこういったおしゃれコードが書けないのかと調べてみるとそれっぽいレポジトリを見つけました。

github.com

このレポジトリのコードを読んでみるとプラグインのコードからmetaclass というものを使っています。これはgroovyがメタプログラミング用に用意している仕組みです。動的にRepositoryHandlerにメソッドを生やすことで任意のレポジトリのエイリアスを作っているようでした。つまりbuild.gradleをkotlinで書いてしまうとみえないと思います。惜しいですが残念ですね。

RepositoryHandlerというのGradleの内部実装のクラスです。

github.com

Gradle実装のこの辺り読むとわかるんですが、RepositoryHandler interfaceに定義されてないものは基本的にrepositoriesDSLの中には書けないんですよね。そういうことならプラグインの中で追加してしまえばbuild.gradleがgroovyだろうがkotlinだろうが解決できるなと思って試してみたら出来たという話でした。

大切な補足

実は冒頭のコードあれだけでは動かなくてclosureOfという拡張関数を生やす必要があります。closureOfの出典元はgradle/gradleです。

gradle/GroovyInteroperability.kt at e9461fe3e2641398c6bdb82461ffbdadb21d0b1f · gradle/gradle · GitHub

fun <T> Any.closureOf(action: T.() -> Unit): Closure<Any?> =
    KotlinClosure1(action, this, this)

class KotlinClosure1<in T : Any?, V : Any>(
    val function: T.() -> V?,
    owner: Any? = null,
    thisObject: Any? = null
) : Closure<V?>(owner, thisObject) {
    @Suppress("unused") // to be called dynamically by Groovy
    fun doCall(it: T): V? = it.function()
}