diff --git a/annotation/.gitignore b/annotation/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/annotation/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/annotation/build.gradle b/annotation/build.gradle new file mode 100644 index 0000000..cc4bf1a --- /dev/null +++ b/annotation/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java-library' + id 'kotlin' +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +sourceCompatibility = "8" +targetCompatibility = "8" \ No newline at end of file diff --git a/annotation/src/main/java/org/fnives/library/reloadable/module/annotation/ReloadableModule.kt b/annotation/src/main/java/org/fnives/library/reloadable/module/annotation/ReloadableModule.kt new file mode 100644 index 0000000..3d5603b --- /dev/null +++ b/annotation/src/main/java/org/fnives/library/reloadable/module/annotation/ReloadableModule.kt @@ -0,0 +1,19 @@ +package org.fnives.library.reloadable.module.annotation + +/** + * Annotate your custom Annotation with this to apply the annotation processor. + * + * The annotation processor will generate 2 classes for you. + * + * First will be an interface ReloadModule with only one method, reload. + * This ReloadModule can be injected anywhere where you want to reload the module. + * + * Second will be a class ReloadModuleImpl which is the actual Module implementation for Hilt. + * This provides every class which constructor you annotate with YourAnnotation. + * + * Reload in this context means, every instance will be cleared and the next time Hilt accesses it, a new will be created. + * This newly created instance is reused until the next reload call. + */ +@Target(AnnotationTarget.ANNOTATION_CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class ReloadableModule \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5f1384b..c38fd43 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = "1.5.21" + ext.hilt_version = "2.38.1" repositories { google() mavenCentral() diff --git a/processor/.gitignore b/processor/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/processor/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/processor/build.gradle b/processor/build.gradle new file mode 100644 index 0000000..d98af28 --- /dev/null +++ b/processor/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'java-library' +apply plugin: 'kotlin' + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +test { + useJUnitPlatform() + testLogging { + events 'started', 'passed', 'skipped', 'failed' + exceptionFormat "full" + showStandardStreams true + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "com.squareup:javapoet:1.13.0" + implementation project(":annotation") + implementation "com.google.dagger:hilt-core:$hilt_version" + + testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.0" + testImplementation "org.junit.jupiter:junit-jupiter-engine:5.7.0" + testImplementation "com.github.tschuchortdev:kotlin-compile-testing:1.4.2" + testRuntimeOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version") + testRuntimeOnly("org.jetbrains.kotlin:kotlin-annotation-processing-embeddable:$kotlin_version") +} + +sourceCompatibility = "8" +targetCompatibility = "8" \ No newline at end of file diff --git a/processor/src/main/java/org/fnives/library/reloadable/module/processor/GenerateReloadModule.kt b/processor/src/main/java/org/fnives/library/reloadable/module/processor/GenerateReloadModule.kt new file mode 100644 index 0000000..7e429fd --- /dev/null +++ b/processor/src/main/java/org/fnives/library/reloadable/module/processor/GenerateReloadModule.kt @@ -0,0 +1,201 @@ +package org.fnives.library.reloadable.module.processor + +import com.squareup.javapoet.AnnotationSpec +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.CodeBlock +import com.squareup.javapoet.FieldSpec +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.ParameterSpec +import com.squareup.javapoet.ParameterizedTypeName +import com.squareup.javapoet.TypeName +import com.squareup.javapoet.TypeSpec +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.fnives.library.reloadable.module.processor.GenerateReloadModuleUseCase.Companion.getModuleUseCaseName +import javax.annotation.processing.Filer +import javax.annotation.processing.RoundEnvironment +import javax.inject.Provider +import javax.lang.model.element.ExecutableElement +import javax.lang.model.element.Modifier +import javax.lang.model.element.TypeElement +import javax.lang.model.util.Elements + +class GenerateReloadModule(private val elementUtils: Elements, private val filer: Filer) { + + fun createAndWrite( + typeElement: TypeElement, + roundEnvironment: RoundEnvironment + ) { + val annotatedElements = roundEnvironment.getElementsAnnotatedWith(typeElement) + .filterIsInstance() + val annotatedTypes = annotatedElements.map { it.enclosingElement as TypeElement } + + val reloadFunctionSpec = createReloadFunctionSpec(annotatedTypes) + val cachedProperties = annotatedTypes.map(::getPropertySpec) + val provideMethods = annotatedElements.mapIndexed { index, it -> + createProvideMethodForDependency(it, annotatedTypes[index]) + } + val typeSpec = createTypeSpec(typeElement, reloadFunctionSpec, provideMethods, cachedProperties) + + val packageName = elementUtils.getPackageOf(typeElement).toString() + writeToSourceFile(typeSpec, packageName, filer) + } + + private fun writeToSourceFile(typeSpec: TypeSpec, packageName: String, filer: Filer) { + JavaFile.builder(packageName, typeSpec).build().writeTo(filer) + } + + private fun getPropertySpec(typeElement: TypeElement): FieldSpec = + FieldSpec.builder(TypeName.get(typeElement.asType()), typeElement.cachedPropertyName()) + .initializer("null") + .addModifiers(Modifier.PRIVATE) + .build() + + private fun createProvideMethodForDependency( + constructorElement: ExecutableElement, + dependencyType: TypeElement + ): MethodSpec { + val constructorParameters = constructorElement.parameters.map { "${it.simpleName}.get()" }.joinToString(", ") + val functionElement = MethodSpec.methodBuilder("provide${dependencyType.simpleName}") + .addAnnotation(Provides::class.java) + .returns(TypeName.get(dependencyType.asType())) + .addModifiers(Modifier.PUBLIC) + .addCode( + CodeBlock.of( + "if (${dependencyType.cachedPropertyName()} == null) {\n" + + " ${dependencyType.cachedPropertyName()} = new ${"$"}T ($constructorParameters);\n" + + "}\n" + + "return ${dependencyType.cachedPropertyName()};", + dependencyType + ) + ) + + return constructorElement.parameters.fold(functionElement) { methodSpecBuilder, variableElement -> + val className = TypeName.get(variableElement.asType()) + val providerTypeSpec = ParameterizedTypeName.get(ClassName.get(Provider::class.java), className) + + methodSpecBuilder.addParameter( + ParameterSpec.builder(providerTypeSpec, variableElement.simpleName.toString()) + .addAnnotations(variableElement.annotationMirrors.map { AnnotationSpec.get(it) }) + .build() + ) + }.build() + } + + + private fun createProvideMethodForReloadModuleUseCase(typeElement: TypeElement): MethodSpec = + MethodSpec.methodBuilder("provide${getModuleUseCaseName(typeElement)}") + .addAnnotation(Provides::class.java) + .addModifiers(Modifier.PUBLIC) + .returns(GenerateReloadModuleUseCase.getTypeName(typeElement, elementUtils)) + .addCode("return this;") + .build() + + private fun createTypeSpec(typeElement: TypeElement, reloadFunctionSpec: MethodSpec, provideMethods: List, cacheProperties: List) = + TypeSpec.classBuilder(getModuleUseCaseName(typeElement) + "Impl") + .addModifiers(Modifier.PUBLIC) + .addSuperinterface(GenerateReloadModuleUseCase.getTypeName(typeElement, elementUtils)) + .addAnnotation(Module::class.java) + .addAnnotation( + AnnotationSpec.builder(InstallIn::class.java) + .addMember("value", "\$T.class", SingletonComponent::class.java) + .build() + ) + .addFields(cacheProperties) + .addMethod(createProvideMethodForReloadModuleUseCase(typeElement)) + .addMethods(provideMethods) + .addMethod(reloadFunctionSpec) + .build() + + private fun createReloadFunctionSpec(annotatedElements: List): MethodSpec { + val reloadFunctionSpec = MethodSpec.methodBuilder(GenerateReloadModuleUseCase.getReloadMethodName()) + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC) + return annotatedElements.fold(reloadFunctionSpec) { methodSpecBuilder, typeElement -> + methodSpecBuilder.addCode("${typeElement.cachedPropertyName()} = null;") + }.build() + } + +// private fun createImplementation(interfaceTypeSpec: TypeSpec, typeElement: TypeElement, roundEnvironment: RoundEnvironment): TypeSpec { +// val annotatedElements = roundEnvironment.getElementsAnnotatedWith(typeElement) +// .filterIsInstance() +// +// val reloadFunctionSpec = MethodSpec.methodBuilder("reload") +// .addAnnotation(Override::class.java) +// .addModifiers(Modifier.PUBLIC) +// val moduleImplName = "Reloadable${typeElement.simpleName}Impl" +// val typeSpecBuilder = TypeSpec.classBuilder(moduleImplName) +// .addModifiers(Modifier.PUBLIC) +// .addSuperinterface(interfaceTypeSpec.asTypeName(typeElement)) +// .addAnnotation(Module::class.java) +// .addAnnotation( +// AnnotationSpec.builder(InstallIn::class.java) +// .addMember("value", "\$T.class", SingletonComponent::class.java) +// .build() +// ) +// .addMethod( +// MethodSpec.methodBuilder("provideReloadable${typeElement.simpleName}") +// .addAnnotation(Provides::class.java) +// .addModifiers(Modifier.PUBLIC) +// .returns(interfaceTypeSpec.asTypeName(typeElement)) +// .addCode("return this;") +// .build() +// ) +// +// annotatedElements.forEach { constructorElement -> +// val annotatedType = constructorElement.enclosingElement as TypeElement +// val annotatedTypeClassName = ClassName.bestGuess(annotatedType.qualifiedName.toString()) +// val memberElement = FieldSpec.builder( +// annotatedTypeClassName, +// "cached${annotatedType.simpleName}" +// ) +// .initializer("null") +// .addModifiers(Modifier.PRIVATE) +// .build() +// reloadFunctionSpec.addCode("cached${annotatedType.simpleName} = null;") +// val constructorParameters = constructorElement.parameters.map { "${it.simpleName}.get()" }.joinToString(", ") +// val functionElement = MethodSpec.methodBuilder("provide${annotatedType.simpleName}") +// .addAnnotation(Provides::class.java) +// .returns(annotatedTypeClassName) +// .addModifiers(Modifier.PUBLIC) +// .addCode( +// CodeBlock.of( +// """if (cached${annotatedType.simpleName} == null) { +// cached${annotatedType.simpleName} = new ${"$"}T ($constructorParameters); +// } +// return cached${annotatedType.simpleName}; +// """.trim(), +// annotatedType +// ) +// ) +// +// constructorElement.parameters.forEach { variableElement -> +// val className = TypeName.get(variableElement.asType()) +// val providerTypeSpec = ParameterizedTypeName.get(ClassName.get(Provider::class.java), className) +// functionElement.addParameter( +// ParameterSpec.builder(providerTypeSpec, variableElement.simpleName.toString()) +// .addAnnotations(variableElement.annotationMirrors.map { AnnotationSpec.get(it) }) +// .build() +// ) +// } +// +// typeSpecBuilder.addField(memberElement) +// typeSpecBuilder.addMethod(functionElement.build()) +// } +// +// +// typeSpecBuilder.addMethod(reloadFunctionSpec.build()) +// +// return typeSpecBuilder.build() +// } +// +// private fun TypeSpec.asTypeName(typeElement: TypeElement) = +// ClassName.bestGuess(elementUtils.getPackageOf(typeElement).toString() + "." + name.orEmpty()) + + companion object { + private fun TypeElement.cachedPropertyName() = "cached${simpleName}" + } +} \ No newline at end of file diff --git a/processor/src/main/java/org/fnives/library/reloadable/module/processor/GenerateReloadModuleUseCase.kt b/processor/src/main/java/org/fnives/library/reloadable/module/processor/GenerateReloadModuleUseCase.kt new file mode 100644 index 0000000..970ac62 --- /dev/null +++ b/processor/src/main/java/org/fnives/library/reloadable/module/processor/GenerateReloadModuleUseCase.kt @@ -0,0 +1,43 @@ +package org.fnives.library.reloadable.module.processor + +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.TypeSpec +import javax.annotation.processing.Filer +import javax.lang.model.element.Modifier +import javax.lang.model.element.TypeElement +import javax.lang.model.util.Elements + +class GenerateReloadModuleUseCase(private val elementUtils: Elements, private val filer: Filer) { + + fun createAndWrite(typeElement: TypeElement) { + val typeSpec = createTypeSpec(getModuleUseCaseName(typeElement)) + val packageName = elementUtils.getPackageOf(typeElement).toString() + writeToSourceFile(typeSpec, packageName, filer) + } + + private fun createTypeSpec(moduleUseCaseName: String) = + TypeSpec.interfaceBuilder(moduleUseCaseName) + .addModifiers(Modifier.PUBLIC) + .addMethod( + MethodSpec.methodBuilder(getReloadMethodName()) + .addModifiers(Modifier.ABSTRACT) + .addModifiers(Modifier.PUBLIC) + .build() + ) + .build() + + private fun writeToSourceFile(typeSpec: TypeSpec, packageName: String, filer: Filer) { + JavaFile.builder(packageName, typeSpec).build().writeTo(filer) + } + + companion object { + fun getModuleUseCaseName(typeElement: TypeElement) = "Reload${typeElement.simpleName}Module" + + fun getReloadMethodName() = "reload" + + fun getTypeName(typeElement: TypeElement, elementUtils: Elements) = + ClassName.bestGuess("${elementUtils.getPackageOf(typeElement)}.${getModuleUseCaseName(typeElement)}") + } +} \ No newline at end of file diff --git a/processor/src/main/java/org/fnives/library/reloadable/module/processor/ReloadableModuleProcessor.kt b/processor/src/main/java/org/fnives/library/reloadable/module/processor/ReloadableModuleProcessor.kt new file mode 100644 index 0000000..d9f5d43 --- /dev/null +++ b/processor/src/main/java/org/fnives/library/reloadable/module/processor/ReloadableModuleProcessor.kt @@ -0,0 +1,52 @@ +package org.fnives.library.reloadable.module.processor + +import org.fnives.library.reloadable.module.annotation.ReloadableModule +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.Filer +import javax.annotation.processing.Messager +import javax.annotation.processing.ProcessingEnvironment +import javax.annotation.processing.RoundEnvironment +import javax.lang.model.SourceVersion +import javax.lang.model.element.TypeElement +import javax.lang.model.util.Elements +import javax.lang.model.util.Types + +class ReloadableModuleProcessor : AbstractProcessor() { + + private lateinit var filer: Filer + private lateinit var messager: Messager + private lateinit var elementUtils: Elements + private lateinit var typeUtils: Types + private lateinit var generateReloadModuleUseCase: GenerateReloadModuleUseCase + private lateinit var generateReloadModule: GenerateReloadModule + private val supportedAnnotations = setOf(ReloadableModule::class.java) + + @Synchronized + override fun init(processingEnvironment: ProcessingEnvironment) { + super.init(processingEnvironment) + filer = processingEnvironment.filer + messager = processingEnvironment.messager + elementUtils = processingEnvironment.elementUtils + typeUtils = processingEnvironment.typeUtils + generateReloadModuleUseCase = GenerateReloadModuleUseCase(elementUtils, filer) + generateReloadModule = GenerateReloadModule(elementUtils, filer) + } + + override fun getSupportedAnnotationTypes(): Set = + supportedAnnotations.map(Class<*>::getCanonicalName).toSet() + + override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported() + + override fun process(set: Set, roundEnvironment: RoundEnvironment): Boolean { + if (roundEnvironment.processingOver()) return false + val annotatedElements = supportedAnnotations.flatMap(roundEnvironment::getElementsAnnotatedWith).filterIsInstance() + + annotatedElements.forEach { + generateReloadModuleUseCase.createAndWrite(it) + generateReloadModule.createAndWrite(it, roundEnvironment) + } + + return false + } + +} \ No newline at end of file diff --git a/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors new file mode 100644 index 0000000..3aca719 --- /dev/null +++ b/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -0,0 +1 @@ +org.fnives.library.reloadable.module.processor.ReloadableModuleProcessor,isolating \ No newline at end of file diff --git a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..9d4f481 --- /dev/null +++ b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.fnives.library.reloadable.module.processor.ReloadableModuleProcessor \ No newline at end of file diff --git a/processor/src/test/java/org/fnives/library/reloadable/module/processor/ResourceFileUtils.kt b/processor/src/test/java/org/fnives/library/reloadable/module/processor/ResourceFileUtils.kt new file mode 100644 index 0000000..5fe6295 --- /dev/null +++ b/processor/src/test/java/org/fnives/library/reloadable/module/processor/ResourceFileUtils.kt @@ -0,0 +1,12 @@ +package org.fnives.library.reloadable.module.processor + +import java.io.File +import java.nio.file.Paths + +/** ++ * Helper class which read the file in the resources folder with the given [fileName] into a string, each line separated with [lineDelimiter]. ++ */ +fun Any.readResourceFileToString(fileName: String): String { + val path = this::class.java.classLoader.getResource(fileName).toURI().path + return File(Paths.get(path).toUri()).readText() +} diff --git a/processor/src/test/java/org/fnives/library/reloadable/module/processor/TestReloadableModuleProcessor.kt b/processor/src/test/java/org/fnives/library/reloadable/module/processor/TestReloadableModuleProcessor.kt new file mode 100644 index 0000000..f33648a --- /dev/null +++ b/processor/src/test/java/org/fnives/library/reloadable/module/processor/TestReloadableModuleProcessor.kt @@ -0,0 +1,50 @@ +package org.fnives.library.reloadable.module.processor + +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class TestReloadableModuleProcessor { + + @Test + fun testSimpleSetup() { + val userModuleInput = readResourceFileToString("simple/input/UserModuleInject.kt") + val providedByUserModule = readResourceFileToString("simple/input/ProvidedByUserModule.kt") + val fooDependency = readResourceFileToString("simple/input/FooDependency.kt") + + val result = KotlinCompilation().apply { + sources = listOf( + SourceFile.kotlin("UserModule.kt", userModuleInput), + SourceFile.kotlin("ProvidedByUserModule.kt", providedByUserModule), + SourceFile.kotlin("FooDependency.kt", fooDependency) + ) + + annotationProcessors = listOf(ReloadableModuleProcessor()) + + inheritClassPath = true + messageOutputStream = System.out // see diagnostics in real time + }.compile() + + val generatedFiles = result.generatedFiles.toList() + val reloadableUserModule = generatedFiles[0].readText() + val reloadableUserModuleImpl = generatedFiles[1].readText() + AssertionsAssertEqualsIgnoringMultipleSpaces( + readResourceFileToString("simple/expected/ReloadUserModuleInjectModule.java"), + reloadableUserModule + ) + AssertionsAssertEqualsIgnoringMultipleSpaces( + readResourceFileToString("simple/expected/ReloadUserModuleInjectModuleImpl.java"), + reloadableUserModuleImpl + ) + Assertions.assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + } + + companion object { + private fun String.replaceSpacesWithOnlyOne(): String = replace(" *".toRegex(), " ") + + @Suppress("TestFunctionName") + private fun AssertionsAssertEqualsIgnoringMultipleSpaces(expected: String, actual: String) = + Assertions.assertEquals(expected.replaceSpacesWithOnlyOne(), actual.replaceSpacesWithOnlyOne()) + } +} diff --git a/processor/src/test/resources/simple/expected/ReloadUserModuleInjectModule.java b/processor/src/test/resources/simple/expected/ReloadUserModuleInjectModule.java new file mode 100644 index 0000000..86a07b2 --- /dev/null +++ b/processor/src/test/resources/simple/expected/ReloadUserModuleInjectModule.java @@ -0,0 +1,5 @@ +package simple.input; + +public interface ReloadUserModuleInjectModule { + void reload(); +} diff --git a/processor/src/test/resources/simple/expected/ReloadUserModuleInjectModuleImpl.java b/processor/src/test/resources/simple/expected/ReloadUserModuleInjectModuleImpl.java new file mode 100644 index 0000000..072127d --- /dev/null +++ b/processor/src/test/resources/simple/expected/ReloadUserModuleInjectModuleImpl.java @@ -0,0 +1,34 @@ +package simple.input; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import java.lang.Override; +import javax.inject.Provider; +import org.jetbrains.annotations.NotNull; + +@Module +@InstallIn(SingletonComponent.class) +public class ReloadUserModuleInjectModuleImpl implements ReloadUserModuleInjectModule { + private ProvidedByUserModule cachedProvidedByUserModule = null; + + @Provides + public ReloadUserModuleInjectModule provideReloadUserModuleInjectModule() { + return this; + } + + @Provides + public ProvidedByUserModule provideProvidedByUserModule( + @NotNull Provider fooDependency) { + if (cachedProvidedByUserModule == null) { + cachedProvidedByUserModule = new ProvidedByUserModule (fooDependency.get()); + } + return cachedProvidedByUserModule; + } + + @Override + public void reload() { + cachedProvidedByUserModule = null; + } +} diff --git a/processor/src/test/resources/simple/input/FooDependency.kt b/processor/src/test/resources/simple/input/FooDependency.kt new file mode 100644 index 0000000..4ce79bf --- /dev/null +++ b/processor/src/test/resources/simple/input/FooDependency.kt @@ -0,0 +1,5 @@ +package simple.input + +import javax.inject.Inject + +class FooDependency @Inject constructor() \ No newline at end of file diff --git a/processor/src/test/resources/simple/input/ProvidedByUserModule.kt b/processor/src/test/resources/simple/input/ProvidedByUserModule.kt new file mode 100644 index 0000000..efc1671 --- /dev/null +++ b/processor/src/test/resources/simple/input/ProvidedByUserModule.kt @@ -0,0 +1,5 @@ +package simple.input + +import org.jetbrains.annotations.NotNull + +class ProvidedByUserModule @UserModuleInject constructor(@NotNull fooDependency: FooDependency) \ No newline at end of file diff --git a/processor/src/test/resources/simple/input/UserModuleInject.kt b/processor/src/test/resources/simple/input/UserModuleInject.kt new file mode 100644 index 0000000..bd9b4de --- /dev/null +++ b/processor/src/test/resources/simple/input/UserModuleInject.kt @@ -0,0 +1,6 @@ +package simple.input + +import org.fnives.library.reloadable.module.annotation.ReloadableModule + +@ReloadableModule +annotation class UserModuleInject \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 6893806..686accb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,3 +8,5 @@ dependencyResolutionManagement { } rootProject.name = "HiltReloadableModule" include ':app' +include ':processor' +include ':annotation'