jCenter (bintray) にアップロードするbuild.gradle.kts

**[Into the Sunset on May 1st: Bintray, JCenter, GoCenter, and ChartCenter](https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/)**
jCenterは閉鎖する（リードオンリーで参照のみ可能）とのことですので、Maven Centralに移行しましょう。

Maven Centralへ移行する方法はこちらを参照
[jCenterで公開していたKotlinライブラリをMaven Centralで公開する](https://qiita.com/ryo_mm2d/items/40df6e292c691366038e)

----

Android StudioのKTSサポートも強化され、ビルドスクリプトを徐々にGroovy DSLからKotlin DSLに乗り換える人も増えてきていると思います。この記事は [jCenter (bintray) にアップロードするbuild.gradle](https://qiita.com/ryo_mm2d/items/5ffc52c4554ebeef45de) のKTS版です。

projectのbuild.gradleに記述していた定義は、buildSrcでobjectとして定義してます。
[Gradle Kotlin DSL (build.gradle.kts) で構造体っぽい定数定義をしたい](https://qiita.com/ryo_mm2d/items/fde898338967f5a64ce3) を参照

```kotlin:ProjectProperties.kt
package build

object ProjectProperties {
    const val groupId: String = "com.example"

    private const val versionMajor: Int = 1
    private const val versionMinor: Int = 0
    private const val versionPatch: Int = 0
    const val versionName: String = "$versionMajor.$versionMinor.$versionPatch"
    const val versionCode: Int = versionMajor * 10000 + versionMinor * 100 + versionPatch

    object Url {
        const val site: String = "https://github.com/example/example"
        const val github: String = "https://github.com/example/example"
        const val scm: String = "scm:git:https://github.com/example/example.git"
    }
}
```

また、ホームディレクトリの.gradle/gradle.propertiesにて以下のようにbintrayのuse名とアップロード用のkeyを定義しておきます。ここは一緒。

```gradle
bintray_user=xxx
bintray_key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

各モジュールではライブラリ名や、グループID、バージョンを設定しています。
`archivesBaseName` がKotlin DSLでは `base.archivesBaseName` にしないといけないのがなかなか気づけなかったです。

```kotlin
base.archivesBaseName = "library name"
group = ProjectProperties.groupId
version = ProjectProperties.versionName
```


## publishingタスク

Groovy DSLでは以下のように書いていました。

```gradle
apply plugin: "maven-publish"

publishing {
    publications {
        bintray(MavenPublication) {
            artifact("$buildDir/libs/${libraryName}-${version}.jar")
            groupId = pj.groupId
            artifactId = libraryName
            version = pj.versions.name

            artifact sourcesJar

            pom.withXml {
                def node = asNode()

                def licenses = node.appendNode("licenses")
                appendLicense(licenses, "The MIT License", "https://opensource.org/licenses/MIT", "repo")

                appendScm(node, pj.scmConnection, pj.githubUrl)

                def dependencies = node.appendNode("dependencies")
                configurations.api.dependencies.each {
                    appendDependency(dependencies, it.group, it.name, it.version, "compile")
                }
                configurations.implementation.dependencies.each {
                    appendDependency(dependencies, it.group, it.name, it.version, "runtime")
                }
            }
        }
    }
}

static def appendLicense(parentNode, name, url, distribution) {
    def node = parentNode.appendNode("license")
    node.appendNode("name", name)
    node.appendNode("url", url)
    node.appendNode("distribution", distribution)
}

static def appendScm(parentNode, connection, url) {
    def node = parentNode.appendNode("scm")
    node.appendNode("connection", connection)
    node.appendNode("url", url)
}

static def appendDependency(parentNode, groupId, artifactId, version, scope) {
    def node = parentNode.appendNode("dependency")
    node.appendNode("groupId", groupId)
    node.appendNode("artifactId", artifactId)
    node.appendNode("version", version)
    node.appendNode("scope", scope)
}
```

これをKTSにすると以下のようになります。

```kotlin
plugins {
    `maven-publish`
}

publishing {
    publications {
        create<MavenPublication>("bintray") {
            artifact("$buildDir/outputs/aar/${base.archivesBaseName}-release.aar")
            groupId = ProjectProperties.groupId
            artifactId = base.archivesBaseName
            version = ProjectProperties.versionName
            artifact(tasks["sourcesJar"])
            pom.withXml {
                val node = asNode()
                val licenses = node.appendNode("licenses")
                appendLicense(licenses, "The MIT License", "https://opensource.org/licenses/MIT", "repo")
                appendScm(node, ProjectProperties.Url.scm, ProjectProperties.Url.github)
                val dependencies = node.appendNode("dependencies")
                configurations.api.get().dependencies.forEach {
                    appendDependency(
                        dependencies,
                        groupId = it.group ?: "",
                        artifactId = it.name,
                        version = it.version ?: "",
                        scope = "compile"
                    )
                }
                configurations.implementation.get().dependencies.forEach {
                    appendDependency(
                        dependencies,
                        groupId = it.group ?: "",
                        artifactId = it.name,
                        version = it.version ?: "",
                        scope = "runtime"
                    )
                }
            }
        }
    }
}

private fun appendLicense(parentNode: Node, name: String, url: String, distribution: String) {
    parentNode.appendNode("license").apply {
        appendNode("name", name)
        appendNode("url", url)
        appendNode("distribution", distribution)
    }
}

private fun appendScm(parentNode: Node, connection: String, url: String) {
    parentNode.appendNode("scm").apply {
        appendNode("connection", connection)
        appendNode("url", url)
    }
}

private fun appendDependency(
    parentNode: Node,
    groupId: String,
    artifactId: String,
    version: String,
    scope: String
) {
    parentNode.appendNode("dependency").apply {
        appendNode("groupId", groupId)
        appendNode("artifactId", artifactId)
        appendNode("version", version)
        appendNode("scope", scope)
    }
}
```

## bintrayタスク

Groovy DSLでは以下のように書いていました。

```gradle
bintray {
    user = project.hasProperty("bintray_user") ? bintray_user : ""
    key = project.hasProperty("bintray_key") ? bintray_key : ""
    publications = ["bintray"]

    pkg {
        repo = "maven"
        name = pj.groupId + "." + libraryName
        licenses = ["MIT"]
        websiteUrl = pj.siteUrl
        vcsUrl = pj.githubUrl + ".git"
        issueTrackerUrl = pj.githubUrl + "/issues"
        publicDownloadNumbers = true
        version {
            name = project.version
        }
    }
}

bintrayUpload.dependsOn assemble
```

これをKTSにすると以下のようになります。
※dryRun = trueなので実際のアップロードは行われません。アップロードする場合はfalseに書き換えるか、削除してください。

```kotlin
bintray {
    user = project.findProperty("bintray_user") as? String ?: ""
    key = project.findProperty("bintray_key") as? String ?: ""
    setPublications("bintray")

    dryRun = true

    pkg(closureOf<BintrayExtension.PackageConfig> {
        repo = "maven"
        name = ProjectProperties.groupId + "." + base.archivesBaseName
        setLicenses("MIT")
        websiteUrl = ProjectProperties.Url.site
        vcsUrl = ProjectProperties.Url.github + ".git"
        issueTrackerUrl = ProjectProperties.Url.github + "/issues"
        publicDownloadNumbers = true
        version = VersionConfig().apply {
            name = ProjectProperties.versionName
        }
    })
}

tasks.named("bintrayUpload") {
    dependsOn("assemble")
}
```

## モジュール共通の処理をまとめる

マルチモジュールなプロジェクトだと、これらの処理をまとめたくなります。
ライブラリ名は `base.archivesBaseName` を参照しているので、完全にコピペになってしまいますからね。

Groovy DSLでは共通の処理を`common.gradle`とかにまとめて書いて、各gradleファイルからは以下のように書くだけでいけました。

```gradle
apply from: "${rootDir}/common.gradle"
```

Kotlin DSLではこの方法が使えません（commonの方がGroovy DSLなら問題無くできますが）

ではどうやるかというと、これらの処理をbuildSrcで定義するようにします。

buildSrc側にビルドスクリプトを各場合はいくつかの細工が必要です。
まず、プラグインに関する処理の場合、buildscriptのdependenciesに書いているclasspathをimportする必要があります。
拡張関数などを使う場合はkotlin-stdlibなどもimportしましょう。

```kotlin:buildSrc/build.gradle.kts
plugins {
    kotlin("jvm") version "1.3.72"
    `kotlin-dsl`
}

repositories {
    google()
    jcenter()
}

dependencies {
    implementation(kotlin("stdlib"))
    implementation("com.android.tools.build:gradle:4.0.1")
    implementation("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5")
}
```

KTSのスクリプトはProjectに対する拡張関数として実装されているので、同様に拡張関数をbuildSrcとして実装することになります。
ただし、KTSで使用できるアクセッサの一部はライブラリではなく自動生成されているものがあるようで、それらは自前で実装する必要があります。

デフォルトで使用できるアクセッサは `org.gradle.kotlin.dsl` のパッケージになっているので、このパッケージをワイルドカードでimportしておいてコーディングすると良いでしょう。

```kotlin
import org.gradle.kotlin.dsl.*
```

シンボルを解決できないと言われたら、go to implementationでKTSから飛んで実装をコピペで持ってくれば良いです。
上記のpublishingとbintrayの設定をbuildSrcに切り出したものが以下になります。

```kotlin:Publishing.kt
package build.internal

import build.ProjectProperties
import groovy.util.Node
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.internal.HasConvention
import org.gradle.api.plugins.BasePluginConvention
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getPluginByName
import org.gradle.kotlin.dsl.named

private fun Project.publishing(configure: PublishingExtension.() -> Unit): Unit =
    (this as ExtensionAware).extensions.configure("publishing", configure)

private val Project.base: BasePluginConvention
    get() = ((this as? Project)?.convention
        ?: (this as HasConvention).convention).getPluginByName("base")

private val NamedDomainObjectContainer<Configuration>.api: NamedDomainObjectProvider<Configuration>
    get() = named<Configuration>("api")

private val NamedDomainObjectContainer<Configuration>.implementation: NamedDomainObjectProvider<Configuration>
    get() = named<Configuration>("implementation")

fun Project.publishingSettings() {
    publishing {
        publications {
            create<MavenPublication>("bintray") {
                artifact("$buildDir/outputs/aar/${base.archivesBaseName}-release.aar")
                groupId = ProjectProperties.groupId
                artifactId = base.archivesBaseName
                version = ProjectProperties.versionName
                artifact(tasks["sourcesJar"])
                pom.withXml {
                    val node = asNode()
                    val licenses = node.appendNode("licenses")
                    appendLicense(
                        licenses,
                        "The MIT License",
                        "https://opensource.org/licenses/MIT",
                        "repo"
                    )
                    appendScm(node, ProjectProperties.Url.scm, ProjectProperties.Url.github)
                    val dependencies = node.appendNode("dependencies")
                    configurations.api.get().dependencies.forEach {
                        appendDependency(
                            dependencies,
                            groupId = it.group ?: "",
                            artifactId = it.name,
                            version = it.version ?: "",
                            scope = "compile"
                        )
                    }
                    configurations.implementation.get().dependencies.forEach {
                        appendDependency(
                            dependencies,
                            groupId = it.group ?: "",
                            artifactId = it.name,
                            version = it.version ?: "",
                            scope = "runtime"
                        )
                    }
                }
            }
        }
    }
}

private fun appendLicense(parentNode: Node, name: String, url: String, distribution: String) {
    parentNode.appendNode("license").apply {
        appendNode("name", name)
        appendNode("url", url)
        appendNode("distribution", distribution)
    }
}

private fun appendScm(parentNode: Node, connection: String, url: String) {
    parentNode.appendNode("scm").apply {
        appendNode("connection", connection)
        appendNode("url", url)
    }
}

private fun appendDependency(
    parentNode: Node,
    groupId: String,
    artifactId: String,
    version: String,
    scope: String
) {
    parentNode.appendNode("dependency").apply {
        appendNode("groupId", groupId)
        appendNode("artifactId", artifactId)
        appendNode("version", version)
        appendNode("scope", scope)
    }
}
```

```kotlin:Bintray.kt
package build.internal

import build.ProjectProperties
import com.jfrog.bintray.gradle.BintrayExtension
import org.gradle.api.Project
import org.gradle.api.internal.HasConvention
import org.gradle.api.plugins.BasePluginConvention
import org.gradle.api.plugins.ExtensionAware
import org.gradle.kotlin.dsl.closureOf
import org.gradle.kotlin.dsl.getPluginByName

private fun Project.bintray(configure: BintrayExtension.() -> Unit): Unit =
    (this as ExtensionAware).extensions.configure("bintray", configure)

private val Project.base: BasePluginConvention
    get() = ((this as? Project)?.convention ?: (this as HasConvention).convention).getPluginByName("base")

fun Project.bintraySettings() {
    bintray {
        user = project.findProperty("bintray_user") as? String ?: ""
        key = project.findProperty("bintray_key") as? String ?: ""
        setPublications("bintray")

        dryRun = true

        pkg(closureOf<BintrayExtension.PackageConfig> {
            repo = "maven"
            name = ProjectProperties.groupId + "." + base.archivesBaseName
            setLicenses("MIT")
            websiteUrl = ProjectProperties.Url.site
            vcsUrl = ProjectProperties.Url.github + ".git"
            issueTrackerUrl = ProjectProperties.Url.github + "/issues"
            publicDownloadNumbers = true
            version = VersionConfig().apply {
                name = ProjectProperties.versionName
            }
        })
    }

    tasks.named("bintrayUpload") {
        dependsOn("assemble")
    }
}
```

※dryRun = trueなので実際のアップロードは行われません。アップロードする場合はfalseに書き換えるか、削除してください。

これで、buildGradleからは`publishingSettings()`と`bintraySettings()`というメソッドが使えるようになりますので以下のように呼び出します。

```kotlin:build.gradle.kts
publishingSettings()
bintraySettings()
```

以上です。