From e4b619e050ee0a43b96afe71808afc753a893774 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 20 Feb 2023 11:54:26 +0600 Subject: [PATCH 1/5] start 0.16.12 --- CHANGELOG.md | 2 ++ gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbb0ce5fa10..7e4ab5259c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.16.12 + ## 0.16.11 * `LanguageCodes`: diff --git a/gradle.properties b/gradle.properties index 9bad69f4bfb..9a4f1279a67 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.16.11 -android_code_version=179 +version=0.16.12 +android_code_version=180 From 2b76ad0aa93bda418e6838b5dfe647824b7a9470 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 20 Feb 2023 11:55:14 +0600 Subject: [PATCH 2/5] improvements in selectByIds of CommonExposedRepo --- CHANGELOG.md | 4 ++++ .../micro_utils/repos/exposed/CommonExposedRepo.kt | 14 ++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e4ab5259c0..19dc0e3b0e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.16.12 +* `Repos`: + * `Exposed`: + * `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach + ## 0.16.11 * `LanguageCodes`: diff --git a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt index 8e12921bece..febc8648ddf 100644 --- a/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt +++ b/repos/exposed/src/jvmMain/kotlin/dev/inmo/micro_utils/repos/exposed/CommonExposedRepo.kt @@ -7,15 +7,9 @@ interface CommonExposedRepo : ExposedRepo { val ResultRow.asId: IdType val selectById: ISqlExpressionBuilder.(IdType) -> Op val selectByIds: ISqlExpressionBuilder.(List) -> Op - get() = { list -> - if (list.isEmpty()) { - Op.FALSE - } else { - var op = selectById(list.first()) - (1 until list.size).forEach { - op = op.or(selectById(list[it])) - } - op - } + get() = { + it.foldRight?>(null) { id, acc -> + acc ?.or(selectById(id)) ?: selectById(id) + } ?: Op.FALSE } } From 9c40d7da3d488be7fe55807cd93a356971768f7d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 22 Feb 2023 01:34:42 +0600 Subject: [PATCH 3/5] add generator for koin definitions --- build.gradle | 1 + extensions.gradle | 2 +- gradle/libs.versions.toml | 10 ++ jvm.publish.gradle | 118 +++++++++++++ jvm.publish.kpsb | 1 + koin/generator/build.gradle | 15 ++ koin/generator/src/main/kotlin/Processor.kt | 163 ++++++++++++++++++ koin/generator/src/main/kotlin/Provider.kt | 11 ++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + koin/generator/test/build.gradle | 27 +++ .../kotlin/GeneratedDefinitionsTest.kt | 26 +++ .../test/src/commonMain/kotlin/Test.kt | 13 ++ .../test/src/main/AndroidManifest.xml | 1 + .../annotations/GenerateKoinDefinition.kt | 14 ++ settings.gradle | 2 + 15 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 jvm.publish.gradle create mode 100644 jvm.publish.kpsb create mode 100644 koin/generator/build.gradle create mode 100644 koin/generator/src/main/kotlin/Processor.kt create mode 100644 koin/generator/src/main/kotlin/Provider.kt create mode 100644 koin/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider create mode 100644 koin/generator/test/build.gradle create mode 100644 koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt create mode 100644 koin/generator/test/src/commonMain/kotlin/Test.kt create mode 100644 koin/generator/test/src/main/AndroidManifest.xml create mode 100644 koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt diff --git a/build.gradle b/build.gradle index 714b2f1f80c..58556396edd 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ buildscript { dependencies { classpath libs.buildscript.kt.gradle classpath libs.buildscript.kt.serialization + classpath libs.buildscript.kt.ksp classpath libs.buildscript.jb.dokka classpath libs.buildscript.gh.release classpath libs.buildscript.android.gradle diff --git a/extensions.gradle b/extensions.gradle index 9b17566dc84..5399f555dfc 100644 --- a/extensions.gradle +++ b/extensions.gradle @@ -29,6 +29,6 @@ allprojects { defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" - publishMavenPath = "${rootProject.projectDir.absolutePath}/maven.publish.gradle" + publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle" } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eb4143623cc..0d61310cebd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,9 @@ gh-release = "2.4.1" koin = "3.3.2" +ksp = "1.7.20-1.0.8" +kotlin-poet = "1.12.0" + android-gradle = "7.3.0" dexcount = "3.1.0" @@ -83,9 +86,16 @@ android-test-junit = { module = "androidx.test.ext:junit", version.ref = "androi kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" } kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" } +# ksp dependencies + +kotlin-poet = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin-poet" } +ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } + +# Buildscript buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" } buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" } +buildscript-kt-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" } buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" } buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" } buildscript-android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" } diff --git a/jvm.publish.gradle b/jvm.publish.gradle new file mode 100644 index 00000000000..074de9bf513 --- /dev/null +++ b/jvm.publish.gradle @@ -0,0 +1,118 @@ +apply plugin: 'maven-publish' + +task javadocJar(type: Jar) { + from javadoc + classifier = 'javadoc' +} +task sourcesJar(type: Jar) { + from sourceSets.main.allSource + classifier = 'sources' +} + +publishing { + publications { + maven(MavenPublication) { + from components.java + + artifact javadocJar + artifact sourcesJar + + pom { + resolveStrategy = Closure.DELEGATE_FIRST + + description = "It is set of projects with micro tools for avoiding of routines coding" + name = "${project.name}" + url = "https://github.com/InsanusMokrassar/MicroUtils/" + + scm { + developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/MicroUtils.git[push=]https://github.com/InsanusMokrassar/MicroUtils.git" + url = "https://github.com/InsanusMokrassar/MicroUtils.git" + } + + developers { + + developer { + id = "InsanusMokrassar" + name = "Aleksei Ovsiannikov" + email = "ovsyannikov.alexey95@gmail.com" + } + + + developer { + id = "000Sanya" + name = "Syrov Aleksandr" + email = "000sanya.000sanya@gmail.com" + } + + } + + licenses { + + license { + name = "Apache Software License 2.0" + url = "https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE" + } + + } + } + repositories { + if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) { + maven { + name = "GithubPackages" + url = uri("https://maven.pkg.github.com/InsanusMokrassar/MicroUtils") + + credentials { + username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER') + password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD') + } + + } + } + if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { + maven { + name = "Gitea" + url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") + + credentials(HttpHeaderCredentials) { + name = "Authorization" + value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') + } + + authentication { + header(HttpHeaderAuthentication) + } + + } + } + if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { + maven { + name = "sonatype" + url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") + + credentials { + username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') + password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') + } + + } + } + } + } + } +} + +if (project.hasProperty("signing.gnupg.keyName")) { + apply plugin: 'signing' + + signing { + useGpgCmd() + + sign publishing.publications + } + + task signAll { + tasks.withType(Sign).forEach { + dependsOn(it) + } + } +} diff --git a/jvm.publish.kpsb b/jvm.publish.kpsb new file mode 100644 index 00000000000..795bb763de5 --- /dev/null +++ b/jvm.publish.kpsb @@ -0,0 +1 @@ +{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/MicroUtils/blob/master/LICENSE"}],"mavenConfig":{"name":"${project.name}","description":"It is set of projects with micro tools for avoiding of routines coding","url":"https://github.com/InsanusMokrassar/MicroUtils/","vcsUrl":"https://github.com/InsanusMokrassar/MicroUtils.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"},{"id":"000Sanya","name":"Syrov Aleksandr","eMail":"000sanya.000sanya@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/MicroUtils"},{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"} \ No newline at end of file diff --git a/koin/generator/build.gradle b/koin/generator/build.gradle new file mode 100644 index 00000000000..dc08c5eb3a6 --- /dev/null +++ b/koin/generator/build.gradle @@ -0,0 +1,15 @@ +plugins { + id "org.jetbrains.kotlin.jvm" +} + +apply from: "$publishJvmOnlyPath" + +repositories { + mavenCentral() +} + +dependencies { + api project(":micro_utils.koin") + api libs.kotlin.poet + api libs.ksp +} diff --git a/koin/generator/src/main/kotlin/Processor.kt b/koin/generator/src/main/kotlin/Processor.kt new file mode 100644 index 00000000000..bfe6f0336b1 --- /dev/null +++ b/koin/generator/src/main/kotlin/Processor.kt @@ -0,0 +1,163 @@ +package dev.inmo.micro_utils.koin.generator + +import com.google.devtools.ksp.KSTypeNotPresentException +import com.google.devtools.ksp.KSTypesNotPresentException +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getAnnotationsByType +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSFile +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.asTypeName +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toTypeName +import com.squareup.kotlinpoet.ksp.writeTo +import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition +import org.koin.core.Koin +import org.koin.core.module.Module +import org.koin.core.scope.Scope +import java.io.File +import kotlin.reflect.KClass + +class Processor( + private val codeGenerator: CodeGenerator +) : SymbolProcessor { + private val definitionClassName = ClassName("org.koin.core.definition", "Definition") + private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition") + + @OptIn(KspExperimental::class) + override fun process(resolver: Resolver): List { + resolver.getSymbolsWithAnnotation( + GenerateKoinDefinition::class.qualifiedName!! + ).filterIsInstance().forEach { ksFile -> + FileSpec.builder( + ksFile.packageName.asString(), + "GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}" + ).apply { + addFileComment( + """ + THIS CODE HAVE BEEN GENERATED AUTOMATICALLY + TO REGENERATE IT JUST DELETE FILE + ORIGINAL FILE: ${ksFile.fileName} + """.trimIndent() + ) + ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach { + val type = runCatching { + it.type.asTypeName() + }.getOrElse { e -> + if (e is KSTypeNotPresentException) { + e.ksType.toClassName() + } else { + throw e + } + } + val targetType = runCatching { + type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type)) + }.getOrElse { e -> + when (e) { + is KSTypeNotPresentException -> e.ksType.toClassName() + } + if (e is KSTypesNotPresentException) { + type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray()) + } else { + throw e + } + }.copy( + nullable = it.nullable + ) + fun addGetterProperty( + receiver: KClass<*> + ) { + addProperty( + PropertySpec.builder( + it.name, + targetType, + ).apply { + getter( + FunSpec.getterBuilder().apply { + addCode( + "return " + (if (it.nullable) { + "getOrNull" + } else { + "get" + }) + "(named(\"${it.name}\"))" + ) + }.build() + ) + receiver(receiver) + }.build() + ) + } + + addGetterProperty(Scope::class) + addGetterProperty(Koin::class) + + if (it.generateSingle) { + addFunction( + FunSpec.builder("${it.name}Single").apply { + receiver(Module::class) + addParameter( + ParameterSpec.builder( + "createdAtStart", + Boolean::class + ).apply { + defaultValue("false") + }.build() + ) + addParameter( + ParameterSpec.builder( + "definition", + definitionClassName.parameterizedBy(targetType.copy(nullable = false)) + ).build() + ) + returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false))) + addCode( + "return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)" + ) + }.build() + ) + } + + if (it.generateFactory) { + addFunction( + FunSpec.builder("${it.name}Factory").apply { + receiver(Module::class) + addParameter( + ParameterSpec.builder( + "definition", + definitionClassName.parameterizedBy(targetType.copy(nullable = false)) + ).build() + ) + returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false))) + addCode( + "return factory(named(\"${it.name}\"), definition = definition)" + ) + }.build() + ) + } + addImport("org.koin.core.qualifier", "named") + } + }.build().let { + File( + File(ksFile.filePath).parent, + "GeneratedDefinitions${ksFile.fileName}" + ).takeIf { !it.exists() } ?.apply { + parentFile.mkdirs() + + writer().use { writer -> + it.writeTo(writer) + } + } + } + } + + return emptyList() + } +} diff --git a/koin/generator/src/main/kotlin/Provider.kt b/koin/generator/src/main/kotlin/Provider.kt new file mode 100644 index 00000000000..776cee73178 --- /dev/null +++ b/koin/generator/src/main/kotlin/Provider.kt @@ -0,0 +1,11 @@ +package dev.inmo.micro_utils.koin.generator + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class Provider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor( + environment.codeGenerator + ) +} diff --git a/koin/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/koin/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000000..8b8357cb6cf --- /dev/null +++ b/koin/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +dev.inmo.micro_utils.koin.generator.Provider diff --git a/koin/generator/test/build.gradle b/koin/generator/test/build.gradle new file mode 100644 index 00000000000..bae4cfcf47d --- /dev/null +++ b/koin/generator/test/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" + id "com.google.devtools.ksp" +} + +apply from: "$mppProjectWithSerializationPresetPath" + + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":micro_utils.koin") + } + } + } +} + + +dependencies { + add("kspCommonMainMetadata", project(":micro_utils.koin.generator")) +} + +ksp { +} diff --git a/koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt b/koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt new file mode 100644 index 00000000000..52f94b499db --- /dev/null +++ b/koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt @@ -0,0 +1,26 @@ +// THIS CODE HAVE BEEN GENERATED AUTOMATICALLY +// TO REGENERATE IT JUST DELETE FILE +// ORIGINAL FILE: Test.kt +package dev.inmo.micro_utils.koin.generator.test + +import kotlin.Boolean +import kotlin.String +import org.koin.core.Koin +import org.koin.core.definition.Definition +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.Module +import org.koin.core.qualifier.named +import org.koin.core.scope.Scope + +public val Scope.sampleInfo: Test + get() = get(named("sampleInfo")) + +public val Koin.sampleInfo: Test + get() = get(named("sampleInfo")) + +public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, + definition: Definition>): KoinDefinition> = + single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) + +public fun Module.sampleInfoFactory(definition: Definition>): + KoinDefinition> = factory(named("sampleInfo"), definition = definition) diff --git a/koin/generator/test/src/commonMain/kotlin/Test.kt b/koin/generator/test/src/commonMain/kotlin/Test.kt new file mode 100644 index 00000000000..7ae8be7edbe --- /dev/null +++ b/koin/generator/test/src/commonMain/kotlin/Test.kt @@ -0,0 +1,13 @@ +@file:GenerateKoinDefinition("sampleInfo", Test::class, String::class, nullable = false) +package dev.inmo.micro_utils.koin.generator.test + +import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition +import org.koin.core.Koin + +class Test( + koin: Koin +) { + init { + koin.sampleInfo + } +} diff --git a/koin/generator/test/src/main/AndroidManifest.xml b/koin/generator/test/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..f9c1662df72 --- /dev/null +++ b/koin/generator/test/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt b/koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt new file mode 100644 index 00000000000..f4035f1d68b --- /dev/null +++ b/koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt @@ -0,0 +1,14 @@ +package dev.inmo.micro_utils.koin.annotations + +import kotlin.reflect.KClass + +@Target(AnnotationTarget.FILE) +@Repeatable +annotation class GenerateKoinDefinition( + val name: String, + val type: KClass<*>, + vararg val typeArgs: KClass<*>, + val nullable: Boolean = true, + val generateSingle: Boolean = true, + val generateFactory: Boolean = true +) diff --git a/settings.gradle b/settings.gradle index 76ab4756747..3ffca7736c8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,8 @@ String[] includes = [ ":safe_wrapper", ":crypto", ":koin", + ":koin:generator", + ":koin:generator:test", ":selector:common", ":pagination:common", ":pagination:exposed", From 9e4bb9d6780b890a981021f8a26622b7f52a47d5 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 22 Feb 2023 10:20:15 +0600 Subject: [PATCH 4/5] add readme to generator and add several fixes in processor --- CHANGELOG.md | 3 + koin/generator/README.md | 100 ++++++++++++++++++ koin/generator/src/main/kotlin/Processor.kt | 15 +++ .../kotlin/GeneratedDefinitionsTest.kt | 12 +++ .../annotations/GenerateKoinDefinition.kt | 14 +++ 5 files changed, 144 insertions(+) create mode 100644 koin/generator/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 19dc0e3b0e4..8795d05b210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ * `Repos`: * `Exposed`: * `CommonExposedRepo.selectByIds` uses `foldRight` by default instead of raw foreach +* `Koin`: + * `Generator`: + * Module has been created ## 0.16.11 diff --git a/koin/generator/README.md b/koin/generator/README.md new file mode 100644 index 00000000000..b714bc31616 --- /dev/null +++ b/koin/generator/README.md @@ -0,0 +1,100 @@ +# Koin generator + +It is Kotlin Symbol Processing generator for `Koin` module in `MicroUtils`. + +1. [What may do this generator](#what-may-do-this-generator) +2. [How to add generator](#how-to-add-generator) + +## What may do this generator + +Let's imagine you want to have shortcuts in koin, to get something easily: + +```kotlin +val koin: Koin// some initialization + +val someUrl = koin.serverUrl +``` + +So, in that case you may mark containing file with next annotation (in the beginning of file): + +```kotlin +@file:GenerateKoinDefinition("serverUrl", String::class, nullable = false) +``` + +If file is called like `Sample.kt`, will be generated file `GeneratedDefinitionsSample.kt` with next content: + +```kotlin +public val Scope.serverUrl: String + get() = get(named("serverUrl")) + +public val Koin.serverUrl: String + get() = get(named("serverUrl")) + +public fun Module.serverUrlSingle(createdAtStart: Boolean = false, + definition: Definition): KoinDefinition = + single(named("serverUrl"), createdAtStart = createdAtStart, definition = definition) + +public fun Module.serverUrlFactory(definition: Definition): + KoinDefinition = factory(named("serverUrl"), definition = definition) +``` + +Besides, you may use the generics: + +```kotlin +@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false) +``` + +Will generate: + +```kotlin +public val Scope.sampleInfo: Sample + get() = get(named("sampleInfo")) + +public val Koin.sampleInfo: Sample + get() = get(named("sampleInfo")) + +public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, + definition: Definition>): KoinDefinition> = + single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) + +public fun Module.sampleInfoFactory(definition: Definition>): + KoinDefinition> = factory(named("sampleInfo"), definition = definition) +``` + +In case you wish not to generate single: + +```kotlin +@file:GenerateKoinDefinition("sampleInfo", Sample::class, G1::class, G2::class, nullable = false, generateSingle = false) +``` + +And you will take next code: + +```kotlin +public val Scope.sampleInfo: Sample + get() = get(named("sampleInfo")) + +public val Koin.sampleInfo: Sample + get() = get(named("sampleInfo")) + +public fun Module.sampleInfoFactory(definition: Definition>): + KoinDefinition> = factory(named("sampleInfo"), definition = definition) +``` + +## How to add generator + +**Note: $ksp_version in the samples above is equal to supported `ksp` version presented in `/gradle/libs.versions.toml` of project** + +**Note: $microutils_version in the version of MicroUtils library in your project** + +1. Add `classpath` in `build.gradle` (`classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"`) +2. Add plugin to the plugins list of your module: `id "com.google.devtools.ksp"` +3. In `dependencies` block add to the required target/compile the dependency `dev.inmo:micro_utils.koin.generator:$microutils_version`: + ```groovy + dependencies { + add("kspCommonMainMetadata", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in commonMain of your multiplatform module + add("kspJvm", "dev.inmo:micro_utils.koin.generator:$microutils_version") // will work in main of your JVM module + } + + ksp { // this generator do not require any arguments and we should left `ksp` empty + } + ``` diff --git a/koin/generator/src/main/kotlin/Processor.kt b/koin/generator/src/main/kotlin/Processor.kt index bfe6f0336b1..225db7794e0 100644 --- a/koin/generator/src/main/kotlin/Processor.kt +++ b/koin/generator/src/main/kotlin/Processor.kt @@ -80,6 +80,11 @@ class Processor( it.name, targetType, ).apply { + addKdoc( + """ + @return Definition by key "${it.name}" + """.trimIndent() + ) getter( FunSpec.getterBuilder().apply { addCode( @@ -102,6 +107,11 @@ class Processor( if (it.generateSingle) { addFunction( FunSpec.builder("${it.name}Single").apply { + addKdoc( + """ + Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}" + """.trimIndent() + ) receiver(Module::class) addParameter( ParameterSpec.builder( @@ -128,6 +138,11 @@ class Processor( if (it.generateFactory) { addFunction( FunSpec.builder("${it.name}Factory").apply { + addKdoc( + """ + Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}" + """.trimIndent() + ) receiver(Module::class) addParameter( ParameterSpec.builder( diff --git a/koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt b/koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt index 52f94b499db..811fada75fa 100644 --- a/koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt +++ b/koin/generator/test/src/commonMain/kotlin/GeneratedDefinitionsTest.kt @@ -12,15 +12,27 @@ import org.koin.core.module.Module import org.koin.core.qualifier.named import org.koin.core.scope.Scope +/** + * @return Definition by key "sampleInfo" + */ public val Scope.sampleInfo: Test get() = get(named("sampleInfo")) +/** + * @return Definition by key "sampleInfo" + */ public val Koin.sampleInfo: Test get() = get(named("sampleInfo")) +/** + * Will register [definition] with [org.koin.core.module.Module.single] and key "sampleInfo" + */ public fun Module.sampleInfoSingle(createdAtStart: Boolean = false, definition: Definition>): KoinDefinition> = single(named("sampleInfo"), createdAtStart = createdAtStart, definition = definition) +/** + * Will register [definition] with [org.koin.core.module.Module.factory] and key "sampleInfo" + */ public fun Module.sampleInfoFactory(definition: Definition>): KoinDefinition> = factory(named("sampleInfo"), definition = definition) diff --git a/koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt b/koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt index f4035f1d68b..4a4b25cc648 100644 --- a/koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt +++ b/koin/src/commonMain/kotlin/annotations/GenerateKoinDefinition.kt @@ -2,6 +2,20 @@ package dev.inmo.micro_utils.koin.annotations import kotlin.reflect.KClass +/** + * Use this annotation to mark files near to which generator should place generated extensions for koin [org.koin.core.scope.Scope] + * and [org.koin.core.Koin] + * + * @param name Name for definitions. This name will be available as extension for [org.koin.core.scope.Scope] and [org.koin.core.Koin] + * @param type Type of extensions. It is base star-typed class + * @param typeArgs Generic types for [type]. For example, if [type] == `Something::class` and [typeArgs] == `G1::class, + * G2::class`, the result type will be `Something` + * @param nullable In case when true, extension will not throw error when definition has not been registered in koin + * @param generateSingle Generate definition factory with [org.koin.core.module.Module.single]. You will be able to use + * the extension [org.koin.core.module.Module].[name]Single(createdAtStart/* default false */) { /* your definition */ } + * @param generateFactory Generate definition factory with [org.koin.core.module.Module.factory]. You will be able to use + * the extension [org.koin.core.module.Module].[name]Factory { /* your definition */ } + */ @Target(AnnotationTarget.FILE) @Repeatable annotation class GenerateKoinDefinition( From 28a804d98894a69ee2c904591e532735010b277c Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 22 Feb 2023 10:28:25 +0600 Subject: [PATCH 5/5] improve start launcher plugin logs --- .../src/commonMain/kotlin/StartLauncherPlugin.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/startup/launcher/src/commonMain/kotlin/StartLauncherPlugin.kt b/startup/launcher/src/commonMain/kotlin/StartLauncherPlugin.kt index 6d7289da2bd..e9e14facee2 100644 --- a/startup/launcher/src/commonMain/kotlin/StartLauncherPlugin.kt +++ b/startup/launcher/src/commonMain/kotlin/StartLauncherPlugin.kt @@ -39,14 +39,18 @@ object StartLauncherPlugin : StartPlugin { includes( config.plugins.mapNotNull { + val pluginName = it::class.simpleName ?: it.toString() runCatching { + logger.i { "Start koin module registration for $pluginName" } module { with(it) { setupDI(rawJsonObject) } } }.onFailure { e -> - logger.w("Unable to load DI part of $it", e) + logger.w("Unable to register koin module of $pluginName", e) + }.onSuccess { + logger.i("Successfully registered koin module of $pluginName") }.getOrNull() } ) @@ -76,16 +80,17 @@ object StartLauncherPlugin : StartPlugin { logger.i("Start starting of subplugins") val scope = koin.get() koin.get().plugins.map { plugin -> + val pluginName = plugin::class.simpleName ?: plugin.toString() scope.launch { runCatchingSafely { - logger.i("Start loading of $plugin") + logger.i("Start loading of $pluginName") with(plugin) { startPlugin(koin) } }.onFailure { e -> - logger.w("Unable to start plugin $plugin", e) + logger.w("Unable to start plugin $pluginName", e) }.onSuccess { - logger.i("Complete loading of $plugin") + logger.i("Complete loading of $pluginName") } } }.joinAll()