Custom destination dir on Kotlin KSP gradle plugin - kotlin

I am working on a kotlin multiplatform project where i need to generate jvm and common code from single jvmMain module. But Kotlin KSP forces to use same destination dir which i'm generating from (ie: JvmMain to JvmMain or commonMain to metadata/commonMain). So, i can't generate code for both module from single module.
Example code:
// jvmMain - backend
#Router
class Server{
fun greeting():String{
// some backend works
return "Hello, StackOverflow"
}
}
// generated - JvmMain
fun router(){
routers.register(Server::greeting)
}
// generated - commonMain
Client.greeting():String{
// request to server
}
I need to setup destination dir for each platform with Kotlin KSP.

Related

Cannot generate a KMP library correctly and use it in a KMM library project

I am currently working on a project that implements the following steps:
generation of a KMP project for an API using open api generator and a swagger description file
package the common, jvm and iOS libraries
create a KMM project
add the libraries generated during the step 2 and use it
The step number 1 works correctly.
For the step number 2, I use the assemble gradle task. This task generates the following binaries in the build/libs folder:
kotlin-client-iosarm64-1.0.0-metadata.jar
kotlin-client-iosx64-1.0.0-metadata.jar
kotlin-client-js-1.0.0.jar
kotlin-client-jvm-1.0.0.jar
kotlin-client-metadata-1.0.0.jar
For the step 3, I use Android Studio and the "Kotlin Multiplatform Mobile" plugin from JetBrains and I select the "Kotlin Multiplatform Library" template.
Currently, I do not deploy the generated binaries on a maven repository. So for the step number 4, I copy/past the jar files into a "libs" folder and I reference them into the build.gradle.kts file:
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.3")
implementation("io.ktor:ktor-client-core:2.1.3")
implementation("io.ktor:ktor-client-serialization:2.1.3")
implementation("io.ktor:ktor-client-content-negotiation:2.1.3")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.3")
implementation(files("libs/kotlin-client-metadata-1.0.0.jar"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-cio-jvm:2.1.3")
implementation(files("libs/kotlin-client-jvm-1.0.0.jar"))
}
}
val androidUnitTest by getting
val iosX64Main by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:2.1.3")
implementation(files("libs/kotlin-client-iosx64-1.0.0-metadata.jar"))
}
}
val iosArm64Main by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:2.1.3")
implementation(files("libs/kotlin-client-iosarm64-1.0.0-metadata.jar"))
}
}
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
Using this configuration, it seems to work only for the android part of my KMM library project.
In fact, in my KMM library project, I can use classes from my KMP project into classes of the "androidMain" folder. But when I am trying to use the same classes into classes of the "commonMain" or the "iosMain" folders, it does not work. Classes cannot be find (Unresolved reference).
What is wrong with my project? How can I package my KMP library in order to use it in a KMM library?
The main reason that you can't use your library in iOS and common code, because files kotlin-client-*-metadata.jar are just metadata. For multiplatform and native code Kotlin use special format of libraries *.klib. Please take a look at article that explains this.
If you don't want to publish your library and want to use local setup, no need to build the library, this is not a Gradle way. Place your library near your main code and take advantage of multi project builds and add dependency to your lib project:
val commonMain by getting {
dependencies {
//...
implementation(project(":kotlin-client"))
}
}
// no need to add to another source sets, if your library already supports all targets
Or publish it in local Maven repository.

how to add gradle soruce set in kotlin Kotlin Multiplatform Mobile

there is a Gradle task that generates some class. , I want those classes to be in the common KMM file
sourceSets {
val commonMain by getting{
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.3.2")
}
}
how to add the path "$buildDir/generated/src/main/kotlin" to the commonMain

How to import a file from another directory in Kotlin?

I have a gradle kotlin project, and I'm generating a kotlin file from a Rust project, so it ends up in a totally different place with no gradle project structure, etc.
How do I import this file into my gradle project?
It has its own package but it's a completely standalone file. This is my gradle file:
rootProject.name = "my_project"
include("app")
It's a desktop project, NOT android.
My build.gradle.kts:
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
id("org.jetbrains.kotlin.jvm") version "1.5.31"
// Apply the application plugin to add support for building a CLI application in Java.
application
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
// Align versions of all Kotlin components
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
// Use the Kotlin JDK 8 standard library.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// This dependency is used by the application.
implementation("com.google.guava:guava:30.1.1-jre")
// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
application {
// Define the main class for the application.
mainClass.set("my_project.ffi.AppKt")
}
Adding the following code to your build.gradle.kts should do the trick (tested with Gradle 7.3.2):
// TODO: replace this dummy task with the task from your Rust project which
// generates the Kotlin source directory. Make sure that the generated
// directory (with the Kotlin file(s)) is the task output directory.
val rustTask by tasks.registering(Copy::class) {
// To test this, I had simply put a Kotlin file into this "somewhere"
// directory.
from("somewhere")
into(temporaryDir)
}
sourceSets {
main {
java {
srcDir(rustTask)
}
}
}
tasks {
compileKotlin {
dependsOn(rustTask)
}
}
So, we’re simply adding the generated sources as an additional source directory to the default SourceSet which is consumed by the compileKotlin task. In addition, we make sure that the sources are generated before compileKotlin runs.

How to share Java code between Android and JVM targets (with Kotlin Multiplatform)?

I am trying to share Java code between Android and JVM targets using Kotlin Multiplatform feature (sample project: https://github.com/dmitrykolesnikovich/accessJavaCode-issue)
Simply saying, ":library1" and ":library2" both are Kotlin multiplatform libraries targeting JVM and Android. ":library2" depends on ":library1". They both uses Kotlin and Java. ":library2" is intended to be dependency of 1) Android application and 2) desktop (JavaFX) application. That's why 1) AAR artifact and 2) JAR artifact both are needed (?) - so I use 1) Android target and 2) JVM target for both ":library1" and ":library2".
The problem is that, when I have Java code in ":library1"
public class JavaCode {} // JavaCode.java
And Kotlin code in ":library2" that depends on ":library1"
class AccessJavaCode : JavaCode() // AccessJavaCode.kt
Android target is OK with recognizing Java but JVM target is not:
> Task :library2:compileKotlinJvm FAILED
e: AccessJavaCode.kt: (3, 38): Unresolved reference: JavaCode
In gradle config I define two plugins: kotlin-multiplatform and com.android.library:
apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
androidMain {
dependsOn jvmMain
}
}
}
android {
compileSdkVersion 28
sourceSets {
main {
java.srcDirs += "src/jvmMain/kotlin" // Android target recognizes Java with this
manifest.srcFile "src/androidMain/AndroidManifest.xml"
}
}
}
I am pretty sure it’s something simple with my gradle file. Many thanks for your help guys.
~~~~ EDIT ~~~~
Another workaround the issue without losing the ability to generate an android archive .aar for the library1 would be to make a new version of this same library depending on precompiled artefacts of a splitted version of the original library1.
So you would end up with a multi-module gradle project, something like this:
library1-jvm with java plugin enabled
library1-android with android plugin enabled
library1 which will depends on prebuilt library1-jvm.jar and library1-android.aar
You could use whatever you prefer to publish those artefacts, but a local maven repo should work just fine!
That would mean replacing:
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
androidMain {
dependsOn jvmMain
}
}
}
with:
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
api "com.company:library-jvm:1.0.0"
}
}
androidMain {
dependsOn jvmMain
dependencies {
api "com.company:library-android:1.0.0"
}
}
}
}
That way, you don't need the java plugin at all in the final library1, because all the java code will already be built in a separate step.
Hence library1 could keep both the JVM and Android targets
~~~~~~~~~~~~~
In order to fix your issue, you need to:
Split your build.gradle configuration in order to have one config per library, this will be needed because you cannot enable the java plugin and the android at the same time for the same Gradle project, or you will end up with the following Error: The 'java' plugin has been applied, but it is not compatible with the Android plugins
Enable the java plugin in your library1 project if you want your JVM target to recognize your Java source files.
The Java source files need to be placed in the sibling directories java of the kotlin source roots.
More infos: Kotlin docs for java-support-in-jvm-targets
I also created a pull request solving your issue.
The downside of this approach is that you will not be able to generate an android archive .aar for the library1, but I guess using the java archive .jar in your android project should not be a problem at all.
library1/build.gradle:
apply plugin: "kotlin-multiplatform"
kotlin {
jvm {
withJava()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
}
}
library2/build.gradle:
apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"
kotlin{
jvm()
android()
sourceSets {
jvmMain.dependencies {
api project(":library1")
}
androidMain {
dependsOn jvmMain
}
}
}
android {
compileSdkVersion 28
sourceSets {
main {
java.srcDirs += "src/jvmMain/kotlin"
manifest.srcFile "src/androidMain/AndroidManifest.xml"
}
}
}

How to add default `jvm` sourceSet to `android` target in Kotlin Multiplatform?

With Kotlin/Multiplatform 1.3.11 i had the following build.gradle:
fromPreset(presets.jvm, 'jvm')
fromPreset(presets.android, 'jvm') // reusing jvm sources for android platform impl
So basically i wanted to reuse all code for jvm target for android target.
With just-released Kotlin/Multiplatform 1.3.20 now i'm getting an error:
The target 'jvm' already exists, but it was not created with the 'android' preset. To configure it, access it by name in kotlin.targets or use the preset function 'jvm'
Open File
I've tried to migrate to new syntax:
jvm()
android() {
sourceSets.add(kotlin.targets.jvm.compilations.main.defaultSourceSet)
}
but it does not reuse jvm defaultSourceSet for Android target:
Expected class 'URL' has no actual declaration in module
so it does not use default jvm sourceset actually and not throwing Groovy syntax error.
What's wrong?
Actually, the best solution is to have some common sourceset and one for each platform:
commonJvmMain {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
}
}
jvmMain {
dependsOn commonJvmMain
}
androidMain {
dependsOn commonJvmMain
}
Put shared code in commonJvmMain sourceset directory.
https://github.com/JetBrains/kotlin-native/issues/2577