Skip to content

Commit

Permalink
only create FragmentFactory and *Subcomponent.Factory bindings once i…
Browse files Browse the repository at this point in the history
…n each classpath (#124)

fixes #123

#124
  • Loading branch information
RBusarow authored Aug 4, 2021
1 parent c29adab commit 873d0a2
Show file tree
Hide file tree
Showing 20 changed files with 334 additions and 106 deletions.
8 changes: 7 additions & 1 deletion tangle-compiler/api/tangle-compiler.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
public final class tangle/inject/compiler/ApplyKt {
public static final fun applyEach (Ljava/lang/Object;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
}

public final class tangle/inject/compiler/ClassNames {
public static final field INSTANCE Ltangle/inject/compiler/ClassNames;
public final fun getAndroidxFragment ()Lcom/squareup/kotlinpoet/ClassName;
Expand Down Expand Up @@ -26,6 +30,7 @@ public final class tangle/inject/compiler/ClassNames {
public final fun getMergeSubomponent ()Lcom/squareup/kotlinpoet/ClassName;
public final fun getModule ()Lcom/squareup/kotlinpoet/ClassName;
public final fun getMultibinds ()Lcom/squareup/kotlinpoet/ClassName;
public final fun getNamed ()Lcom/squareup/kotlinpoet/ClassName;
public final fun getOptIn ()Lcom/squareup/kotlinpoet/ClassName;
public final fun getParcelable ()Lcom/squareup/kotlinpoet/ClassName;
public final fun getProvider ()Lcom/squareup/kotlinpoet/ClassName;
Expand Down Expand Up @@ -149,8 +154,9 @@ public final class tangle/inject/compiler/KotlinPoetKt {
public static final fun AnnotationSpec (Lcom/squareup/kotlinpoet/ClassName;Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/AnnotationSpec;
public static final fun FunSpec (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/FunSpec;
public static final fun addFunction (Lcom/squareup/kotlinpoet/TypeSpec$Builder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/kotlinpoet/TypeSpec$Builder;
public static final fun applyEach (Ljava/lang/Object;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public static final fun buildFile (Lcom/squareup/kotlinpoet/FileSpec$Companion;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/String;
public static final fun generateSimpleNameString (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;)Ljava/lang/String;
public static synthetic fun generateSimpleNameString$default (Lcom/squareup/kotlinpoet/ClassName;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String;
public static final fun qualifierAnnotationSpecs (Ljava/util/List;Lorg/jetbrains/kotlin/descriptors/ModuleDescriptor;)Ljava/util/List;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dagger.internal.Factory
import dagger.internal.InstanceFactory
import dagger.multibindings.*
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider

public object ClassNames {
Expand Down Expand Up @@ -88,6 +89,7 @@ public object ClassNames {
public val mergeSubomponent: ClassName = MergeSubcomponent::class.asClassName()
public val module: ClassName = Module::class.asClassName()
public val multibinds: ClassName = Multibinds::class.asClassName()
public val named: ClassName = Named::class.asClassName()
public val provides: ClassName = Provides::class.asClassName()
public val stringKey: ClassName = StringKey::class.asClassName()
public val subcomponentFactory: ClassName = Subcomponent.Factory::class.asClassName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.io.ByteArrayOutputStream

inline fun <T : Any, E> T.applyEach(elements: Iterable<E>, block: T.(E) -> Unit): T {
elements.forEach { element -> this.block(element) }
return this
}

private fun String.addGeneratedByComment(): String {
return """
// Generated by Tangle
Expand Down Expand Up @@ -153,3 +148,7 @@ internal fun TypeName.withJvmSuppressWildcardsIfNeeded(
else -> this
}
}

fun ClassName.generateSimpleNameString(
separator: String = "_"
): String = simpleNames.joinToString(separator)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tangle.inject.compiler

inline fun <T : Any, E> T.applyEach(elements: Iterable<E>, block: T.(E) -> Unit): T {
elements.forEach { element -> this.block(element) }
return this
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,17 @@ import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFile
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.internal.classesAndInnerClasses
import com.squareup.anvil.compiler.internal.generateClassName
import com.squareup.anvil.compiler.internal.hasAnnotation
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.anvil.compiler.internal.scope
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.KModifier.ABSTRACT
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.resolveClassByFqName
import org.jetbrains.kotlin.incremental.components.NoLookupLocation.FROM_BACKEND
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import tangle.inject.compiler.ClassNames
import tangle.inject.compiler.FqNames
import tangle.inject.compiler.asClassName
import tangle.inject.compiler.buildFile
import tangle.inject.compiler.*
import java.io.File

@Suppress("unused")
Expand All @@ -34,75 +32,86 @@ class TangleFragmentFactoryModuleGenerator : CodeGenerator {
): Collection<GeneratedFile> = projectFiles
.flatMap { it.classesAndInnerClasses(module) }
.filter { it.hasAnnotation(FqNames.mergeComponent, module) }
.map { generateComponent(codeGenDir, module, it) }
// fast path for excluding duplicate binding modules if they're in the same source
.distinctBy { it.scope(FqNames.mergeComponent, module) }
.mapNotNull { generateComponent(codeGenDir, module, it) }

private fun generateComponent(
codeGenDir: File,
module: ModuleDescriptor,
clazz: KtClassOrObject
): GeneratedFile {
val packageName = clazz.containingKtFile.packageFqName.safePackageString()
val className = "${clazz.generateClassName()}Tangle_FragmentFactory_Module"
): GeneratedFile? {
// Every single instance of this generated Module will have the same FqName,
// except for the scope name which is prepended to the interface name.
// This is crucial because we can only ever have one TangleFragmentFactory binding declaration
// per scope, even if they're in different Gradle modules. We use this known constant FqName
// to look for instances of the module within the classpath,
// and skip generation if one already exists.
// Modules for a different scope are okay.
val packageName = "tangle.fragment"

val scope = clazz.scope(FqNames.mergeComponent, module)
val scopeFqName = clazz.scope(FqNames.mergeComponent, module)
val scopeClassName = scopeFqName.asClassName(module)
val scopeClassNameString = scopeClassName.generateSimpleNameString()

val componentClassName = ClassName(packageName, className)
val moduleClassNameString = "${scopeClassNameString}_Tangle_FragmentFactory_Module"
val moduleFqName = FqName("$packageName.$moduleClassNameString")
val moduleClassName = ClassName(packageName, moduleClassNameString)

val content = FileSpec.buildFile(packageName, className) {
TypeSpec.interfaceBuilder(componentClassName)
// If the (Dagger) Module for this scope already exists in a different Gradle module,
// it can't be created again here without creating a duplicate binding
// for the TangleFragmentFactory.
val alreadyCreated = listOf(module)
.plus(module.allDependencyModules)
.any { depMod ->
depMod.resolveClassByFqName(moduleFqName, FROM_BACKEND) != null
}

if (alreadyCreated) {
return null
}

val content = FileSpec.buildFile(packageName, moduleClassNameString) {
TypeSpec.interfaceBuilder(moduleClassName)
.addAnnotation(ClassNames.module)
.addAnnotation(
AnnotationSpec.builder(ClassNames.contributesTo)
.addMember("%T::class", scope.asClassName(module))
.build()
)
.addFunction(
FunSpec.builder("bind${ClassNames.tangleFragmentFactory.simpleName}")
.addAnnotation(ClassNames.binds)
.addModifiers(ABSTRACT)
.addParameter("fragmentFactory", ClassNames.tangleFragmentFactory)
.returns(ClassNames.androidxFragmentFactory)
.build()
)
.addFunction(
FunSpec.builder("bindProviderMap")
.addAnnotation(ClassNames.multibinds)
.addModifiers(ABSTRACT)
.returns(ClassNames.fragmentMap)
.build()
)
.addFunction(
FunSpec.builder("bindTangleProviderMap")
.addAnnotation(ClassNames.multibinds)
.addAnnotation(ClassNames.tangleFragmentProviderMap)
.addModifiers(ABSTRACT)
.returns(ClassNames.fragmentMap)
.addMember("%T::class", scopeFqName.asClassName(module))
.build()
)
.addFunction("bindProviderMap") {
addAnnotation(ClassNames.multibinds)
addModifiers(ABSTRACT)
returns(ClassNames.fragmentMap)
}
.addFunction("bindTangleProviderMap") {
addAnnotation(ClassNames.multibinds)
addAnnotation(ClassNames.tangleFragmentProviderMap)
addModifiers(ABSTRACT)
returns(ClassNames.fragmentMap)
}
.addType(
TypeSpec.companionObjectBuilder()
.addFunction(
FunSpec.builder("provide${ClassNames.tangleFragmentFactory.simpleName}")
.addAnnotation(ClassNames.provides)
.addParameter("providerMap", ClassNames.fragmentProviderMap)
.addParameter(
ParameterSpec.builder("tangleProviderMap", ClassNames.fragmentProviderMap)
.addAnnotation(ClassNames.tangleFragmentProviderMap)
.build()
)
.returns(ClassNames.tangleFragmentFactory)
.addStatement(
"return·%T(providerMap,·tangleProviderMap)",
ClassNames.tangleFragmentFactory
)
.build()
)
.addFunction("provide${ClassNames.tangleFragmentFactory.simpleName}") {
addAnnotation(ClassNames.provides)
addParameter("providerMap", ClassNames.fragmentProviderMap)
addParameter(
ParameterSpec.builder("tangleProviderMap", ClassNames.fragmentProviderMap)
.addAnnotation(ClassNames.tangleFragmentProviderMap)
.build()
)
returns(ClassNames.tangleFragmentFactory)
addStatement(
"return·%T(providerMap,·tangleProviderMap)",
ClassNames.tangleFragmentFactory
)
}
.build()
)
.build()
.let { addType(it) }
}

return createGeneratedFile(codeGenDir, packageName, className, content)
return createGeneratedFile(codeGenDir, packageName, moduleClassNameString, content)
}
}
9 changes: 7 additions & 2 deletions tangle-fragment-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,17 @@ dependencies {
implementation(libs.kotlin.reflect)
implementation(libs.kotlin.compiler)
implementation(libs.square.kotlinPoet)
implementation(projects.tangleApi)
implementation(projects.tangleFragmentApi)

implementation(projects.tangleApi)
implementation(projects.tangleCompiler)

implementation(projects.tangleFragmentApi)
implementation(projects.tangleFragmentCompiler)

implementation(projects.tangleTestUtils)

implementation(projects.tangleViewmodelApi)
implementation(projects.tangleViewmodelCompiler)
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>()
Expand Down
Loading

0 comments on commit 873d0a2

Please sign in to comment.