Skip to content

Commit

Permalink
Merge pull request #353 from jmfayard/draft-kodein-di
Browse files Browse the repository at this point in the history
- New `DpendencyGroup` base API for dependency notations
- Documentation for it
- Dependency notations for Spring, Http4k, Kodein DI
  • Loading branch information
jmfayard committed Aug 6, 2021
2 parents 2f23c23 + 3b23788 commit 04aad93
Show file tree
Hide file tree
Showing 19 changed files with 2,236 additions and 85 deletions.
62 changes: 56 additions & 6 deletions docs/contributing/submitting-prs/dependency-notations-updates.md
@@ -1,10 +1,60 @@
# Submitting dependency notations updates
# Submitting dependency notations

Thanks for considering an update to the built-in dependency notations in refreshVersions!
We want to provide dependency notations for more popular libraries.

However, we are in the process of refactoring the way we define dependency notations in refreshVersions,
so it's less overhead for both you and us, and less error-prone to do overall (especially regarding backwards and forward compatibility).
Want to contribute some?

That means that **we're unlikely to accept any submission** of dependency notation updates **for now**.
Here is what a dependency notation should look like:

To be informed when that refactoring is complete, you can subscribe to this issue: {{link.issues}}/275
```kotlin
@file:Suppress("PackageDirectoryMismatch", "SpellCheckingInspection", "unused") // 1

import de.fayard.refreshVersions.core.internal.DependencyGroup
import org.gradle.api.Incubating
import org.gradle.kotlin.dsl.IsNotADependency

/** // 2
* painless Kotlin dependency injection
*
* - [Official website here](https://kodein.org/di/)
* - GitHub page: [Kodein-Framework/Kodein-DI](https://github.com/Kodein-Framework/Kodein-DI)
* - [GitHub Releases here](https://github.com/Kodein-Framework/Kodein-DI/releases)
*/
@Incubating
object Kodein: IsNotADependency { // 3

val di = DI // 4

object DI : DependencyGroup( // 5
group = "org.kodein.di", // 6
usePlatformConstraints = true, // 7
rawRule = """
org.kodein.di:kodein-di(-*)
^^^^^^^^^
""".trimIndent() // 8
) {
val bom by module("kodein-bom", isBom = true) // 9
val js by module("kodein-di-js") // 10
val androidx by module("kodein-di-framework-android-x")
}
}
```

Here is what you need to know:

1. We use on purpose no package - annd suppress the corresponding warning - so that the user doesn't have to import the dependency notation
2. We provide a KDoc with a description of what the library does and link to GitHub and the documentation
3. We tag classes with `IsNotADependency` so that the IDE shows a warning if the user tries to do `implementation(Kodein)`
4. We support Gradle build scripts in both Kotlin and Groovy. `Kodein.DI` works in Kotlin but not in Groovy. That's why we have the property `Kotlin.di` who works in both.
5. We use the base class `DependencyGroup` to define a group of dependency notations.
6. The maven group that will be used for all the modules of this `DependencyGroup`.
7. If the library provides a Bill of Materials `BoM` of another kind of platform constraints, we set `usePlatformConstraints = true`.
8. All dependency notations with a name like `org.kodein.di:kodein-di(-*)` will use the same version `version.kodein.di` because we defined an artifact rule. To learn more about [refreshVersions rules, have a look here](thttps://github.com/jmfayard/refreshVersions)
9. DependencyGroup has firt class support for `BoM`s via the `isBom = true` parameter. It switches the boolean `usePlatformConstraints = true` and does various checks.
10. A module is defined via the `by module("module.name")` syntax.

Three more things before you start coding:

- Look at [`Http4K`, `Spring` or `Kodein`](https://github.com/jmfayard/refreshVersions/tree/main/plugins/dependencies/src/main/kotlin/dependencies) for inspiration.
- **Try to not forget any artifact!**. One of the best way to do that is to open an issue in the project of the library for which you contribute dependency notations.
- **Run the unit tests!**. There are multiple checks that are done to prevent the most common mistakes.
Expand Up @@ -3,7 +3,8 @@ package de.fayard.refreshVersions.core.internal
/**
* The rules are case sensitive.
*/
internal abstract class ArtifactVersionKeyRule protected constructor(
@InternalRefreshVersionsApi
abstract class ArtifactVersionKeyRule protected constructor(
internal val artifactPattern: String,
internal val versionKeyPattern: String
) : Comparable<ArtifactVersionKeyRule> {
Expand All @@ -24,6 +25,11 @@ internal abstract class ArtifactVersionKeyRule protected constructor(

private val versionKeySignificantCharsLength = versionKeyPattern.count { it != ' ' }

override fun toString(): String = """
$artifactPattern
$versionKeyPattern
""".trimIndent()

companion object {

operator fun invoke(
Expand Down
@@ -0,0 +1,77 @@
package de.fayard.refreshVersions.core.internal

import org.gradle.kotlin.dsl.IsNotADependency
import kotlin.reflect.KProperty

@InternalRefreshVersionsApi
open class DependencyGroup(
val group: String,
rawRule: String? = null,
var usePlatformConstraints: Boolean = false
) : IsNotADependency {

val rule: ArtifactVersionKeyRule? = rawRule?.let {
val lines = it.lines()
assert(lines.size == 2) {
"2 lines were expected, but ${lines.size} were found: $it"
}
ArtifactVersionKeyRule(
artifactPattern = lines.first(),
versionKeyPattern = lines.last()
)
}


companion object {
private val ALL = mutableListOf<DependencyGroup>()
val ALL_RULES: List<ArtifactVersionKeyRule>
get() = ALL.mapNotNull { it.rule }

private val isRunningTests: Boolean by lazy {
try {
Class.forName("org.junit.jupiter.api.AssertEquals")
true
} catch (e: ClassNotFoundException) {
false
}
}
}

init {
assert(group.isNotBlank()) { "Group shall not be blank" }
ALL.add(this)
}

fun module(module: String, isBom: Boolean = false): Module {
assert(module.trimStart() == module) { "module=[$module] has superfluous leading whitespace" }
assert(module.trimEnd() == module) { "module=[$module] has superfluous trailing whitespace" }
assert(module.contains(":").not()) { "module=[$module] is invalid" }
return Module(
name = "$group:$module" + if (usePlatformConstraints && isBom.not()) "" else ":_",
isBom = isBom
)
}

private var haveDependencyNotationsBeenUsed = false

operator fun Module.getValue(thisRef: Any?, property: KProperty<*>): String {
markDependencyNotationsUsage()
return name
}

inner class Module internal constructor(
val name: String,
val isBom: Boolean
) {
@PublishedApi
internal fun markDependencyNotationsUsage() {
if (isBom && usePlatformConstraints.not()) {
if (haveDependencyNotationsBeenUsed && !isRunningTests) {
error("You are trying to use a BoM ($name), but dependency notations relying on it have been declared before! Declare the BoM first to fix this issue.")
}
}
if (isBom) usePlatformConstraints = true
haveDependencyNotationsBeenUsed = true
}
}
}
Expand Up @@ -24,7 +24,8 @@ open class RefreshVersionsPlugin : Plugin<Any> {
"kotlin(x)-version-alias-rules",
"square-version-alias-rules",
"other-version-alias-rules",
"testing-version-alias-rules"
"testing-version-alias-rules",
"dependency-groups-alias-rules"
).map {
RefreshVersionsPlugin::class.java.getResourceAsStream("/refreshVersions-rules/$it.txt")!!
.bufferedReader()
Expand Down
@@ -1,17 +1,6 @@
package de.fayard.refreshVersions.internal

import AndroidX
import COIL
import CashApp
import Firebase
import Google
import JakeWharton
import Kotlin
import KotlinX
import Ktor
import Splitties
import Square
import Testing
import dependencies.ALL_DEPENDENCIES_NOTATIONS
import dependencies.DependencyNotationAndGroup
import org.gradle.api.artifacts.ModuleIdentifier
import java.lang.reflect.Field
Expand Down Expand Up @@ -44,20 +33,7 @@ internal data class DependencyMapping(
}

internal fun getArtifactNameToConstantMapping(excludeBomDependencies: Boolean = false): List<DependencyMapping> {
return sequenceOf(
AndroidX,
CashApp,
Google,
JakeWharton,
Firebase,
Kotlin,
KotlinX,
Splitties,
Square,
Ktor,
Testing,
COIL
).flatMap { objectInstance ->
return ALL_DEPENDENCIES_NOTATIONS.asSequence().flatMap { objectInstance ->
getArtifactNameToConstantMappingFromObject(
objectInstance,
excludeBomDependencies = excludeBomDependencies,
Expand Down Expand Up @@ -95,6 +71,7 @@ private fun getArtifactNameToConstantMappingFromObject(
it != typeOf<String>() && it.javaType != java.lang.Void::class.java
}
}.flatMap { kProperty ->
if (kProperty.name == "rule") return@flatMap emptySequence()
@Suppress("unchecked_cast")
val nestedObjectInstance = (kProperty as KProperty1<Any?, Any>).get(objectInstance)
getArtifactNameToConstantMappingFromObject(
Expand Down
105 changes: 105 additions & 0 deletions plugins/dependencies/src/main/kotlin/dependencies/Http4k.kt
@@ -0,0 +1,105 @@
import de.fayard.refreshVersions.core.internal.DependencyGroup


object Http4k : DependencyGroup(
group = "org.http4k",
usePlatformConstraints = false,
rawRule = """
org.http4k:http4k-*
^^^^^^
""".trimIndent()
) {
val bom by module("http4k-bom", isBom = true)

val aws by module("http4k-aws")
val cloudnative by module("http4k-cloudnative")
val contract by module("http4k-contract")
val core by module("http4k-core")
val graphql by module("http4k-graphql")
val incubator by module("http4k-incubator")
val jsonrpc by module("http4k-jsonrpc")
val metricsMicrometer by module("http4k-metrics-micrometer")
val multipart by module("http4k-multipart")
val opentelemetry by module("http4k-opentelemetry")
val realtimeCore by module("http4k-realtime-core")
val resilience4j by module("http4k-resilience4j")
val securityOauth by module("http4k-security-oauth")

val client = Client

object Client : DependencyGroup(group, usePlatformConstraints = true) {
val apache by module("http4k-client-apache")
val apacheAsync by module("http4k-client-apache-async")
val apache4 by module("http4k-client-apache4")
val apache4Async by module("http4k-client-apache4-async")
val jetty by module("http4k-client-jetty")
val okhttp by module("http4k-client-okhttp")
val websocket by module("http4k-client-websocket")
}

val format = Format

object Format : DependencyGroup(group, usePlatformConstraints = true) {
val argo by module("http4k-format-argo")
val core by module("http4k-format-core")
val gson by module("http4k-format-gson")
val jackson by module("http4k-format-jackson")
val jacksonXml by module("http4k-format-jackson-xml")
val jacksonYaml by module("http4k-format-jackson-yaml")
val klaxon by module("http4k-format-klaxon")
val kotlinxSerialization by module("http4k-format-kotlinx-serialization")
val moshi by module("http4k-format-moshi")
val xml by module("http4k-format-xml")
}


val server = Server

object Server : DependencyGroup(group, usePlatformConstraints = true) {
val apache by module("http4k-server-apache")
val apache4 by module("http4k-server-apache4")
val jetty by module("http4k-server-jetty")
val ktorcio by module("http4k-server-ktorcio")
val ktornetty by module("http4k-server-ktornetty")
val netty by module("http4k-server-netty")
val ratpack by module("http4k-server-ratpack")
val undertow by module("http4k-server-undertow")
}

val serverless = Serverless

object Serverless : DependencyGroup(group, usePlatformConstraints = true) {
val alibaba by module("http4k-serverless-alibaba")
val azure by module("http4k-serverless-azure")
val gcf by module("http4k-serverless-gcf")
val lambda by module("http4k-serverless-lambda")
val lambdaRuntime by module("http4k-serverless-lambda-runtime")
val openwhisk by module("http4k-serverless-openwhisk")
val tencent by module("http4k-serverless-tencent")
}

val template = Template

object Template : DependencyGroup(group, usePlatformConstraints = true) {
val core by module("http4k-template-core")
val dust by module("http4k-template-dust")
val freemarker by module("http4k-template-freemarker")
val handlebars by module("http4k-template-handlebars")
val jade4j by module("http4k-template-jade4j")
val pebble by module("http4k-template-pebble")
val thymeleaf by module("http4k-template-thymeleaf")
}

val testing = Testing

object Testing : DependencyGroup(group, usePlatformConstraints = true) {
val approval by module("http4k-testing-approval")
val chaos by module("http4k-testing-chaos")
val hamkrest by module("http4k-testing-hamkrest")
val kotest by module("http4k-testing-kotest")
val servirtium by module("http4k-testing-servirtium")
val strikt by module("http4k-testing-strikt")
val webdriver by module("http4k-testing-webdriver")
}
}

37 changes: 37 additions & 0 deletions plugins/dependencies/src/main/kotlin/dependencies/Kodein.kt
@@ -0,0 +1,37 @@
@file:Suppress("PackageDirectoryMismatch", "SpellCheckingInspection", "unused")

import de.fayard.refreshVersions.core.internal.DependencyGroup
import org.gradle.api.Incubating
import org.gradle.kotlin.dsl.IsNotADependency

/**
* painless Kotlin dependency injection
*
* - [Official website here](https://kodein.org/di/)
* - GitHub page: [Kodein-Framework/Kodein-DI](https://github.com/Kodein-Framework/Kodein-DI)
* - [GitHub Releases here](https://github.com/Kodein-Framework/Kodein-DI/releases)
*/
@Incubating
object Kodein: IsNotADependency {

val di = DI

object DI : DependencyGroup(
group = "org.kodein.di",
usePlatformConstraints = false,
rawRule = """
org.kodein.di:kodein-di(-*)
^^^^^^^^^
""".trimIndent()
) {
val androidCore by module("kodein-di-framework-android-core")
val androidSupport by module("kodein-di-framework-android-support")
val androidx by module("kodein-di-framework-android-x")
val configurableJS by module("kodein-di-conf-js")
val configurableJvm by module("kodein-di-conf-jvm")
val js by module("kodein-di-js")
val jsr330 by module("kodein-di-jxinject-jvm")
val ktor by module("kodein-di-framework-ktor-server-jvm")
val tornadofx by module("kodein-di-framework-tornadofx-jvm")
}
}

0 comments on commit 04aad93

Please sign in to comment.