How to declare dependencies in one place when using Gradle Kotlin DSL?

I’m in process of migrating one of my project to gradle kotlin dsl now and I’m trying to do two things:

  1. Place all my additional build code inside buildSrc folder
  2. Keep all my project dependencies in one place

So to do 2 I decided to create Kotlin object inside buildSrc folder and declare all my dependencies here. The problem is now I need to apply some dependencies inside buildSrc builld.gradle.kts to be able to access classes declared by this dependencies, but I can’t use my dependencies digest because it also located inside buildSrc folder (see https://github.com/gradle/kotlin-dsl/issues/1320). What I tried to do is create folder “dependenciesDigest”, place my Kotlin object here and apply it as separate build inside buildSrc, but I get error message “Cannot include build ‘dependenciesDigest’ in build ‘buildSrc’. This is not supported yet.”. So can I somehow achieve those goals with Gradle Kotlin DSL?

Edit: I created sample project to better describe what I’m trying to achieve: GradleExample.zip (249.7 KB).

Basically I have main android app module with following build.gradle.kts file:

import DependenciesDigest as Deps

plugins {
    androidApplication
    kotlinAndroid
    kotlinAndroidExtensions
}

android {
    setCompileSdkVersion(28)

    defaultConfig {
        applicationId = "com.bejibx.gradleexample"
        setMinSdkVersion(21)
        setTargetSdkVersion(28)
        versionCode = 1
        versionName = "1.0"
        testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        named("release") {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
        }
    }

    testOptions {
        animationsDisabled = true

        unitTests.apply {
            isIncludeAndroidResources = true
            all {
                jvmArgs = listOf("-noverify")
            }
        }
    }
}

dependencies {
    implementation(Deps.Kotlin.StdLib.jdk7)
    implementation(Deps.AndroidX.appcompat)
}

DependenciesDigest is implemented inside buildSrc as follows:

object VersionsDigest {

    /**
     * [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
     */
    const val kotlin = "1.3.40"

    /**
     * [Changelog](https://developer.android.com/studio/releases/gradle-plugin)
     */
    const val androidGradlePlugin = "3.4.1"

    /**
     * [Documentation](https://developer.android.com/jetpack/androidx)
     */
    object AndroidX {

        /**
         * [Changelog](https://developer.android.com/jetpack/androidx/releases/appcompat)
         */
        const val appcompat = "1.0.2"
    }
}

object DependenciesDigest {

    const val androidGradlePlugin =
        "com.android.tools.build:gradle:${VersionsDigest.androidGradlePlugin}"

    object Kotlin {
        object StdLib {
            const val jdk7 = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${VersionsDigest.kotlin}"
            const val jdk8 = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${VersionsDigest.kotlin}"
        }
    }

    object AndroidX {
        const val appcompat = "androidx.appcompat:appcompat:${VersionsDigest.AndroidX.appcompat}"
    }
}

I’m also placing some helpers inside buildSrc to fix some groovy interop problems, for example:

import com.android.build.gradle.internal.dsl.TestOptions
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.KotlinClosure1

fun TestOptions.UnitTestOptions.all(block: Test.() -> Unit) {
    all(KotlinClosure1<Any, Test>({ (this as Test).apply(block) }, owner = this))
}

Now the problem is to use classes from Android Gradle plugin I have to declare it as dependency inside buildSrc/build.gradle.kts like so:

plugins {
    `kotlin-dsl`
}

repositories {
    jcenter()
    google()
}

dependencies {
    implementation("com.android.tools.build:gradle:3.4.1")

// This fails with error message: "Unresolved reference: DependenciesDigest"
//    implementation(DependenciesDigest.androidGradlePlugin)
}

Which makes my DependenciesDigest a bit pointless because now I have to declare Android Gradle plugin version in two places.

What I tried to do is extract DependenciesDigest into separate folder and use it as included build which also failed buildSrc/settings.gradle.kts:

// This fails with error message: "Cannot include build ':dependenciesDigest' in build 'buildSrc'. This is not supported yet."
//includeBuild(":dependenciesDigest")

Can I somehow avoid redeclaring my dependencies in multiple places?

1 Like

Finally figure out how to do this.

I create example project to demonstrate this approach.

Basically what you need to do is precompile all your build extensions into jar and add this jar to classpath in settings.gradle[.kts] like so:

buildscript {
    repositories {
        flatDir {
            dirs("${rootProject.projectDir}/libs/")
        }
    }
    dependencies {
        classpath(group = "ru.bejibx.android.gradle", name = "build-extensions", version = "1.0")
    }
}

After this you can reference almost all of your extension code in build scripts.

Two problems with this approach that I found:

  • pluginManagement block in settings.gradle[.kts] should be first block so you can’t update classpath before it and use you code here. The solution is to not use this block and move plugins configuration logic to your own gradle plugin and just apply this plugin inside settings.gradle[.kts].

  • For some strange reason plugins block only allow you to use code from default java package. See this stackoverflow question for more info. To fix this I just keep my extension methods for `` in default java package.