Open Jean-Daniel opened 1 year ago
ViewBinding is a KAPT processor and thus not compatible with KSP processors, essentially you are running into the issue described here: https://dagger.dev/dev-guide/ksp#interaction-with-javackapt-processors. See also https://github.com/google/ksp/issues/1388.
ViewBinding does not rely on KAPT. It is its own code-generating task created by AGP.
Ups! I was thinking of DataBinding then which I believe has an annotation processor component that runs in KAPT for Kotlin sources. Sorry for the confusion. 😅
So, ViewBinding being a task in AGP then my guess is that it needs some extra wiring so that generates classes are part of KSP's inputs. I'll ask AGP team and investigate more...
I've made a simple minimum reproducible app, in case it helps: https://github.com/ZOlbrys/HiltKspViewBindingTestApp
The error only happens when you use view bindings in a generic manner, FWIW.
Hey @danysantiago
Glad to hear that you are investigating this issue. Is there any progress on this one?
any updates on this one? thank you!
The AGP team is looking into it: https://issuetracker.google.com/301245705
We're working on a proper fix for the issue by exposing an API for generated java sources by the android gradle plugin to be consumed by KSP gradle plugin. Meanwhile, you can add the following snippet to your build file as a workaround to wire databinding/viewbinding generated classes to the ksp task.
androidComponents {
onVariants(selector().all(), { variant ->
afterEvaluate {
// This is a workaround for https://issuetracker.google.com/301245705 which depends on internal
// implementations of the android gradle plugin and the ksp gradle plugin which might change in the future
// in an unpredictable way.
project.tasks.getByName("ksp" + variant.name.capitalize() + "Kotlin") {
def dataBindingTask = (com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask) project.tasks.getByName("dataBindingGenBaseClasses" + variant.name.capitalize())
((org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool) it).setSource(
dataBindingTask.sourceOutFolder
)
}
}
})
}
We're working on a proper fix for the issue by exposing an API for generated java sources by the android gradle plugin to be consumed by KSP gradle plugin. Meanwhile, you can add the following snippet to your build file as a workaround to wire databinding/viewbinding generated classes to the ksp task.
androidComponents { onVariants(selector().all(), { variant -> afterEvaluate { // This is a workaround for https://issuetracker.google.com/301245705 which depends on internal // implementations of the android gradle plugin and the ksp gradle plugin which might change in the future // in an unpredictable way. project.tasks.getByName("ksp" + variant.name.capitalize() + "Kotlin") { def dataBindingTask = (com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask) project.tasks.getByName("dataBindingGenBaseClasses" + variant.name.capitalize()) ((org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool) it).setSource( dataBindingTask.sourceOutFolder ) } } }) }
While this didn't quite work for me right away, it did highlight what I needed to do to get things working in my case. Sincere thanks for the stopgap solution @AmrAfifiy 🙏
Here's the modified code that fit my situation:
androidComponents {
onVariants(selector().all(), { variant ->
afterEvaluate {
// This is a workaround for https://issuetracker.google.com/301245705 which depends on internal
// implementations of the android gradle plugin and the ksp gradle plugin which might change in the future
// in an unpredictable way.
def dataBindingTask = (DataBindingGenBaseClassesTask) project.tasks.named("dataBindingGenBaseClasses" + variant.name.capitalize()).get()
if (dataBindingTask != null) {
project.tasks.getByName("ksp" + variant.name.capitalize() + "Kotlin") {
((AbstractKotlinCompileTool) it).setSource(dataBindingTask.sourceOutFolder)
}
}
}
})
}
The main difference included here was picked up by a user on IssueTracker, who raised a very valid point that not all modules that apply the ksp plugin use view binding. So with that in mind, it makes sense to optionally process DataBindingGenBaseClassesTask
instances, as they could be null
(when a module that applies the ksp plugin does not make use of view binding).
I hope that helps!
There is a workaround for those who are facing a simular issue with AIDL using:
// Workaround for https://github.com/google/dagger/issues/4158
androidComponents {
onVariants(selector().all(), { variant ->
afterEvaluate {
def capName = variant.name.capitalize()
tasks.getByName("ksp${capName}Kotlin") {
setSource(tasks.getByName("compile${capName}Aidl").outputs)
}
}
})
}
Originally posted by @Goooler in https://github.com/google/dagger/issues/4158#issuecomment-1825399362
Facing the same issue with a custom plugin that generates Kotlin files and registers them using addGeneratedSourceDirectory
:
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
androidComponents.onVariants { variant ->
val task = project.tasks.register("myTask${variant.name.capitalized()}", MyKotlinGeneratingTask::class.java)
variant.sources.java!!.addGeneratedSourceDirectory(task) { it.outputDirectory }
}
Dagger KSP processor as of 2.48.1/KSP 1.0.13 cannot resolve any type generated by the custom task.
Is there a version of snippet in Kotlin DSL? I am not really sure how to apply the Kotlin sample from the issue tracker. Thanks
Something like the following should work for you as a Kotlin DSL:
import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask
import org.gradle.api.UnknownTaskException
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool
extensions.configure<LibraryAndroidComponentsExtension> {
onVariants { variant ->
afterEvaluate {
project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
val dataBindingTask =
try {
val taskName = "dataBindingGenBaseClasses${variant.name.capitalize()}"
project.tasks.getByName(taskName) as DataBindingGenBaseClassesTask
} catch (e: UnknownTaskException) {
return@getByName
}
project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
(this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder)
}
}
}
}
}
Something like the following should work for you as a Kotlin DSL:
import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask import org.gradle.api.UnknownTaskException import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool extensions.configure<LibraryAndroidComponentsExtension> { onVariants { variant -> afterEvaluate { project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") { val dataBindingTask = try { val taskName = "dataBindingGenBaseClasses${variant.name.capitalize()}" project.tasks.getByName(taskName) as DataBindingGenBaseClassesTask } catch (e: UnknownTaskException) { return@getByName } project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") { (this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder) } } } } }
Which part of the build.gradle.kts
should be placed? I tried it inside the app module build gradle right below dependencies block and I got syncing error.
Extension of type 'LibraryAndroidComponentsExtension' does not exist. Currently registered extension types: [ExtraPropertiesExtension, LibrariesForAdmob, LibrariesForFirebase, LibrariesForGms, LibrariesForLibs, LibrariesForMaterial, VersionCatalogsExtension, BasePluginExtension, DefaultArtifactPublicationSet, SourceSetContainer, ReportingExtension, JavaToolchainService, JavaPluginExtension, BaseAppModuleExtension, ApplicationAndroidComponentsExtension, NamedDomainObjectContainer<BaseVariantOutput>, KotlinAndroidProjectExtension, KotlinTestsRegistry, AppSweepExtension, PlayPublisherExtension, KspExtension, GoogleServicesPlugin.GoogleServicesPluginConfig]
Ah, apologies. I have this configured as a plugin. As a pure DSL within your app's build.gradle.kts
, you should be able to do the same thing as suggested by others above:
import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask
import org.gradle.api.UnknownTaskException
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool
androidComponents {
onVariants(selector().all()) { variant ->
afterEvaluate {
project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
val dataBindingTask =
try {
val taskName = "dataBindingGenBaseClasses${variant.name.capitalize()}"
project.tasks.getByName(taskName) as DataBindingGenBaseClassesTask
} catch (e: UnknownTaskException) {
return@getByName
}
project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") {
(this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder)
}
}
}
}
}
Ah, apologies. I have this configured as a plugin. As a pure DSL within your app's
build.gradle.kts
, you should be able to do the same thing as suggested by others above:import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask import org.gradle.api.UnknownTaskException import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool androidComponents { onVariants(selector().all()) { variant -> afterEvaluate { project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") { val dataBindingTask = try { val taskName = "dataBindingGenBaseClasses${variant.name.capitalize()}" project.tasks.getByName(taskName) as DataBindingGenBaseClassesTask } catch (e: UnknownTaskException) { return@getByName } project.tasks.getByName("ksp${variant.name.capitalize()}Kotlin") { (this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder) } } } } }
This work, thanks a lot!
As others pointed out, this is not an issue only of View Binding. BuildConfig, SafeArgs, AIDL, etc. are affected just the same. Here's a script covering all of them.
/*
* AGP tasks do not get properly wired to the KSP task at the moment.
* As a result, KSP sees `error.NonExistentClass` instead of generated types.
*
* https://github.com/google/dagger/issues/4049
* https://github.com/google/dagger/issues/4051
* https://github.com/google/dagger/issues/4061
* https://github.com/google/dagger/issues/4158
*/
androidComponents {
onVariants(selector().all()) { variant ->
afterEvaluate {
val variantName = variant.name.capitalize()
val ksp = "ksp${variantName}Kotlin"
val viewBinding = "dataBindingGenBaseClasses$variantName"
val buildConfig = "generate${variantName}BuildConfig"
val safeArgs = "generateSafeArgs$variantName"
val aidl = "compile${variantName}Aidl"
val kspTask = project.tasks.findByName(ksp)
as? org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool<*>
val viewBindingTask = project.tasks.findByName(viewBinding)
as? com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask
val buildConfigTask = project.tasks.findByName(buildConfig)
as? com.android.build.gradle.tasks.GenerateBuildConfig
val aidlTask = project.tasks.findByName(aidl)
as? com.android.build.gradle.tasks.AidlCompile
val safeArgsTask = project.tasks.findByName(safeArgs)
as? androidx.navigation.safeargs.gradle.ArgumentsGenerationTask
kspTask?.run {
viewBindingTask?.let { setSource(it.sourceOutFolder) }
buildConfigTask?.let { setSource(it.sourceOutputDir) }
aidlTask?.let { setSource(it.sourceOutputDir) }
safeArgsTask?.let { setSource(it.outputDir) }
}
}
}
}
As others pointed out, this is not an issue only of View Binding. BuildConfig, SafeArgs, AIDL, etc. are affected just the same. Here's a script covering all of them.
/* * AGP tasks do not get properly wired to the KSP task at the moment. * As a result, KSP sees `error.NonExistentClass` instead of generated types. * * https://github.com/google/dagger/issues/4049 * https://github.com/google/dagger/issues/4051 * https://github.com/google/dagger/issues/4061 * https://github.com/google/dagger/issues/4158 */ androidComponents { onVariants(selector().all()) { variant -> afterEvaluate { val variantName = variant.name.capitalize() val ksp = "ksp${variantName}Kotlin" val viewBinding = "dataBindingGenBaseClasses$variantName" val buildConfig = "generate${variantName}BuildConfig" val safeArgs = "generateSafeArgs$variantName" val aidl = "compile${variantName}Aidl" val kspTask = project.tasks.findByName(ksp) as? org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool<*> val viewBindingTask = project.tasks.findByName(viewBinding) as? com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask val buildConfigTask = project.tasks.findByName(buildConfig) as? com.android.build.gradle.tasks.GenerateBuildConfig val aidlTask = project.tasks.findByName(aidl) as? com.android.build.gradle.tasks.AidlCompile val safeArgsTask = project.tasks.findByName(safeArgs) as? androidx.navigation.safeargs.gradle.ArgumentsGenerationTask kspTask?.run { viewBindingTask?.let { setSource(it.sourceOutFolder) } buildConfigTask?.let { setSource(it.sourceOutputDir) } aidlTask?.let { setSource(it.sourceOutputDir) } safeArgsTask?.let { setSource(it.outputDir) } } } } }
thanks,it work for me!
As others pointed out, this is not an issue only of View Binding. BuildConfig, SafeArgs, AIDL, etc. are affected just the same. Here's a script covering all of them.
/* * AGP tasks do not get properly wired to the KSP task at the moment. * As a result, KSP sees `error.NonExistentClass` instead of generated types. * * https://github.com/google/dagger/issues/4049 * https://github.com/google/dagger/issues/4051 * https://github.com/google/dagger/issues/4061 * https://github.com/google/dagger/issues/4158 */ androidComponents { onVariants(selector().all()) { variant -> afterEvaluate { val variantName = variant.name.capitalize() val ksp = "ksp${variantName}Kotlin" val viewBinding = "dataBindingGenBaseClasses$variantName" val buildConfig = "generate${variantName}BuildConfig" val safeArgs = "generateSafeArgs$variantName" val aidl = "compile${variantName}Aidl" val kspTask = project.tasks.findByName(ksp) as? org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool<*> val viewBindingTask = project.tasks.findByName(viewBinding) as? com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask val buildConfigTask = project.tasks.findByName(buildConfig) as? com.android.build.gradle.tasks.GenerateBuildConfig val aidlTask = project.tasks.findByName(aidl) as? com.android.build.gradle.tasks.AidlCompile val safeArgsTask = project.tasks.findByName(safeArgs) as? androidx.navigation.safeargs.gradle.ArgumentsGenerationTask kspTask?.run { viewBindingTask?.let { setSource(it.sourceOutFolder) } buildConfigTask?.let { setSource(it.sourceOutputDir) } aidlTask?.let { setSource(it.sourceOutputDir) } safeArgsTask?.let { setSource(it.outputDir) } } } } }
Thanks! You might also want to add these lines if you use protobuf (with datastore for example)
androidComponents {
onVariants(selector().all()) { variant ->
afterEvaluate {
//...
val proto = "generate${variantName}Proto"
//...
val protoTask = project.tasks.findByName(proto)
as? com.google.protobuf.gradle.GenerateProtoTask
kspTask?.run {
//...
protoTask?.let { setSource(it.outputSourceDirectorySet) }
}
}
}
}
AGP issue fixed: https://issuetracker.google.com/301245705
Can confirm, issue not reproduce anymore with ksp 1.9.22-1.0.18
I've just updated to this version to check out if it works, but unfortunately I still have the same error:
Dependency trace:
=> element (CLASS): com.example.auth.data.SessionStore
=> element (CONSTRUCTOR): SessionStore(kotlinx.coroutines.CoroutineDispatcher, androidx.datastore.core.DataStore<error.NonExistentClass>)
=> type (EXECUTABLE constructor): (kotlinx.coroutines.CoroutineDispatcher,androidx.datastore.core.DataStore<error.NonExistentClass>)void
=> type (DECLARED parameter type): androidx.datastore.core.DataStore<error.NonExistentClass>
=> type (ERROR type argument): error.NonExistentClass
My SessionStore
constructor looks like this:
internal class SessionStore @Injec constructor(
@BackgroundDispatcher
private val backgroundDispatcher: CoroutineDispatcher,
private val stateStore: DataStore<Session> = context.sessionStore,
)
and Session
is generated from proto using squareup's wire
library.
I've tried the workarounds too (adapted to wire
task), still no dice.
I have the same error. Any updates on this?
I have the same error as @rekaszeru . Any updates on this?
AFAIK, this is not a Dagger issue.
However, there's a number of workarounds listed above, e.g. https://github.com/google/dagger/issues/4049#issuecomment-1743321244.
@bcorso , like @rekaszeru said, we have tried the workarounds. Specifically this was what I put in my Convention plugin:
configure<LibraryAndroidComponentsExtension> {
onVariants { variant ->
afterEvaluate {
val variantName = variant.name.capitalize()
val kspTaskName = "ksp${variantName}Kotlin"
println("kspTaskName: $kspTaskName")
val wireTaskName = "generate${variantName}Protos"
val kspTask = project.tasks.findByName(kspTaskName) as? AbstractKotlinCompileTool<*>
val wireTask = project.tasks.findByName(wireTaskName) as? WireTask
kspTask?.run {
wireTask?.let {
// itSource: SomeClass.proto
println("itSource: ${it.source.files.joinToString { it.name }}")
setSource(it.source)
}
}
}
}
}
And yet dagger ksp chokes when it can't find "SomeClass".
Edit: Wait, why would .proto be in the source that we want to set for kspTask?
Edit2: Well I tried it with setSource(it.outputDirectories)
, but that didn't work either.
@bcorso , like @rekaszeru said, we have tried the workarounds. Specifically this was what I put in my Convention plugin:
AFAICT, @rekaszeru never directly confirmed if they tried the workaround or not. My interpretation of https://github.com/google/dagger/issues/4049#issuecomment-1974017840 was that they tried upgrading to the new KSP version mentioned in https://github.com/google/dagger/issues/4049#issuecomment-1955890325.
Like I said, this isn't really a Dagger issue. This is an AGP/KSP issue and it just fails in Dagger because they aren't getting the ordering/wiring of generation correct.
Edit: Wait, why would .proto be in the source that we want to set for kspTask?
As mentioned in https://github.com/google/dagger/issues/4049#issuecomment-1846912761, this issue shows up with a number of different libraries like Protobuf, View Binding, BuildConfig, SafeArgs, AIDL, etc. so you have to make sure the workaround you use includes the library you need.
@rekaszeru I was able to get Wire working with Dagger KSP with the following in my Convention plugin:
configure<LibraryAndroidComponentsExtension> {
onVariants { variant ->
afterEvaluate {
val variantName = variant.name.capitalize()
val kspTaskName = "ksp${variantName}Kotlin"
val wireTaskName = "generate${variantName}Protos"
val kspTask = project.tasks.findByName(kspTaskName) as? AbstractKotlinCompileTool<*>
val wireTask = project.tasks.findByName(wireTaskName) as? WireTask
kspTask?.run {
wireTask?.let {
dependsOn(it) // It chokes if you try to setSource without `dependsOn`, so I guess we need both.
setSource(it.outputDirectories)
}
}
}
}
}
Are you able to verify?
I have a class that uses ViewBinding as a generic parameter:
DownloadsListBinding
is a class generated from a xml layout resource by the Android Gradle Plugin, and everything is working fine with kapt, but when trying to use KSP, the compilation fails with the following error:Versions: Gradle Plugin version: 8.1.1 Kotlin: 1.9.10 KSP: 1.9.10-1.0.13 Hilt: 2.48