Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multimodule scan for definitions #135

Merged
merged 2 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/android-coffee-maker/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ dependencies {

ksp {
arg("KOIN_CONFIG_CHECK","true")
arg("KOIN_DEFAULT_MODULE","false")
// arg("KOIN_DEFAULT_MODULE","false")
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import org.koin.ksp.generated.module
import org.koin.sample.android.library.CommonRepository
import org.koin.sample.android.library.MyScope
import org.koin.sample.androidx.app.ScopedStuff
import org.koin.sample.androidx.data.DataConsumer
import org.koin.sample.androidx.data.MyDataConsumer
import org.koin.sample.androidx.di.AppModule
import org.koin.sample.androidx.di.DataModule
import org.koin.sample.androidx.repository.RepositoryModule
Expand All @@ -31,6 +33,9 @@ class AndroidModuleTest {
val scope = koin.createScope<MyScope>()
scope.get<ScopedStuff>()

assert(koin.getOrNull<DataConsumer>() != null)
assert(koin.getOrNull<MyDataConsumer>() != null)

stopKoin()
}
}
1 change: 1 addition & 0 deletions examples/android-library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ dependencies {

ksp {
arg("KOIN_CONFIG_CHECK","true")
// arg("KOIN_DEFAULT_MODULE","false")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.koin.sample.androidx.data

import org.koin.core.annotation.Factory

@Factory
class DataConsumer

class MyDataConsumer(val dc : DataConsumer)

@Factory
fun funDataConsumer(dc : DataConsumer) = MyDataConsumer(dc)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.koin.example.coffee

import org.koin.core.annotation.Single

@Single
class MyDetachCoffeeComponent
1 change: 1 addition & 0 deletions examples/coffee-maker/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation(project(":coffee-maker-module"))

testImplementation(libs.koin.test)
testImplementation(libs.junit)
}

ksp {
Expand Down
3 changes: 3 additions & 0 deletions examples/coffee-maker/src/test/java/CoffeeAppTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.koin.core.qualifier.StringQualifier
import org.koin.core.qualifier.named
import org.koin.example.CoffeeApp
import org.koin.example.coffee.CoffeePumpList
import org.koin.example.coffee.MyDetachCoffeeComponent
import org.koin.example.coffee.pump.PumpCounter
import org.koin.example.di.CoffeeAppModule
import org.koin.example.di.CoffeeTesterModule
Expand Down Expand Up @@ -81,6 +82,8 @@ class CoffeeAppTest {
assert(koin.get<CoffeePumpList>().list.size == 2)
assert(koin.get<PumpCounter>().count == 2)

assert(koin.getOrNull<MyDetachCoffeeComponent>() != null)

stopKoin()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,12 @@ annotation class Module(val includes: Array<KClass<*>> = [], val createdAtStart:
* @param value: package to scan
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
annotation class ComponentScan(val value: String = "")
annotation class ComponentScan(val value: String = "")

/**
*
*
* @param value: package of declared definition
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
annotation class Definition(val value: String = "")
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package org.koin.compiler

import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import org.koin.compiler.KspOptions.KOIN_CONFIG_CHECK
import org.koin.compiler.KspOptions.*
import org.koin.compiler.generator.KoinGenerator
import org.koin.compiler.metadata.KoinMetaData
import org.koin.compiler.scanner.KoinMetaDataScanner
Expand All @@ -36,7 +36,6 @@ class BuilderProcessor(
override fun process(resolver: Resolver): List<KSAnnotated> {
logger.logging("Scanning symbols ...")

//TODO Handle allowDefaultModule option

val invalidSymbols = koinMetaDataScanner.scanSymbols(resolver)
if (invalidSymbols.isNotEmpty()) {
Expand All @@ -53,17 +52,8 @@ class BuilderProcessor(
logger.logging("Scan metadata ...")
val moduleList = koinMetaDataScanner.scanKoinModules(defaultModule)

if (isDefaultModuleDisabled()){
if (defaultModule.definitions.isNotEmpty()){
logger.error("Default module is disabled!")
defaultModule.definitions.forEach { def ->
logger.error("definition '${def.packageName}.${def.label}' needs to be defined in a module")
}
}
}

logger.logging("Generate code ...")
koinCodeGenerator.generateModules(moduleList, defaultModule)
koinCodeGenerator.generateModules(moduleList, defaultModule, isDefaultModuleActive())

if (isConfigCheckActive()) {
logger.warn("[Experimental] Koin Configuration Check")
Expand All @@ -74,11 +64,12 @@ class BuilderProcessor(
}

private fun isConfigCheckActive(): Boolean {
return options[KOIN_CONFIG_CHECK.name] == true.toString()
return options.getOrDefault(KOIN_CONFIG_CHECK.name, "true") == true.toString()
}

private fun isDefaultModuleDisabled(): Boolean {
return options[KspOptions.KOIN_DEFAULT_MODULE.name] == false.toString()
//TODO turn KOIN_DEFAULT_MODULE to false by default - Next Major version (breaking)
private fun isDefaultModuleActive(): Boolean {
return options.getOrDefault(KOIN_DEFAULT_MODULE.name, "true") == true.toString()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
import com.google.devtools.ksp.symbol.KSDeclaration
import org.koin.compiler.generator.KoinGenerator.Companion.LOGGER
import org.koin.compiler.metadata.KoinMetaData
import org.koin.compiler.metadata.KoinMetaData.Module.Companion.DEFINE_PREFIX
import org.koin.compiler.metadata.SINGLE
import org.koin.compiler.scanner.filterForbiddenKeywords
import java.io.OutputStream

const val NEW_LINE = "\n\t"

fun OutputStream.generateDefinition(def: KoinMetaData.Definition, label: () -> String) {

fun OutputStream.generateDefinition(def: KoinMetaData.Definition, isExternalDefinition: Boolean = false, label: () -> String) {
LOGGER.logging("generate ${def.label} - $def")
val param = def.parameters.generateParamFunction()
val ctor = generateConstructor(def.parameters)
Expand All @@ -32,9 +34,41 @@ fun OutputStream.generateDefinition(def: KoinMetaData.Definition, label: () -> S
if (qualifier == "") CREATED_AT_START else ",$CREATED_AT_START"
} else ""
val space = if (def.isScoped()) NEW_LINE + "\t" else NEW_LINE

if (isExternalDefinition) {
writeExternalDefinitionFunction(def, qualifier, createAtStart, param, label, ctor, binds)
}
else {
writeDefinition(space, def, qualifier, createAtStart, param, label, ctor, binds)
}
}

private fun OutputStream.writeDefinition(
space: String,
def: KoinMetaData.Definition,
qualifier: String,
createAtStart: String,
param: String,
label: () -> String,
ctor: String,
binds: String
) {
appendText("$space${def.keyword.keyword}($qualifier$createAtStart) { ${param}${label()}$ctor } $binds")
}

private fun OutputStream.writeExternalDefinitionFunction(
def: KoinMetaData.Definition,
qualifier: String,
createAtStart: String,
param: String,
label: () -> String,
ctor: String,
binds: String
) {
appendText("\n@Definition(\"${def.packageName}\")\n")
appendText("fun Module.$DEFINE_PREFIX${def.label}() = ${def.keyword.keyword}($qualifier$createAtStart) { ${param}${label()}$ctor } $binds")
}

fun OutputStream.generateModuleFunctionDeclarationDefinition(def: KoinMetaData.Definition.FunctionDefinition) {
generateDefinition(def) { "moduleInstance.${def.functionName}" }
}
Expand All @@ -43,15 +77,26 @@ fun OutputStream.generateObjectModuleFunctionDeclarationDefinition(
def: KoinMetaData.Definition.FunctionDefinition,
modulePath: String
) {
generateDefinition(def) { "$modulePath.${def.functionName}" }
generateDefinition(def ) { "$modulePath.${def.functionName}" }
}

fun OutputStream.generateFunctionDeclarationDefinition(def: KoinMetaData.Definition.FunctionDefinition,isExternalDefinition: Boolean = false) {
generateDefinition(def,isExternalDefinition) { "${def.packageNamePrefix}${def.functionName}" }
}

fun OutputStream.generateClassDeclarationDefinition(
def: KoinMetaData.Definition.ClassDefinition,
isExternalDefinition: Boolean = false
) {
generateDefinition(def,isExternalDefinition) { "${def.packageNamePrefix}${def.className}" }
}

fun OutputStream.generateFunctionDeclarationDefinition(def: KoinMetaData.Definition.FunctionDefinition) {
generateDefinition(def) { "${def.packageNamePrefix}${def.functionName}" }
internal fun OutputStream.generateExternalDefinitionCalls(module: KoinMetaData.Module) {
generateExternalDefinitionCalls(module.externalDefinitions)
}

fun OutputStream.generateClassDeclarationDefinition(def: KoinMetaData.Definition.ClassDefinition) {
generateDefinition(def) { "${def.packageNamePrefix}${def.className}" }
internal fun OutputStream.generateExternalDefinitionCalls(externalDefinitions : List<KoinMetaData.ExternalDefinition>) {
this.appendText("${NEW_LINE}${externalDefinitions.joinToString(separator = "\n${NEW_LINE}") { "${it.name}()" }}")
}

const val CREATED_AT_START = "createdAtStart=true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import generateClassModule
import generateDefaultModuleFooter
import generateDefaultModuleFunction
import generateDefaultModuleHeader
import generateExternalDefinitionCalls
import generateFieldDefaultModule
import org.koin.compiler.metadata.KoinMetaData
import java.io.OutputStream
Expand All @@ -36,35 +38,51 @@ class KoinGenerator(

fun generateModules(
moduleList: List<KoinMetaData.Module>,
defaultModule: KoinMetaData.Module
defaultModule: KoinMetaData.Module,
generateDefaultModule : Boolean
) {
logger.logging("generate ${moduleList.size} modules ...")
moduleList.forEach { generateModule(it) }

if (defaultModule.definitions.isNotEmpty()) {
logger.logging("generate default module ...")
val defaultModuleFile = codeGenerator.getFile(fileName = "Default")
defaultModuleFile.generateDefaultModuleHeader(defaultModule.definitions)
generateModule(defaultModule, defaultModuleFile)
defaultModuleFile.generateDefaultModuleFooter()
defaultModuleFile.close()
generateDefaultFile(defaultModule, generateDefaultModule)
}
}

private fun generateModule(module: KoinMetaData.Module, defaultFile: OutputStream? = null) {
logger.logging("generate $module - ${module.type}")
private fun generateDefaultFile(
defaultModule: KoinMetaData.Module,
generateDefaultModule: Boolean
) {
logger.logging("generate default file ...")
val defaultModuleFile = codeGenerator.getFile(fileName = "Default")
defaultModuleFile.generateDefaultModuleHeader(defaultModule.definitions)
generateAllExternalDefinitions(defaultModule, defaultModuleFile)

if (generateDefaultModule) {
generateDefaultModule(defaultModule, defaultModuleFile)
}
defaultModuleFile.close()
}

val isFieldModule = module.type == KoinMetaData.ModuleType.FIELD && module.definitions.isNotEmpty()
if (isFieldModule){
// generate field module
defaultFile!!.generateFieldDefaultModule(module.definitions)
} else {
// generate class module
val moduleFile = codeGenerator.getFile(fileName = module.generateModuleFileName())
generateClassModule(moduleFile, module)
private fun generateAllExternalDefinitions(defaultModule: KoinMetaData.Module, defaultModuleFile: OutputStream) {
defaultModuleFile.generateFieldDefaultModule(defaultModule.definitions, generateExternalDefinitions = true)
}

private fun generateDefaultModule(defaultModule: KoinMetaData.Module, defaultModuleFile: OutputStream) {
with(defaultModuleFile){
generateDefaultModuleFunction()
generateExternalDefinitionCalls(defaultModule.getDefinitionsAsExternals())
generateDefaultModuleFooter()
}
}

private fun generateModule(module: KoinMetaData.Module) {
logger.logging("generate $module - ${module.type}")
// generate class module
val moduleFile = codeGenerator.getFile(fileName = module.generateModuleFileName())
generateClassModule(moduleFile, module)
}

private fun KoinMetaData.Module.generateModuleFileName(): String {
val extensionName = packageName("$")
return "${name}Gen${extensionName}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,31 @@ import org.koin.compiler.generator.*
import org.koin.compiler.metadata.KoinMetaData
import java.io.OutputStream

fun OutputStream.generateFieldDefaultModule(definitions: List<KoinMetaData.Definition>) {
fun OutputStream.generateFieldDefaultModule(definitions: List<KoinMetaData.Definition>, generateExternalDefinitions : Boolean) {
val standardDefinitions = definitions.filter { it.isNotScoped() }.toSet()
val scopeDefinitions = definitions.filter { it.isScoped() }.toSet()

standardDefinitions.forEach { generateDefaultModuleDefinition(it) }
standardDefinitions.forEach { generateDefaultModuleDefinition(it,generateExternalDefinitions) }
//TODO Scope in function?
scopeDefinitions
.groupBy { it.scope }
.forEach { (scope, definitions) ->
appendText(generateScope(scope!!))
definitions.forEach { definition ->
generateDefaultModuleDefinition(definition)
generateDefaultModuleDefinition(definition, generateExternalDefinitions)
}
appendText(generateScopeClosing())
}
}

fun OutputStream.generateDefaultModuleDefinition(definition: KoinMetaData.Definition) {
fun OutputStream.generateDefaultModuleDefinition(
definition: KoinMetaData.Definition,
generateExternalDefinitions: Boolean
) {
if (definition is KoinMetaData.Definition.ClassDefinition){
generateClassDeclarationDefinition(definition)
generateClassDeclarationDefinition(definition, isExternalDefinition = generateExternalDefinitions)
} else if (definition is KoinMetaData.Definition.FunctionDefinition && !definition.isClassFunction) {
generateFunctionDeclarationDefinition(definition)
generateFunctionDeclarationDefinition(definition, isExternalDefinition = generateExternalDefinitions)
}
}

Expand Down Expand Up @@ -67,6 +71,10 @@ fun generateClassModule(classFile: OutputStream, module: KoinMetaData.Module) {
generateDefinitions(module, classFile)
}

if (module.externalDefinitions.isNotEmpty()){
classFile.generateExternalDefinitionCalls(module)
}

classFile.appendText("\n}")
val visibilityString = module.visibility.toSourceString()
classFile.appendText(
Expand Down Expand Up @@ -140,6 +148,10 @@ private fun KoinMetaData.Module.generateModuleField(
fun OutputStream.generateDefaultModuleHeader(definitions: List<KoinMetaData.Definition>) {
appendText(DEFAULT_MODULE_HEADER)
appendText(definitions.generateImports())
}

fun OutputStream.generateDefaultModuleFunction() {
appendText("\n\n")
appendText(DEFAULT_MODULE_FUNCTION)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ val DEFAULT_MODULE_HEADER = """

import org.koin.core.KoinApplication
import org.koin.core.module.Module
import org.koin.core.annotation.Definition
import org.koin.dsl.*

""".trimIndent()
Expand Down
Loading
Loading