Hello! I’ve developed a code generation processor ...
# ksp
g
Hello! I’ve developed a code generation processor based on annotations using KSP. Initially, I designed it for the JVM, and after successful testing, I want to integrate it into a KMM project. My goal is to generate helper code in the
commonMain
source set so that it can be used in
iosMain
, be it by dependingOn
commonMain
or if the generated files are created/moved in/to
build/generated/ksp/iosMain
(even better). However, I’ve encountered some challenges. KSP does not currently support Native, and I can’t make
commonMain
implement my
lib-ksp
directly due to issues with iOS targets. I came across a helpful

video

that suggests porting the library to multiplatform instead of just targeting JVM. In our KMM project’s “shared” module, we should apply the “android.library” plugin and include
lib-ksp
in the dependencies outside of the Kotlin source sets. I attempted this, and it worked for
androidMain
but not for
commonMain
, which was not the outcome I expected. In the end I see no difference from making
androidMain
implement this dependencies. I’ve been studying how @Rick Clephas achieved a similar setup in his KMP-NativeCoroutines library, but I haven’t been able to reproduce it yet. Can anyone provide guidance on the current solution or a workaround for this issue? Your help would be greatly appreciated. Thanks!
r
I am not really sure what challenges you are facing. What do you mean by "KSP does not currently support Native"? Also what is "lib-ksp" and what issues with the iOS targets are you referring to?
g
Hello Rick and thanks for your time. This is the
build.gradle.kts
of my library (let’s call it
lib-ksp
):
Copy code
plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    explicitApi()
    jvmToolchain(11)

    jvm {
        compilations.all {
            kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=all")
        }
    }
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    val commonMain by sourceSets.getting {
        dependencies {
            implementation(libs.ksp.api)
        }
    }

    val jvmTest by sourceSets.getting {
        dependencies {
            implementation(libs.test.junit)
            implementation(libs.test.kotlin)
            implementation(libs.test.kotlinCompile)
            implementation(libs.test.kotlinCompileKsp)
        }
    }
}
And when I try to import it in a KMM project:
Copy code
val commonMain by getting {
            dependencies {                
                implementation("lib-ksp")
                configurations["ksp"].dependencies.add(implementation("lib-ksp"))
            }
        }

        val iosMain by creating {
            dependsOn(commonMain)
        }
I get this output:
Copy code
> Could not resolve com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13.
  Required by:
      project :shared > project :lib-ksp
   > No matching variant of com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13 was found. The consumer was configured to find a library for use during 'kotlin-api', preferably optimized for non-jvm, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native', attribute 'org.jetbrains.kotlin.native.target' with value 'ios_arm64' but:
       - Variant 'apiElements' capability com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13 declares a library for use during compile-time, preferably optimized for standard JVMs:
           - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
           - Other compatible attribute:
               - Doesn't say anything about org.jetbrains.kotlin.native.target (required 'ios_arm64')
       - Variant 'runtimeElements' capability com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13 declares a library for use during runtime, preferably optimized for standard JVMs:
           - Incompatible because this component declares a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'jvm' and the consumer needed a component, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'native'
           - Other compatible attribute:
               - Doesn't say anything about org.jetbrains.kotlin.native.target (requoired 'ios_arm64')
What am I doing wrong?
r
Thanks for the clarification. So
lib-ksp
is your KSP plugin, right? In that case it should be a plain Kotlin JVM project. KSP only runs on the JVM (during compilation). If your library also provides Kotlin code that should be used in the client project, you'll need to create a separate Kotlin Multiplatform module for that. E.g. KMP-NativeCoroutines has a
kmp-nativecoroutines-annotations
module that contains multiplatform annotations that can/should be used in the client project. The actual KSP plugin is in the
kmp-nativecoroutines-ksp
module (which is JVM only).
g
So
lib-ksp
is your KSP plugin, right?
It’s equivalent to your
kmp-nativecoroutines-ksp
module, not
kmp-nativecoroutines-gradle-plugin
, in other words, it’s where I have my symbol processing logic.
If your library also provides Kotlin code that should be used in the client project, you’ll need to create a separate Kotlin Multiplatform module for that.
Alright, I’ve noticed that you’ve also separated the annotations into a KMP module, and this one covers all the desired targets. Now, correct me if I’m wrong, I believe your “glue” resides in the
KmpNativeCoroutinesPlugin: KotlinCompilerPluginSupportPlugin
which is your gradle plugin, correct? Because your sample
shared
imports this logic by doing:
id("com.rickclephas.kmp.nativecoroutines")
. I have never created a gradle plugin, only conventions, but I believe the logic is the same, correct? Sorry if I’m misunderstanding these concepts, but it’s my first time trying, and your project is the best example I’ve found so far. 😅
Just a quick update, Still testing, but this configuration generated code inside `iosSimulatorArm64Main`:
Copy code
val iosMain by creating {
    dependsOn(commonMain)
    dependencies {
        implementation("lib-annotations")
    }
}

listOf(iosX64, iosArm64, iosSimulatorArm64).forEach { target ->
    target.binaries.framework { baseName = "shared" }
    getByName("${target.targetName}Main") { dependsOn(iosMain) }

    val kspConfigName = "ksp${target.name.replaceFirstChar { it.uppercaseChar() }}"
    dependencies.add(kspConfigName, "lib-ksp")
}
r
Yeah that's correct. Although you don't really need the Gradle plugin (KMP-NativeCoroutines needs it because it's also a Kotlin compiler plugin). Your updated configuration is indeed all that's needed to use a KSP plugin.
👌🏼 1
g
Thanks for your help Rick! One last detail, the code generated, for example inside
iosSimulator64Arm
, is not capable of "seeing" the code from
iosMain
, for example, some imports cannot be resolved. My
build.gradle
already specifies that all ios Targets depend on
iosMain
. Shouldn't be enough? Maybe I'm missing something 🤔
r
Yeah that should be enough. You aren’t using the experimental K2 compiler, right?
g
Nop, just these configurations:
Copy code
#KMP
kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.mpp.stability.nowarn=true
org.jetbrains.compose.experimental.uikit.enabled=true
xcodeproj=./iosApp
r
In that case I am not sure why those imports are unresolved
g
Ok, I’ll deep dive a bit to see if I find something, if not I will take this question to another channel (i believe it may not be related to ksp). Thank you so much for you help 🙂
👍 1
Turns out I can use it on iosApp. I can even debug the code and the classes effectively can see each other. Could it be an IDE problem? 🤔
r
g
yup on point! didn’t know how I missed that. Thanks again 🙂
👍🏻 1