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",