Gradle Kotlin DSL: Define Kotlin version in unique place

18,251

Solution 1

In later versions of Gradle you no longer need to specify the version of your kotlin(stdlib|reflect|test) dependencies, the Kotlin plugin will automatically configure them for you.

As for extracting the dependency to a single place, there are two main patterns:

  • define the constants you want to share in an object within buildSrc/src/main/kotlin/ and use that object in your build script, code from buildSrc is available to the whole script including the plugins block
  • use a system property, you can define a system property in gradle.properties by prefixing its name with systemProp. and you can access system properties via System.getProperties(), for example:

    // build.gradle.kts
    plugins {
      val kotlinVersion by System.getProperties()
      println("Kotlin version is $kotlinVersion")
    }
    
    // gradle.properties
    systemProp.kotlinVersion=1.2.20
    

Solution 2

What I just stumbled upon was using Kotlin classes ins my build.gradle.kts.

I had to:

  • create a module called buildSrc with src/main/kotlin and a build.gradle.kts in its root.
  • (obsolete) include("buildSrc") in settings.gradle.kts

The buildSrc/build.gradle.kts is very minimal:

plugins {
    `kotlin-dsl`
}

repositories {
    jcenter()
}

In buildSrc/src/main/kotlin I've added a Config.kt

const val GROUP_ID = "my-company"
const val VERSION = "0.1.0-SNAPSHOT"

const val POM_NAME = "my-library-name"
const val POM_DESCRIPTION = "A library doing stuff."
const val POM_URL = "https://github.com/${GROUP_ID}/${POM_NAME}/"
const val POM_SCM_URL = POM_URL
const val POM_SCM_CONNECTION = "scm:git:git://github.com/${GROUP_ID}/${POM_NAME}.git"
const val POM_SCM_DEV_CONNECTION = "scm:git:ssh://[email protected]/${GROUP_ID}/${POM_NAME}.git"
const val POM_LICENCE_NAME = "The Apache Software License, Version 2.0"
const val POM_LICENCE_URL = "http://www.apache.org/licenses/LICENSE-2.0.txt"
const val POM_LICENCE_DIST = "repo"
const val POM_DEVELOPER_ID = "me"
const val POM_DEVELOPER_NAME = "meeee"
const val POM_DEVELOPER_EMAIL = "[email protected]"

And a Dependencies.kt

@file:Suppress("MemberVisibilityCanBePrivate")

object Jvm {
    const val version = "1.8"
}

object Kotlin {
    const val version = "1.3.50"
    const val stdlibJdk8 = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version"
    const val jvmId = "jvm"
    const val kaptId = "kapt"
}

object MavenPublish {
    const val id = "maven-publish"
}

object Arrow {
    const val version = "0.10.1"
    const val core = "io.arrow-kt:arrow-core:$version"
    const val syntax = "io.arrow-kt:arrow-syntax:$version"
    const val optics = "io.arrow-kt:arrow-optics:$version"
    const val fx = "io.arrow-kt:arrow-fx:$version"
    const val meta = "io.arrow-kt:arrow-meta:$version"
}

object Versions {
    const val version = "0.27.0"
    const val versions = "com.github.ben-manes:gradle-versions-plugin:$version"
    const val id = "com.github.ben-manes.versions"
}

So I could use it in my root build.gradle.kts like

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin(Kotlin.jvmId) version Kotlin.version
    kotlin(Kotlin.kaptId) version Kotlin.version
    id(Versions.id) version Versions.version
    id(MavenPublish.id)
}

group = GROUP_ID
version = VERSION

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    implementation(Kotlin.stdlibJdk8)
    implementation(Arrow.core)
    implementation(Arrow.syntax)
    kapt(Arrow.meta)
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = Jvm.version
}

publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            @Suppress("UnstableApiUsage")
            pom {
                name.set(POM_NAME)
                description.set(POM_DESCRIPTION)
                url.set(POM_URL)
                licenses {
                    license {
                        name.set(POM_LICENCE_NAME)
                        url.set(POM_LICENCE_URL)
                        distribution.set(POM_LICENCE_DIST)
                    }
                }
                developers {
                    developer {
                        id.set(POM_DEVELOPER_ID)
                        name.set(POM_DEVELOPER_NAME)
                        email.set(POM_DEVELOPER_EMAIL)
                    }
                }
                scm {
                    connection.set(POM_SCM_CONNECTION)
                    developerConnection.set(POM_SCM_DEV_CONNECTION)
                    url.set(POM_SCM_URL)
                }
            }
        }
    }
}

I am quite happy with this, but when it comes down to automatically increment the version I may fall back to maintain it in the gradle.properties.


Edit: It is no longer necessary (and allowed) to add buildSrc to the settings.gradle.kts, instead it will automatically get picked up if present.

Solution 3

You can extract the version from the plugin class:

import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper

plugins {
    kotlin("jvm") version "1.2.0"
}

val kotlinVersion = plugins.getPlugin(KotlinPluginWrapper::class.java).kotlinPluginVersion

Solution 4

There's a workaround available, which searches the version defined for the kotlin plugin and assignes this one to the outer variable. The following demonstrates this:

val kotlinVersion: String? by extra {
    buildscript.configurations["classpath"]
            .resolvedConfiguration.firstLevelModuleDependencies
            .find { it.moduleName == "kotlin-gradle-plugin" }?.moduleVersion
}

plugins {
    kotlin("jvm").version("1.2.30")
    //more
}

The variable kotlinVersion can then be used in the dependencies without further trouble.

Solution 5

Answer for @s1m0nw1 comment (too long for comment): No you can't use buildSrc/src stuff in buildSrc/build.gradle. I had exactly this problem as I wrote android-based plugin and I need android gradle plugin dependency in buildsSrc but I also declare this dependency in project. So I had two different places and two versions to maintain.

I resolved this by creating gradle.properties file in buildSrc directory. In it I've created prop androidGradlePluginVersion=3.6.0-rc02

buildSrc/build.gradle:

val androidGradlePluginVersion: String by project
dependencies {
    implementation("com.android.tools.build:gradle:$androidGradlePluginVersion")

buildSrc/src/.../Versions.kt:

var ANDROID_PLUGIN = loadAndroidGradlePluginVersion()

Util for props:

val GRADLE_PROPERTIES = "buildSrc/gradle.properties"
val ANDROID_PLUGIN_VERSION_PROP = "androidGradlePluginVersion"

fun loadAndroidGradlePluginVersion(): String {
    Properties().apply { load(FileInputStream(GRADLE_PROPERTIES)) }.let {
        return it.getProperty(ANDROID_PLUGIN_VERSION_PROP)
    }

    error("Provide $ANDROID_PLUGIN_VERSION_PROP in $GRADLE_PROPERTIES")
} 
Share:
18,251
s1m0nw1
Author by

s1m0nw1

I'm a Software Engineer working on distributed systems and integrations at BRYTER. I'm an Oracle Certified Java Professional, a Kotlin Enthusiast and also a Google Developer Expert for Kotlin. Read about my Kotlin stories on this blog: https://kotlinexpertise.com I'm currently learning to like Go. If you want to say thank you for just anything, feel free to buy me a coffee ☕️ on https://ko-fi.com/s1m0nw1 Other profiles around the web: GitHub Twitter Kotlin Blog LinkedIn

Updated on June 06, 2022

Comments

  • s1m0nw1
    s1m0nw1 almost 2 years

    For describing Gradle build scripts, we can use Kotlin via build.gradle.kts files. It's a common problem to globally define the Kotlin version to be used, both in the dependencies and also in the build plugin section (It's rather uncommon to have different versions in use for the given case).

    Consider the following code (Gradle 4.3.1):

    plugins {
        var pluginVersion = "1.2.30"
        kotlin("jvm").version(kotlinVersion)
        // more
    }
    
    var dependencyVersion = "1.2.30"
    dependencies {
        compile(kotlin("stdlib", kotlinVersion))
        compile(kotlin("reflect", kotlinVersion))
        testCompile(kotlin("test", kotlinVersion))
        // more
    }
    

    As you can see, the kotlin version (1.2.30 in this case) is defined twice: dependencyVersion and pluginVersion, which very often does not differ. Due to DSL restrictions, it is impossible to access the pluginVersion from outside the plugins block or access the dependencyVersion from within the plugins block.

    How can the version string, "1.2.30" be extracted to a single place?

    • nuamehas
      nuamehas over 5 years
      kotlin version constant is part of gradle-kotlin-DSL. Usage samples: implementation(embeddedKotlin("stdlib-jdk7")) or classpath(embeddedKotlin("gradle-plugin"))
  • s1m0nw1
    s1m0nw1 over 5 years
    When defining constants in buildSrc, can I also use them in the build.gradle.kts of buildSrc itself?
  • nuamehas
    nuamehas over 5 years
    Note: kotlin version constant is part of gradle-kotlin-DSL. Usage samples: implementation(embeddedKotlin("stdlib-jdk7")) or classpath(embeddedKotlin("gradle-plugin"))
  • peacepassion
    peacepassion about 5 years
    buildSrc is really a good place and Gradle support such way officially.
  • Kamil Seweryn
    Kamil Seweryn about 4 years
    You can get the version from KotlinCompilerVersion.VERSION
  • Shenk
    Shenk almost 4 years
    Doing this, I still had to duplicate the property definitions in both buildSrc/gradle.properties and rootProject/gradle.properties. Otherwise, when trying to use the property, such as in buildSrc/src/main/kotlin/Versions.kt, it doesn't find the property. For some reason, it tries to search for the property in the rootProject's buildSrc, skipping the buildSrc's. I've tried for hours to find a solution yet to no avail.
  • Shenk
    Shenk almost 4 years
    Okay, so I was eventually able to get around the issue by using this val gradlePropertiesFile: File = File("${System.getProperty("user.dir")}${if (project.name == "buildSrc") "" else "/buildSrc"}/$GRADLE_PROPERTIES") as the file from which to load the input stream. (using Kotlin DSL)
  • Kostek
    Kostek almost 4 years
    I guess it make sense as it is resolved in runtime instead of compile time. My bad, I forgot to add my consts: GRADLE_PROPERTIES = "buildSrc/gradle.properties" ANDROID_PLUGIN_VERSION_PROP = "androidGradlePluginVersion"
  • user3681304
    user3681304 almost 4 years
    'buildSrc' cannot be used as a project name as it is a reserved name in settings.gradle
  • sschrass
    sschrass almost 4 years
    Yes, this is true for recent versions. BuildSrc also gets automatically picked up if present.