Skip to content

Instantly share code, notes, and snippets.

@xinthink
Last active January 21, 2020 06:04
Show Gist options
  • Save xinthink/2e838366f65728ac0dbd68bdd2bb7168 to your computer and use it in GitHub Desktop.
Save xinthink/2e838366f65728ac0dbd68bdd2bb7168 to your computer and use it in GitHub Desktop.
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
Copy link

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