I am running into an issue where an annotation arg...
# ksp
e
I am running into an issue where an annotation argument is missing during processing (KSP beta8, kotlin 1.5.30) - I’m not sure if it’s a KSP or Kotlin bug. I know there have been several bugs with values in annotations not working correctly in some edge cases, but this one seems different. I am seeing this when using an Android resource value (Int) as an annotation argument
Copy code
@Attr(R2.layout.my_layout)
class MyClass
Where the resource is defined in a class like this
Copy code
public final class R2 {
    public static final class layout {
        public static final int my_layout = 1616;
    }
}
We borrowed this pattern from Butterknife, and the R2 class comes from the Butterknife gradle plugin to make the resource values final. When a resource value is used as the annotation argument like this, KSP and the kotlin compiler don’t seem to recognize it. I’ve done some debugging and the whole argument is just absent in the compiler representation of the arguments list. However, if I use a constant Int directly the argument is available. Does anyone know why KSP/Kotlin can’t recognize a value defined this way?
I’ve done some experimenting with the annotation defined in both kotlin and java and it makes no difference. The one clue I’ve gotten is that if I define this R2 structure directly in my own sources it works fine. However, the real R2 class is generated by the gradle plugin at
build/generated/source/r2/debug/
- I’m wondering if Ksp or Kotlin is not correctly incorporating generated sources from there?
j
can you try manually include the generated path in build script?
e
happy to try that- can you point me to what api or syntax you’re thinking of for that? Do you mean something like this?
Copy code
kotlin {
    sourceSets.main {
        kotlin.srcDir("build/generated/source/r2/")
    }
}
this seems to have worked!
Copy code
kotlin {
    sourceSets {
        main.kotlin.srcDirs += 'build/generated/source/r2/debug'
    }
}
kotlin compiles fine normally without this, and the R2 file is linked in the IDE fine, so without this the generated code otherwise interops well. Is there something that KSP could do differently to automatically pick up this generated code?
j
👍 we might be able to, but the fact this generated resource is from another plugin might make it unclear how we want to model such issue. Like how will KSP know which folder it should look into (in this case the compile works probably due to the fact that
build/generated
is picked up later after KSP processing. )
👍 1
t
@elihart Is this the same context that we talked about constValue?
e
yes, same context. However, I think I found a workaround approach that removes our requirement for constValue, by instead getting the KtAnnotationEntry from the KSAnnotationImpl, and using the embedded kotlin compiler to extract the dot qualified reference text in the annotation argument
Like how will KSP know which folder it should look into (in this case the compile works probably due to the fact that 
build/generated
 is picked up later after KSP processing. )
could KSP pick up sources in
build/generated
before processing starts?
t
It's doable in ksp to check whether it is a android compilation and if so, include the resource derived src dir. It is totally feasible as we already have some android specific logic. Just to make sure if I understand correctly: Can ksp read the content of R*.java? Or is it the compiler doesn't recognize the references generated by ksp? In other words, is it kspKotlin or compileKotlin failing?
If you can read the contents in ksp, then ksp probably already included that path. It's probably related to another issue that compiler can't resolve java files if the directory structure isn't "correct" ( some of the java files are not placed according to the package hierarchies).
e
it’s
kspKotlin
that is failing. The normal Android R class seems to be correctly processed, it is only the “R2” class that is generated by the custom gradle plugin that isn’t automatically included, and I need to manually include its source (this R2 class is just a workaround to reference resources in annotations, since the normal R class has non final field values)
t
I see. In general, KSP follows what
compileKotlin
see instead of picking up directly from
build/generated
. Does the custom compiler plugin register R2 to
compileKotlin
?
btw, kotlin compiler is picky about the path of java files. If they are not placed according to their package names, kotlin compiler (and therefore ksp) won't resolve them correctly.
e
The plugin uses gradle’s registerJavaGeneratingTask api. it does look like it generates the code in the right directory for its java package name, within
"generated/source/r2/${variant.dirName}"
I’ve found that using the workaround of explicitly adding this generated path to the kotlin source sets is not ideal, as gradle complains and disables build optimizations
Copy code
Gradle detected a problem with the following location: '...build/generated/source/r2/debug'. 

Reason: Task 'kspDebugKotlin' uses this output of task 'generateDebugR2' without declaring an explicit or implicit dependency. 

This can lead to incorrect results being produced, depending on what order the tasks are executed. 

Please refer to <https://docs.gradle.org/7.2/userguide/validation_problems.html#implicit_dependency> for more details about this problem.
So it would be nice if there was a better solution for KSP to pick up the same generated files automatically that android and kotlin tasks do
Working around this for now by registering task dependencies as well, with
Copy code
private fun Project.setUpR2TaskDependency() {
    // We have to submit another afterEvaluate, as the android plugin may be applied after the ProcessorPlugin, and not had a chance
    // to create sources yet.
    afterEvaluate {
        requireAndroidVariants().forEach {
            val r2Task = runCatching { project.tasks.named("generate${it.name.capitalize()}R2") }.getOrNull()
            if (r2Task != null) {
                project.tasks.named("ksp${it.name.capitalize()}Kotlin").dependsOn(r2Task)

                project.extensions.configure(KotlinAndroidProjectExtension::class.java) {
                    sourceSets.getByName("main")
                        .kotlin
                        .srcDir("build/generated/source/r2/${it.name}")
                }
            }
        }
    }
}
t
I'm trying to see if AGP has API to get the sources registered through registerJavaGeneratingTask. Please allow me to sync with Ivan to see if that's the right way to go. After all, picking up generated source is really hard to manage and we need to be extra careful.
e
yes, it’s definitely tricky. thank you for looking into it 🙏
t
Here is a fix: https://github.com/google/ksp/pull/606, which picks up all sources that were registered to the compilation task, same as kapt.
e
thank you!
t
The change is merged. To get sources from other plugins, please specify
allowSourcesFromOtherPlugins=true
in
Copy code
// build.gradle.kts
ksp {
    allowSourceFromOtherPlugins=true
}
We didn't enable it by default because we are unsure about the impact yet. For example, it may pick up outputs from kapt and it can be a disaster. We need to study more before we can flip the switch.
e
makes sense 🙏