Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Defining Dependencies in Gradle Kotlin DSL

This's for the article Defining Dependencies in Gradle Kotlin DSL.

If you're using kotlin-dsl in a multi-project manner, you may want to define all the dependencies in one place. You can put the files into buildSrc, define dependencies in a compat way like this:

extra.deps {
    "kt"("stdlib-jre7")
    "auto" {
        "common"("com.google.auto:auto-common:0.8")
        "service"("com.google.auto.service:auto-service:1.0-rc3")
    }
    "gson" to "com.google.code.gson:gson:2.8.0"

    // for testing
    "junit"("junit:junit:4.12")
    "mockito" {
        "core" to "org.mockito:mockito-core:2.10.0"
        "inline" to "org.mockito:mockito-inline:2.10.0"
    }
}

Then refer to them from (each) sub-project's build.gradle.kts

dependencies {
    val kt = kotlin(deps["kt"])
    compileOnly(kt)

    compile(deps["auto.common"])
    compile(deps["gson"])

    testRuntime(kt)
    testCompile(deps["junit"])
    testCompile(deps["mockito.inline"])              
}
import org.gradle.api.Project
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.kotlin.dsl.extra
const val DEPS_ATTR = "deps"
/** Supertype of a dependency definition */
interface DependencyItem {
operator fun get(key: String): Any
}
/** Leaf nodes in the dependency tree, defines a single dependency */
data class DependencyNotation(private val notation: String) : CharSequence by notation,
DependencyItem {
override operator fun get(key: String): String = notation
override fun toString() = notation
}
/** Defines a group of dependencies */
class DependencyGroup : DependencyItem {
private val dependencies = mutableMapOf<String, DependencyItem>()
/** Get dependency with the [key].
*
* A `.` notation is also allowed to retrieve grouped dependencies, e.g, `deps["support.appCompat"]`.
*/
override operator fun get(key: String): DependencyItem = key.split('.')
.fold(this as DependencyItem) { acc, k ->
(acc as? DependencyGroup)?.getItem(k) ?: acc
}
private fun getItem(key: String): DependencyItem =
dependencies[key] ?: throw IllegalArgumentException(
"dependency `$key` not found in group $this"
)
/** Define a dependency */
operator fun set(key: String, item: DependencyItem) = dependencies.set(key, item)
/** Define a dependency */
operator fun set(key: String, notation: String) =
dependencies.set(key, DependencyNotation(notation))
/**
* The key/value syntax to define a dependency.
*
* ```
* deps {
* "core-ktx" to "androidx.core:core-ktx:$ktx_version"
* "appcompat" to "androidx.appcompat:appcompat:$appcompat_version"
* }
* ```
*/
infix fun String.to(notation: String) = set(this, notation)
/** The `in` operator */
operator fun contains(key: String): Boolean = key in dependencies
/**
* The closure syntax to define grouped dependencies.
*
* ```
* "androidx" {
* "core-ktx"("androidx.core:core-ktx:$ktx_version")
* "appcompat"("androidx.appcompat:appcompat:$appcompat_version")
* }
* ```
*/
operator fun invoke(config: DependencyGroup.() -> Unit): DependencyGroup = apply(config)
/**
* The `()` syntax to define a single dependency.
*
* ```
* deps {
* "core-ktx"("androidx.core:core-ktx:$ktx_version")
* "appcompat"("androidx.appcompat:appcompat:$appcompat_version")
* }
* ```
*/
operator fun String.invoke(notation: String) = set(this, notation)
/**
* The key/value syntax to define grouped dependencies.
*
* ```
* "androidx"(
* "core-ktx" to "androidx.core:core-ktx:$ktx_version"
* "appcompat" to "androidx.appcompat:appcompat:$appcompat_version"
* )
* ```
*/
operator fun String.invoke(vararg dependencies: Pair<String, Any>) =
set(this, create(*dependencies))
/**
* The closure syntax to define a dependency group.
*
* ```
* "androidx" {
* "core-ktx"("androidx.core:core-ktx:$ktx_version")
* "appcompat"("androidx.appcompat:appcompat:$appcompat_version")
* }
* ```
*/
operator fun String.invoke(init: DependencyGroup.() -> Unit) = set(this, create().apply(init))
override fun toString() = dependencies.toString()
companion object {
@Suppress("UNCHECKED_CAST")
private fun create(dependencies: Map<String, Any>): DependencyGroup {
val inst = DependencyGroup()
dependencies.forEach {
val (key, item) = it
when (item) {
is String -> inst[key] = item
is DependencyItem -> inst[key] = item
is Map<*, *> -> inst[key] = create(item as Map<String, Any>)
else -> throw IllegalArgumentException("Unsupported dependency item type of `$item`")
}
}
return inst
}
private fun create(vararg dependencies: Pair<String, Any>): DependencyGroup =
create(mapOf(*dependencies))
}
}
/**
* Retrieve the dependency tree from extra properties, a new one will be created for the first access.
*/
val ExtraPropertiesExtension.deps: DependencyGroup get() =
if (has(DEPS_ATTR)) this[DEPS_ATTR] as DependencyGroup
else DependencyGroup().apply {
this@deps[DEPS_ATTR] = this
}
/**
* Retrieve current project's dependency tree.
*
* If no `deps` property defined in a sub-project, looks for it in the root project.
*/
val Project.deps: DependencyGroup get() =
if (parent == null || extra.has(DEPS_ATTR)) extra.deps
else rootProject.deps
import org.gradle.api.Project
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.kotlin.dsl.kotlin
import java.io.File
import java.io.FileInputStream
import java.util.*
operator fun Project.contains(propertyName: String): Boolean = hasProperty(propertyName)
fun Project.loadProperties(path: String, extra: ExtraPropertiesExtension)
= loadProperties(file(path), extra)
fun Project.loadProperties(file: File, extra: ExtraPropertiesExtension) {
if (!file.exists()) return
Properties().apply {
FileInputStream(file).use {
load(it)
forEach { (k, v) ->
extra["$k"] = v
}
}
}
}
fun DependencyHandler.kotlin(module: CharSequence) = kotlin(module.toString())
fun DependencyHandler.kotlin(module: DependencyItem)
= kotlin(module as? CharSequence
?: throw IllegalArgumentException("Unexpected module type for $module"))
@vladad

This comment has been minimized.

Copy link

@vladad vladad commented Feb 28, 2019

Hello,
I think you should close FileInputStream instance in loadProperties extension method in utils.kt.

Maybe change to:

file.inputStream().use {
    load(it)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.