Gradle Kotlin DSL: Define Kotlin version in unique place
Asked Answered
M

7

60

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?

Melbourne answered 1/12, 2017 at 7:11 Comment(1)
kotlin version constant is part of gradle-kotlin-DSL. Usage samples: implementation(embeddedKotlin("stdlib-jdk7")) or classpath(embeddedKotlin("gradle-plugin"))Angelicangelica
E
36

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
    
Epigastrium answered 18/1, 2018 at 11:41 Comment(3)
When defining constants in buildSrc, can I also use them in the build.gradle.kts of buildSrc itself?Melbourne
Note: kotlin version constant is part of gradle-kotlin-DSL. Usage samples: implementation(embeddedKotlin("stdlib-jdk7")) or classpath(embeddedKotlin("gradle-plugin"))Angelicangelica
buildSrc is really a good place and Gradle support such way officially.Fowler
B
34

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.

Benares answered 16/10, 2019 at 9:19 Comment(4)
'buildSrc' cannot be used as a project name as it is a reserved name in settings.gradleUnbiased
Yes, this is true for recent versions. BuildSrc also gets automatically picked up if present.Benares
This solution worked for me, thanks! I have only one comment about it, I had exactly the same setup but what your answer made me realize is that I shouldn't import the variable from the dependencies file but use the full import instead i.e, if my dependencies are defined in a file called Plugins.kt inside buildSrc: Don't do (my mistake): plugins { id(Kotlin) version KotlinVersion } Instead, just do : plugins { id(Plugins.Kotlin) version Plugins.KotlinVersion }Colcothar
I was migrating from Groovy to Kotlin and suddenly my gradle.properties variables are not available anymore. Is there any change required?Lebron
K
8

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
Kilowatthour answered 1/12, 2017 at 7:30 Comment(1)
You can get the version from KotlinCompilerVersion.VERSIONBeauteous
D
3

Once you've defined a version for the Kotlin plugin all other Kotlin libraries will use the same version, and do not need a specific version set.

So the only place you need to set the version is when defining the classpath for the plugin, e.g.:

buildscript {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }

    dependencies {
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31")
    }
}

If you then need the version for some other purpose (such as in a resolutionStrategy or just for information) you can get it from org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION

For example:

println("Kotlin version used is ${org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION}")
Distributive answered 25/7, 2021 at 3:21 Comment(0)
M
2

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.

Melbourne answered 1/12, 2017 at 7:13 Comment(0)
C
2

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")
} 
Carlinecarling answered 5/2, 2020 at 8:13 Comment(3)
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.Elbrus
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)Elbrus
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"Carlinecarling
A
2

Easy and fast workaroud:

In buildscript section set value as system property:

buildscript {   
   val version = "X.X.X"
   System.setProperty("version", version)
}

In plugins section get this property:

plugins {
    val version = System.getProperty("version")
}
Aerify answered 12/7, 2022 at 14:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.