Skip to content

Commit

Permalink
Introduce AnvilKspOptionsProvider (#44)
Browse files Browse the repository at this point in the history
* Introduce AnvilKspOptionsProvider

In order for `CommandLineArgumentProvider` to work correctly, it needs to internally track its inputs as a managed object. Prior to this, changes to providers would not actually invalidate a KSP task's inputs and result in incorrect cache hits since the pluginOptions themselves under the hood are deemed internal and ignored.

This fixes it by creating a new `AnvilKspOptionsProvider` managed type with a `MapProperty` that tracks the inputs. This is appropriately wired in `KspTask` as a `@Nested` input, which then fixes this cache hit issue.

* CHANGELOG
  • Loading branch information
ZacSweers authored Aug 8, 2024
1 parent 242bf88 commit 07ab80f
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
**Unreleased**
--------------

- **Fix:** Correctly track inputs to KSP command line options so they don't result in incorrect task build cache hits.

0.2.1
-----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import com.google.devtools.ksp.gradle.KspExtension
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
import org.gradle.process.CommandLineArgumentProvider
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
Expand Down Expand Up @@ -230,25 +231,29 @@ public abstract class AnvilExtension @Inject constructor(
}
}

project.extensions.configure(KspExtension::class.java) { ksp ->
// Do not convert this to a lambda.
// It will leak the AnvilExtension instance and break configuration caching.
ksp.arg(
commandLineArgumentProvider(
"generate-dagger-factories" to generateDaggerFactories,
"generate-dagger-factories-only" to generateDaggerFactoriesOnly,
"disable-component-merging" to disableComponentMerging,
"anvil-ksp-extraContributingAnnotations" to kspContributingAnnotations.map {
it.sorted().joinToString(":")
val argsProvider = project.objects.newInstance(AnvilKspOptionsProvider::class.java)
argsProvider.options.apply {
put("generate-dagger-factories", generateDaggerFactories)
put("generate-dagger-factories-only", generateDaggerFactoriesOnly)
put("disable-component-merging", disableComponentMerging)
put(
"anvil-ksp-extraContributingAnnotations",
kspContributingAnnotations.map {
it.sorted().joinToString(":")
},
)
put("will-have-dagger-factories", willHaveDaggerFactories)
put(
"merging-backend",
useKspComponentMergingBackend
.map { enabled ->
if (enabled) "ksp" else "none"
},
"will-have-dagger-factories" to willHaveDaggerFactories,
"merging-backend" to useKspComponentMergingBackend
.map { enabled ->
if (enabled) "ksp" else "none"
},
),
)
}
project.extensions.configure(KspExtension::class.java) { ksp ->
ksp.arg(argsProvider)
}
}

private fun addKspDep(configurationName: String) {
Expand Down Expand Up @@ -290,26 +295,17 @@ public abstract class AnvilExtension @Inject constructor(
}
}

/**
* This function is propping up configuration caching in two ways:
*
* 1. It creates local references to the providers,
* so that we can pass them to the KSP task without a reference to `AnvilExtension`.
* 2. It creates the `CommandLineArgumentProvider` lambda outside the `AnvilExtension` class,
* so that we can't accidentally capture `AnvilExtension` in the lambda.
*
* [AnvilExtension] currently isn't serializable for configuration caching
* because of its `Project` property.
*/
private fun commandLineArgumentProvider(
vararg args: Pair<String, Provider<*>>,
): CommandLineArgumentProvider {
return CommandLineArgumentProvider {
args.mapNotNull { (arg, provider) ->
val value = provider.orNull ?: return@mapNotNull null
internal abstract class AnvilKspOptionsProvider : CommandLineArgumentProvider {

@get:Input
abstract val options: MapProperty<String, Any?>

override fun asArguments(): Iterable<String> {
return options.get().entries.mapNotNull { (option, value) ->
// kotlinc isn't ok with blank values so catch these and ignore 'em
if (value is String && value.isBlank()) return@mapNotNull null
"$arg=$value"
if (value is Collection<*> && value.isEmpty()) return@mapNotNull null
"$option=$value"
}
}
}
Expand Down

0 comments on commit 07ab80f

Please sign in to comment.