Repository: square/anvil (kotlin)

Issue:
Factory generation: provide a way to disable it in androidTest
I have module which uses anvil Factory generation for it's main source set and an `androidTest` source set which wants to generate(with Dagger) `TestComponen`" and get the dependencies from it to test them.

Currently this leads to an error inside `androidTest` compilation, because Factories for `@Provides` functions in a `TestModule` generated twice(by Dagger and Anvil).

Currently I'm using a wierd workaround(which is another Anvil bug I believe):
If I mark `@Provides` functions in a `TestModule` as `internal`, dagger generates _Factories with different names_ for them, and so the duplicate class exception doesn't happen.
![image](https://user-images.githubusercontent.com/7382654/92042970-e794aa80-ed83-11ea-8635-d0a0abf40492.png)

Pull request:
Introduce a new `VariantFilter` for the Gradle extension. This API allows you to enable and disable Anvil for specific variants. 

Allow to override `generateDaggerFactories`, `generateDaggerFactoriesOnly` and `disableComponentMerging` through the variant filter.

This resolves #100.

Patch:
diff --git a/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilExtension.kt b/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilExtension.kt
index 353be2f4c..776bdeec4 100644
--- a/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilExtension.kt
+++ b/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilExtension.kt
@@ -1,5 +1,6 @@
 package com.squareup.anvil.plugin
 
+import org.gradle.api.Action
 import org.gradle.api.model.ObjectFactory
 import org.gradle.api.provider.Property
 import javax.inject.Inject
@@ -43,6 +44,17 @@ public abstract class AnvilExtension @Inject constructor(objects: ObjectFactory)
   public val disableComponentMerging: Property<Boolean> = objects.property(Boolean::class.java)
     .convention(false)
 
+  @Suppress("PropertyName")
+  internal var _variantFilter: Action<VariantFilter>? = null
+
+  /**
+   * Configures each variant of this project. For Android projects these are the respective
+   * Android variants, for JVM projects these are usually the main and test variant.
+   */
+  public fun variantFilter(action: Action<VariantFilter>) {
+    _variantFilter = action
+  }
+
   /*
    * The below properties are legacy former properties. We do a bit of Kotlin sugar to preserve
    * binary compatibility but not Kotlin source compatibility.
diff --git a/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilPlugin.kt b/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilPlugin.kt
index f77420e7e..7ea3b9ce7 100644
--- a/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilPlugin.kt
+++ b/gradle-plugin/src/main/java/com/squareup/anvil/plugin/AnvilPlugin.kt
@@ -31,12 +31,15 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 import java.io.File
 import java.util.Locale.US
+import java.util.concurrent.ConcurrentHashMap
 
 @Suppress("unused")
 internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
 
+  private val variantCache = ConcurrentHashMap<String, Variant>()
+
   override fun apply(target: Project) {
-    val extension = target.extensions.create("anvil", AnvilExtension::class.java)
+    target.extensions.create("anvil", AnvilExtension::class.java)
 
     // Create a configuration for collecting CodeGenerator dependencies. We need to create all
     // configurations eagerly and cannot wait for applyToCompilation(..) below, because this
@@ -69,22 +72,12 @@ internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
         }
       }
     }
-
-    target.afterEvaluate {
-      if (!extension.generateDaggerFactories.get() &&
-        extension.generateDaggerFactoriesOnly.get()
-      ) {
-        throw GradleException(
-          "You cannot set generateDaggerFactories to false and generateDaggerFactoriesOnly " +
-            "to true at the same time."
-        )
-      }
-    }
   }
 
   override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
     return when (kotlinCompilation.platformType) {
-      androidJvm, jvm -> true
+      // If the variant is ignored, then don't apply the compiler plugin.
+      androidJvm, jvm -> !getVariant(kotlinCompilation).variantFilter.ignore
       else -> false
     }
   }
@@ -92,9 +85,18 @@ internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
   override fun applyToCompilation(
     kotlinCompilation: KotlinCompilation<*>
   ): Provider<List<SubpluginOption>> {
-    val variant = Variant(kotlinCompilation)
+    val variant = getVariant(kotlinCompilation)
     val project = variant.project
 
+    if (!variant.variantFilter.generateDaggerFactories &&
+      variant.variantFilter.generateDaggerFactoriesOnly
+    ) {
+      throw GradleException(
+        "You cannot set generateDaggerFactories to false and generateDaggerFactoriesOnly " +
+          "to true at the same time for variant ${variant.name}."
+      )
+    }
+
     // Make the kotlin compiler classpath extend our configurations to pick up our extra
     // generators.
     project.configurations.getByName(variant.compilerPluginClasspathName)
@@ -103,7 +105,7 @@ internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
     disableIncrementalKotlinCompilation(variant)
     disablePreciseJavaTracking(variant)
 
-    if (!variant.extension.generateDaggerFactoriesOnly.get()) {
+    if (!variant.variantFilter.generateDaggerFactoriesOnly) {
       disableCorrectErrorTypes(variant)
 
       kotlinCompilation.dependencies {
@@ -111,15 +113,13 @@ internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
       }
     }
 
-    // Notice that we use the name of the Kotlin compilation as a directory name. Generated code
+    // Notice that we use the name of the variant as a directory name. Generated code
     // for this specific compile task will be included in the task output. The output of different
     // compile tasks shouldn't be mixed.
     val srcGenDir = File(
       project.buildDir,
-      "anvil${File.separator}src-gen-${kotlinCompilation.name}"
+      "anvil${File.separator}src-gen-${variant.name}"
     )
-    val extension = project.extensions.findByType(AnvilExtension::class.java)
-      ?: project.objects.newInstance(AnvilExtension::class.java)
 
     return project.provider {
       listOf(
@@ -129,15 +129,15 @@ internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
         ),
         SubpluginOption(
           key = "generate-dagger-factories",
-          lazy { extension.generateDaggerFactories.get().toString() }
+          lazy { variant.variantFilter.generateDaggerFactories.toString() }
         ),
         SubpluginOption(
           key = "generate-dagger-factories-only",
-          lazy { extension.generateDaggerFactoriesOnly.get().toString() }
+          lazy { variant.variantFilter.generateDaggerFactoriesOnly.toString() }
         ),
         SubpluginOption(
           key = "disable-component-merging",
-          lazy { extension.disableComponentMerging.get().toString() }
+          lazy { variant.variantFilter.disableComponentMerging.toString() }
         )
       )
     }
@@ -202,8 +202,8 @@ internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
         .namedLazy<KaptGenerateStubsTask>(
           "kaptGenerateStubs${variant.taskSuffix}"
         ) { stubsTaskProvider ->
-          if (variant.extension.generateDaggerFactoriesOnly.get() ||
-            variant.extension.disableComponentMerging.get()
+          if (variant.variantFilter.generateDaggerFactoriesOnly ||
+            variant.variantFilter.disableComponentMerging
           ) {
             // We don't need to disable the incremental compilation for the stub generating task
             // every time, when we only generate Dagger factories or contribute modules. That's only
@@ -285,6 +285,19 @@ internal open class AnvilPlugin : KotlinCompilerPluginSupportPlugin {
         "implementations."
     }
   }
+
+  private fun getVariant(kotlinCompilation: KotlinCompilation<*>): Variant {
+    return variantCache.computeIfAbsent(kotlinCompilation.name) {
+      val variant = Variant(kotlinCompilation)
+
+      // The cache makes sure that we execute this filter action only once.
+      variant.project.extensions.getByType(AnvilExtension::class.java)
+        ._variantFilter
+        ?.execute(variant.variantFilter)
+
+      variant
+    }
+  }
 }
 
 /*
@@ -368,10 +381,10 @@ private val agpPlugins = listOf(
 internal class Variant private constructor(
   val name: String,
   val project: Project,
-  val extension: AnvilExtension,
   val compileTaskProvider: TaskProvider<KotlinCompile>,
   val androidVariant: BaseVariant?,
   val compilerPluginClasspathName: String,
+  val variantFilter: VariantFilter,
 ) {
   // E.g. compileKotlin, compileKotlinJvm, compileDebugKotlin.
   val taskSuffix = compileTaskProvider.name.substringAfter("compile")
@@ -388,19 +401,27 @@ internal class Variant private constructor(
       }
 
       val project = kotlinCompilation.target.project
-      val name = kotlinCompilation.name
+      val extension = project.extensions.getByType(AnvilExtension::class.java)
+      val androidVariant = (kotlinCompilation as? KotlinJvmAndroidCompilation)?.androidVariant
+
+      val commonFilter = CommonFilter(kotlinCompilation.name, extension)
+      val variantFilter = if (androidVariant != null) {
+        AndroidVariantFilter(commonFilter, androidVariant)
+      } else {
+        JvmVariantFilter(commonFilter)
+      }
 
       @Suppress("UNCHECKED_CAST")
       return Variant(
-        name = name,
+        name = kotlinCompilation.name,
         project = project,
-        extension = project.extensions.getByType(AnvilExtension::class.java),
         compileTaskProvider = kotlinCompilation.compileKotlinTaskProvider as
           TaskProvider<KotlinCompile>,
-        androidVariant = (kotlinCompilation as? KotlinJvmAndroidCompilation)?.androidVariant,
+        androidVariant = androidVariant,
         compilerPluginClasspathName = PLUGIN_CLASSPATH_CONFIGURATION_NAME +
           kotlinCompilation.target.targetName.capitalize(US) +
-          name.capitalize(US)
+          kotlinCompilation.name.capitalize(US),
+        variantFilter = variantFilter
       ).also {
         // Sanity check.
         check(it.compileTaskProvider.name.startsWith("compile"))
diff --git a/gradle-plugin/src/main/java/com/squareup/anvil/plugin/VariantFilter.kt b/gradle-plugin/src/main/java/com/squareup/anvil/plugin/VariantFilter.kt
new file mode 100644
index 000000000..e4219c35f
--- /dev/null
+++ b/gradle-plugin/src/main/java/com/squareup/anvil/plugin/VariantFilter.kt
@@ -0,0 +1,71 @@
+package com.squareup.anvil.plugin
+
+import com.android.build.gradle.api.BaseVariant
+import org.gradle.api.Named
+
+public interface VariantFilter : Named {
+
+  /**
+   * Indicate whether or not to ignore Anvil for this particular variant. Default is false.
+   */
+  public var ignore: Boolean
+
+  /**
+   * Indicate whether Dagger factory generation for this variant should be enabled. The default
+   * value comes from the [AnvilExtension]. See [AnvilExtension.generateDaggerFactories] for more
+   * details.
+   */
+  public var generateDaggerFactories: Boolean
+
+  /**
+   * Indicate whether only Dagger factories for this variant should be generated. The default
+   * value comes from the [AnvilExtension]. See [AnvilExtension.generateDaggerFactoriesOnly] for
+   * more details.
+   */
+  public var generateDaggerFactoriesOnly: Boolean
+
+  /**
+   * Indicate whether component merging for this variant should be disabled. The default
+   * value comes from the [AnvilExtension]. See [AnvilExtension.disableComponentMerging] for more
+   * details.
+   */
+  public var disableComponentMerging: Boolean
+}
+
+internal class CommonFilter(
+  private val name: String,
+  private val extension: AnvilExtension
+) : VariantFilter {
+  override fun getName(): String = name
+  override var ignore: Boolean = false
+
+  private var generateDaggerFactoriesOverride: Boolean? = null
+  override var generateDaggerFactories: Boolean
+    get() = generateDaggerFactoriesOverride ?: extension.generateDaggerFactories.get()
+    set(value) {
+      generateDaggerFactoriesOverride = value
+    }
+
+  private var generateDaggerFactoriesOnlyOverride: Boolean? = null
+  override var generateDaggerFactoriesOnly: Boolean
+    get() = generateDaggerFactoriesOnlyOverride ?: extension.generateDaggerFactoriesOnly.get()
+    set(value) {
+      generateDaggerFactoriesOnlyOverride = value
+    }
+
+  private var disableComponentMergingOverride: Boolean? = null
+  override var disableComponentMerging: Boolean
+    get() = disableComponentMergingOverride ?: extension.disableComponentMerging.get()
+    set(value) {
+      disableComponentMergingOverride = value
+    }
+}
+
+public class JvmVariantFilter internal constructor(
+  commonFilter: CommonFilter
+) : VariantFilter by commonFilter
+
+public class AndroidVariantFilter internal constructor(
+  commonFilter: CommonFilter,
+  public val androidVariant: BaseVariant
+) : VariantFilter by commonFilter


Test patch:
diff --git a/integration-tests/mpp/android-module/build.gradle b/integration-tests/mpp/android-module/build.gradle
index ad35e53a4..24a2368f0 100644
--- a/integration-tests/mpp/android-module/build.gradle
+++ b/integration-tests/mpp/android-module/build.gradle
@@ -1,3 +1,6 @@
+import com.android.build.gradle.api.UnitTestVariant
+import com.squareup.anvil.plugin.AndroidVariantFilter
+
 plugins {
   id 'org.jetbrains.kotlin.multiplatform'
   id 'com.android.library'
@@ -51,6 +54,15 @@ kotlin {
   }
 }
 
+anvil {
+  variantFilter { filter ->
+    if (filter instanceof AndroidVariantFilter &&
+        filter.androidVariant instanceof UnitTestVariant) {
+      ignore = true
+    }
+  }
+}
+
 dependencies {
   kapt deps.dagger2.compiler
 
diff --git a/integration-tests/tests/build.gradle b/integration-tests/tests/build.gradle
index 37c63c821..2c61ee3f3 100644
--- a/integration-tests/tests/build.gradle
+++ b/integration-tests/tests/build.gradle
@@ -6,6 +6,12 @@ kotlin {
   explicitApi()
 }
 
+anvil {
+  variantFilter {
+    ignore = name == 'main'
+  }
+}
+
 dependencies {
   testImplementation project(':integration-tests:library')
   testImplementation testFixtures(project(":compiler-utils"))