Skip to content

Commit

Permalink
Add option to disable contributes subcomponent handling (#67)
Browse files Browse the repository at this point in the history
* WIP add option to disable contributes subcomponent handling

* WIP add test

* TMP debugging

* Finish test

* Add missing fallback

* Formatting

* Docs

* Update API

* Fix
  • Loading branch information
ZacSweers authored Aug 30, 2024
1 parent ebdbc71 commit 3a9064a
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
**Unreleased**
--------------

- **New**: Add option to disable contributes subcomponent handling. This can be useful if working in a codebase or project that doesn't use `@ContributeSubcomponent` and thus doesn't need to scan the classpath for them while merging. More details can be found in the `## Options` section of `FORK.md`.
- **Fix**: Ensure round processing is correctly reset if no `@ContributeSubcomponent` triggers are found in a given round. This was an edge case that affected projects with custom code generators that generated triggers in a later round.

0.2.6
Expand Down
8 changes: 8 additions & 0 deletions FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ use the generated `Merged*` classes. See the Technical Design section for more d

You _should_ be able to build now.

## Options

This fork contains some extra KSP options that can be used for different scenarios.

- `anvil-ksp-verbose`: Enable verbose logging, such as processor timing information.
- `anvil-ksp-extraContributingAnnotations`: A colon-delimited list of additional contributing annotations to scan for. More on this below.
- `anvil-ksp-enable-contributes-subcomponent-merging`: Enable/disable scanning the classpath for `@ContributesSubcomponent` triggers. This can be useful if working in a codebase or project that doesn't use `@ContributeSubcomponent` and thus doesn't need to scan the classpath for them while merging. Default is true.

## Compatibility

### Using Anvil KSP without dagger-ksp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ public class AnvilCompilation internal constructor(
kspArgs["generate-dagger-factories-only"] = generateDaggerFactoriesOnly.toString()
kspArgs["disable-component-merging"] = disableComponentMerging.toString()
kspArgs["merging-backend"] = componentMergingBackend.name.lowercase(Locale.US)
if (mode.options.isNotEmpty()) {
for ((key, value) in mode.options) {
kspArgs[key] = value
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public sealed class AnvilCompilationMode(public val analysisBackend: AnalysisBac
) : AnvilCompilationMode(EMBEDDED)
public data class Ksp(
val symbolProcessorProviders: List<SymbolProcessorProvider> = emptyList(),
val options: Map<String, String> = emptyMap(),
) : AnvilCompilationMode(KSP)
}
1 change: 1 addition & 0 deletions compiler/api/compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public final class com/squareup/anvil/compiler/CommandLineOptions$Companion {
}

public final class com/squareup/anvil/compiler/UtilsKt {
public static final field OPTION_ENABLE_CONTRIBUTES_SUBCOMPONENT_MERGING Ljava/lang/String;
public static final field OPTION_EXTRA_CONTRIBUTING_ANNOTATIONS Ljava/lang/String;
public static final field OPTION_GENERATE_SHIMS Ljava/lang/String;
public static final field OPTION_VERBOSE Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.squareup.anvil.compiler.ClassScanningKspProcessor.Provider
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.AnvilKspExtension
import com.squareup.anvil.compiler.api.ComponentMergingBackend
import com.squareup.anvil.compiler.codegen.KspContributesSubcomponentHandler
import com.squareup.anvil.compiler.codegen.KspContributesSubcomponentHandlerSymbolProcessor
import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessor
import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessorProvider
Expand Down Expand Up @@ -44,10 +45,16 @@ internal class ClassScanningKspProcessor(
// Extensions to run
val extensions = extensions(env, context)

val enableContributesSubcomponentHandling = env.options[OPTION_ENABLE_CONTRIBUTES_SUBCOMPONENT_MERGING]
?.toBoolean() ?: true

// ContributesSubcomponent handler, which will always be run but needs to conditionally run
// within KspContributionMerger if it's going to run.
val contributesSubcomponentHandler =
val contributesSubcomponentHandler = if (enableContributesSubcomponentHandling) {
KspContributesSubcomponentHandlerSymbolProcessor(env, classScanner)
} else {
KspContributesSubcomponentHandler.NoOp
}

val componentMergingEnabled =
!context.disableComponentMerging &&
Expand All @@ -57,9 +64,14 @@ internal class ClassScanningKspProcessor(
// We're running component merging, so we need to run both and let KspContributionMerger
// handle running the contributesSubcomponentHandler when needed.
listOf(KspContributionMerger(env, classScanner, contributesSubcomponentHandler, extensions))
} else {
} else if (enableContributesSubcomponentHandling) {
// We're only generating factories/contributessubcomponents, so only run it + extensions
listOf(contributesSubcomponentHandler, AnvilKspExtensionsRunner(extensions))
listOf(
contributesSubcomponentHandler as SymbolProcessor,
AnvilKspExtensionsRunner(extensions),
)
} else {
listOf(AnvilKspExtensionsRunner(extensions))
}
ClassScanningKspProcessor(env, delegates, classScanner)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.squareup.anvil.annotations.internal.InternalBindingMarker
import com.squareup.anvil.annotations.internal.InternalContributedSubcomponentMarker
import com.squareup.anvil.annotations.internal.InternalMergedTypeMarker
import com.squareup.anvil.compiler.api.AnvilKspExtension
import com.squareup.anvil.compiler.codegen.KspContributesSubcomponentHandler
import com.squareup.anvil.compiler.codegen.KspContributesSubcomponentHandlerSymbolProcessor
import com.squareup.anvil.compiler.codegen.KspMergeAnnotationsCheckSymbolProcessor
import com.squareup.anvil.compiler.codegen.generatedAnvilSubcomponentClassId
Expand Down Expand Up @@ -131,7 +132,7 @@ private val COMMON_MERGE_ANNOTATION_NAMES = setOf(
internal class KspContributionMerger(
override val env: SymbolProcessorEnvironment,
private val classScanner: ClassScannerKsp,
private val contributesSubcomponentHandler: KspContributesSubcomponentHandlerSymbolProcessor,
private val contributesSubcomponentHandler: KspContributesSubcomponentHandler,
private val extensions: Set<AnvilKspExtension>,
) : AnvilSymbolProcessor() {

Expand Down Expand Up @@ -239,7 +240,7 @@ internal class KspContributionMerger(
contributesSubcomponentHandler.computePendingEvents(resolver)
if (contributesSubcomponentHandler.hasPendingEvents()) {
shouldDefer = true
contributesSubcomponentHandler.process(resolver)
contributesSubcomponentHandler.processChecked(resolver)
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/main/java/com/squareup/anvil/compiler/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ public const val OPTION_EXTRA_CONTRIBUTING_ANNOTATIONS: String =
*/
public const val OPTION_VERBOSE: String = "anvil-ksp-verbose"

/**
* KSP option to disable `@ContributesSubcomponent` handling. If you don't use this feature in your
* project, you can set this to false. It is enabled by default.
*
* Value should be a boolean string.
*/
public const val OPTION_ENABLE_CONTRIBUTES_SUBCOMPONENT_MERGING: String = "anvil-ksp-enable-contributes-subcomponent-merging"

/**
* Returns the single element matching the given [predicate], or `null` if element was not found.
* Unlike [singleOrNull] this method throws an exception if more than one element is found.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ import com.squareup.kotlinpoet.ksp.writeTo
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name

internal interface KspContributesSubcomponentHandler {
fun computePendingEvents(resolver: Resolver)
fun hasPendingEvents(): Boolean
fun processChecked(resolver: Resolver): List<KSAnnotated>

companion object {
val NoOp = object : KspContributesSubcomponentHandler {
override fun computePendingEvents(resolver: Resolver) {
}

override fun hasPendingEvents(): Boolean = false

override fun processChecked(resolver: Resolver): List<KSAnnotated> = emptyList()
}
}
}

/**
* Looks for `@MergeComponent`, `@MergeSubcomponent` or `@MergeModules` annotations and generates
* the actual contributed subcomponents that specified these scopes as parent scope, e.g.
Expand All @@ -82,7 +99,7 @@ import org.jetbrains.kotlin.name.Name
internal class KspContributesSubcomponentHandlerSymbolProcessor(
override val env: SymbolProcessorEnvironment,
private val classScanner: ClassScannerKsp,
) : AnvilSymbolProcessor() {
) : AnvilSymbolProcessor(), KspContributesSubcomponentHandler {

/**
* Detected triggers that trigger events with [contributions]
Expand All @@ -106,7 +123,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor(
private var hasComputedEventsThisRound = false
private val pendingEvents = mutableListOf<GenerateCodeEvent>()

fun hasPendingEvents(): Boolean = pendingEvents.isNotEmpty()
override fun hasPendingEvents(): Boolean = pendingEvents.isNotEmpty()

override fun processChecked(resolver: Resolver): List<KSAnnotated> {
val deferred = processInternal(resolver)
Expand Down Expand Up @@ -255,7 +272,7 @@ internal class KspContributesSubcomponentHandlerSymbolProcessor(
* This is exposed internally for access in [KspContributionMerger], which may be running this
* processor in an encapsulated fashion.
*/
fun computePendingEvents(resolver: Resolver) = trace("Compute pending events") {
override fun computePendingEvents(resolver: Resolver) = trace("Compute pending events") {
if (hasComputedEventsThisRound) {
// Already computed this round
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.squareup.anvil.compiler.codegen
import com.google.common.truth.Truth.assertThat
import com.rickbusarow.kase.asClueCatching
import com.squareup.anvil.annotations.MergeSubcomponent
import com.squareup.anvil.compiler.OPTION_ENABLE_CONTRIBUTES_SUBCOMPONENT_MERGING
import com.squareup.anvil.compiler.PARENT_COMPONENT
import com.squareup.anvil.compiler.SUBCOMPONENT_FACTORY
import com.squareup.anvil.compiler.SUBCOMPONENT_MODULE
Expand All @@ -23,6 +24,7 @@ import com.squareup.anvil.compiler.internal.testing.use
import com.squareup.anvil.compiler.mergeComponentFqName
import com.squareup.anvil.compiler.secondContributingInterface
import com.squareup.anvil.compiler.subcomponentInterface
import com.squareup.anvil.compiler.walkGeneratedFiles
import com.tschuchort.compiletesting.JvmCompilationResult
import com.tschuchort.compiletesting.KotlinCompilation.ExitCode
import com.tschuchort.compiletesting.KotlinCompilation.ExitCode.OK
Expand All @@ -33,6 +35,7 @@ import org.jetbrains.kotlin.descriptors.runtime.structure.classId
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Test
import org.junit.jupiter.api.assertThrows
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
Expand Down Expand Up @@ -87,6 +90,45 @@ class ContributesSubcomponentHandlerGeneratorTest(
}
}

@Test fun `can be disabled in KSP`() {
assumeTrue(componentProcessingMode == ComponentProcessingMode.NONE)
assumeTrue(mode is AnvilCompilationMode.Ksp)

val modeWithOption = (mode as AnvilCompilationMode.Ksp).copy(
options = mapOf(
OPTION_ENABLE_CONTRIBUTES_SUBCOMPONENT_MERGING to "false",
),
)
compile(
"""
package com.squareup.test
import com.squareup.anvil.annotations.ContributesSubcomponent
import com.squareup.anvil.annotations.MergeComponent
@ContributesSubcomponent(Any::class, Unit::class)
interface SubcomponentInterface
@MergeComponent(Unit::class)
interface ComponentInterface
""".trimIndent(),
mode = modeWithOption,
) {
assertThrows<ClassNotFoundException> {
subcomponentInterface.anvilComponent(componentInterface)
}
// Assert we didn't generate 'anvil/component/com/squareup/test/componentinterface/SubcomponentInterface_....kt'
val files = walkGeneratedFiles(modeWithOption).toList()
assertThat(files.size).isEqualTo(1)
check(
files.single().nameWithoutExtension.equals(
"Com_squareup_test_SubcomponentInterface_7280b174",
ignoreCase = true,
),
)
}
}

@Test fun `generation works for a nested contributed subcomponent with a very long name`() {
assumeTrue(componentProcessingMode == ComponentProcessingMode.NONE)
// This test tests legacy behavior that we no longer need to do in KSP processing
Expand Down

0 comments on commit 3a9064a

Please sign in to comment.