when working with KMP, the docs suggest to apply t...
# ksp
z
when working with KMP, the docs suggest to apply the KSP processors you need to both common and the specific targets: https://kotlinlang.org/docs/ksp-multiplatform.html
Copy code
dependencies {
    add("kspCommonMainMetadata", project(":test-processor"))
    add("kspJvm", project(":test-processor"))
}
However, I'm finding this to have a couple problems 1. The
kspCommonMainMetadata
task never appears to run unless explicitly depended on. This has been pointed out in the issue tracker it seems: https://github.com/google/ksp/issues/567#issuecomment-2609469736 2. It appears that processors in specific targets run on all sources (the complete set of target and common sources), which results in redeclaration/duplication issues for any sources in
commonMain
. My questions on this I guess are • Should
kspCommonMainMetadata
be configured to always run? Or at least if it has processors on its classpath? Seems a longstanding footgun that it's inert by default without some manual gradle fiddling • Should there be a way to limit processing in specific targets to only run on sources defined in that source set, rather than the full set? I don't see a means currently for processors to handle this scenario (no means of detecting source set of origin, etc) In general, it appears strongly to me like KSP on common sources is essentially only viable if you don't use the same processors in multiple source sets and know how to do the above gradle task wiring trick to force it to run. Happy to file issues for each of the above bullets too
plus one 7
r
I have the same problem; I don't quite understand how a processor should handle common code without requiring users to use Gradle tricks. I was looking at the Kotlin Inject - Expect actual code, which can generate
expect actual
or handle it in
common main
using Gradle tricks, but I still don't quite understand it. @eygraber I see your name in the latest commits, could you instruct me on how a processor should support both configurations?
> I don't see a means currently for processors to handle this scenario (no means of detecting source set of origin, etc) Theoretically, you could, looking at the list of platforms in the current round. Environment.platforms but I haven't tried it**
e
It's been a while since I've had the whole structure in my head, but I think it depends on what you're generating. I'll try to organize my thoughts into something more coherent, but I wrote a summary of the problem in the kotlin-inject docs
when working with KMP, the docs suggest to apply the KSP processors you need to both common and the specific targets: https://kotlinlang.org/docs/ksp-multiplatform.html
That doesn't seem correct. I tried doing that originally, and got redeclaration/duplication issues. I thought I would be able to get it to work by detecting what target the processor was running for, but IIRC that wasn't possible. I think the rule of thumb is that if your annotated code is in the common source set, and doesn't reference anything that is expect/actual, then you should add the KSP processors to
kspCommonMainMetadata
The kspCommonMainMetadata task never appears to run unless explicitly depended on
If I'm using
kspCommonMainMetadata
I always do this (I use a convention plugin):
Copy code
kotlin {
    // I don't remember if this is needed anymore
    commonMain {
        kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
    }
}

tasks.withType<KspAATask>().configureEach {
    if (name != "kspCommonMainKotlinMetadata") {
        dependsOn("kspCommonMainKotlinMetadata")
    }
}
It appears that processors in specific targets run on all sources (the complete set of target and common sources), which results in redeclaration/duplication issues for any sources in commonMain
Correct, which is why I'm pretty sure it's is XOR for a processor to be used in common / targets.
r
Yeaaah, redeclaration issue, in my case, actual without expect because generate code in commonMain. So, proposes continuing to use the Gradle trick (kspCommonMainMetadata + dependsOn) or recommending using this:
Copy code
dependencies {
    // don't apply on common main because we going to generate on each platform
    // kspCommonMainMetadata(projects.compiler)
    add("kspAndroidDebug", projects.compiler)
    add("kspAndroidRelease", projects.compiler)
    add("kspJvm", projects.compiler)
    add("kspJs", projects.compiler)
    // all targets ...
}
It's not as simple as in JVM for new users 😕
e
Yeah it definitely could be simpler, and it's probably something that the KSP Gradle plugin could address. Here's how I handle it for my projects - https://github.com/eygraber/gradle-conventions/blob/master/conventions-plugin/src/main/kotlin/ksp.kt
r
Great, thanks. In kotlin 2.3.0-beta2, KGP have a new generated code API https://kotlinlang.org/docs/whatsnew-eap.html#new-api-for-registering-generated-sources-in-gradle-projects If want to use that, change to
generatedKotlin.srcDir("...")
😉
💡 1
t
+ @Hakan Mehmed
Some backgrounds on KSP + multiplatform: I guess
kspCommonMainMetadata
didn't run because
compileKotlinCommonMainMetadata
didn't run. This is rooted from Kotlin's compilation model, which KSP follows. In KMP, common files are compiled with different target files multiple times. They are compiled N+ times if it is shared among N targets. The "+" comes from metadata compilation such as
compileKotlinCommonMainMetadata
.
compileKotlinCommonMainMetadata
can be skipped when there is only JVM target. By design, generated files from common files are not visible to downstream target compilations. Processors (and kotlinc) are expected to re-generate files (and binaries in the case of kotlinc) in each compilation. Unless the generated files are wired together from different tasks manually in the build script, there shouldn't be redeclaration. (or bugs in KSP, of course) Back to Zac's questions: 1. Generated files from
kspCommonMainMetadata
would be useless unless
compileKotlinCommonMainMetadata
also runs. They should not be consumed by other tasks, at least with current build model. 2. I thought
add("kspJvm", project(":test-processor"))
is exactly designed for that. Users can specify which task (slightly different to source sets though) they want processors to run. It would be an regression / bug if that doesn't work.
kodee loving 1
m
I need a high level overview of compilation vs sourceSet vs target badly. I've been looking at all this for multiple years now but I'm still unsure how this all works
Processors (and kotlinc) are expected to re-generate files (and binaries in the case of kotlinc) in each configuration.
@Ting-Yuan Huang did you mean "in each compilation" here?
t
Yes
👍 1
z
what triggers
compileKotlinCommonMainMetadata
to run? As best I can tell, that task never seems to run
It would be an regression / bug if that doesn't work.
That is the observed behavior in this project: https://github.com/JvmName/sprakbund
t
It should run as long as you have multiple targets, and one of them is not JVM.
z
ah, so this behavior is specific to a KMP project with only one target?
It should run as long as you have multiple targets, and one of them is not JVM.
That's quite the asterisk 🙂
m
Does it even matter to run the processor for
commonMainMetadata
then? What is the target used for? IDE support maybe?
z
at the very least it feels like the ksp-kmp docs should document these behaviors
👍 1
e
I'm still a touch confused. Are we supposed to use
kspCommonMainMetadata
AND the target specific
kspIosArm64
(for example)? How exactly do we solve the redeclaration issues? I'm very new to KMP and im having a hard time setting up everything