I have an expect in my commonMain in a module with...
# multiplatform
s
I have an expect in my commonMain in a module with targets for jvm, android and iOS. Is there a way for me to make a sourceset which will combine both android and jvm, while still being able to use code in there which comes from jvm targets of libraries that I am using? More specifically, I want to use
kotlinx.datetime.toJavaLocalDate
which comes from inside kotlinx.datetime, which is defined in the jvm target. If I have the android and the jvm source sets separately, each one of them can import this function and just use it, no problem. However if I make a new sourceset like:
Copy code
applyDefaultHierarchyTemplate()
val jvmAndAndroidMain by creating {
 dependsOn(commonMain.get())
}
jvmMain.get().dependsOn(jvmAndAndroidMain)
androidMain.get().dependsOn(jvmAndAndroidMain)
it is not resolved since as I understand it can't figure out that both those targets are supposed to be able to call the jvm target of the kotlinx.datetime library. Perhaps there might be a way to make this happen by not using the
applyDefaultHierarchyTemplate
and doing something more custom?
o
This (assuming you're using the "com.android.kotlin.multiplatform.library" plugin)?
Copy code
applyHierarchyTemplate(KotlinHierarchyTemplate.default) {
        group("common") {
            group("jvmAndAndroid") {
                withJvm()
                withAndroidTarget()
            }
        }
    }
s
Heh I'm really out of date with kmp configuration it looks like, I haven't used this dsl before. The source code for this target will then live under a directory which I should name "jvmAndAndroidMain", right?
o
Well you could name it however you like. And yes, then it's just there.
s
Yeah yeah of course, I was just asking to make sure that it will have that
...main
suffix. Will give it a try tomorrow, thanks a lot Oliver!
kodee welcoming 1
j
fwiw we have something like that in Confetti
Copy code
applyDefaultHierarchyTemplate {
        common {
            group("mobile") {
                withIos()
                withAndroidTarget()
            }
        }
    }
🙇 1
✅ 1
o
And yes, "*Main" and "*Test" source sets will be there.
s
Really odd, but with this setup I am getting
No actual for expect declaration in module(s): androidMain
, so it does pickup that it should be fine for the jvm target, but not for the android target. I used
withAndroidTarget()
and I am using the new
com.android.kotlin.multiplatform.library
plugin, I am not on the old one. And in my setup I am calling
androidLibrary { ... }
to add the android target, which afaik is the way to do it. I might have to just revert to copy-pasting the code in jvm and android targets for now and figure this out some other time
o
What does your source set hierarchy look like? You can use this code in your build script to visualize it:
Copy code
fun Task.kotlinSourceSetsDiagram(name: String, predicate: (sourceSet: KotlinSourceSet) -> Boolean) {
    group = "help"
    description = "Prints a Mermaid diagram code displaying the Kotlin $name source set hierarchy."
    notCompatibleWithConfigurationCache("This task references source sets by its nature.")

    doLast {
        println("\nclassDiagram")
        val testSourceSets = kotlin.sourceSets.filter { predicate(it) }
        for (sourceSet in testSourceSets) {
            println("    class ${sourceSet.name}")
        }
        for (sourceSet in testSourceSets) {
            for (dependsOnSourceSet in sourceSet.dependsOn.map { it.name }) {
                println("    $dependsOnSourceSet <|-- ${sourceSet.name}")
            }
        }
        println("\nYou can display the above diagram in <https://mermaid.live/>\n")
    }
}

tasks.register("kotlinMainSourceSetsDiagram") {
    kotlinSourceSetsDiagram("main") { !Regex("[tT]est").containsMatchIn(it.name) }
}

tasks.register("kotlinTestSourceSetsDiagram") {
    kotlinSourceSetsDiagram("test") { Regex("[tT]est").containsMatchIn(it.name) }
}
s
Copy code
class androidMain
    class appleMain
    class appleTest
    class commonMain
    class commonTest
    class iosArm64Main
    class iosArm64Test
    class iosMain
    class iosSimulatorArm64Main
    class iosSimulatorArm64Test
    class iosTest
    class iosX64Main
    class iosX64Test
    class jvmAndAndroidMain
    class jvmAndAndroidTest
    class jvmMain
    class jvmTest
    class nativeMain
    class nativeTest
    commonMain <|-- androidMain
    nativeMain <|-- appleMain
    nativeTest <|-- appleTest
    iosMain <|-- iosArm64Main
    iosTest <|-- iosArm64Test
    appleMain <|-- iosMain
    iosMain <|-- iosSimulatorArm64Main
    iosTest <|-- iosSimulatorArm64Test
    appleTest <|-- iosTest
    iosMain <|-- iosX64Main
    iosTest <|-- iosX64Test
    commonMain <|-- jvmAndAndroidMain
    commonTest <|-- jvmAndAndroidTest
    jvmAndAndroidMain <|-- jvmMain
    jvmAndAndroidTest <|-- jvmTest
    commonMain <|-- nativeMain
    commonTest <|-- nativeTest
Wow yeah, androidMain just targets to commonMain, despite me having
Copy code
group("jvmAndAndroid") {
  withJvm()
  withAndroidTarget()
}
Somehow the androidTarget isn't being picked up there properly
Key point above being:
Copy code
...
commonMain <|-- androidMain
...
commonMain <|-- jvmAndAndroidMain
commonTest <|-- jvmAndAndroidTest
jvmAndAndroidMain <|-- jvmMain
jvmAndAndroidTest <|-- jvmTest
...
It might have to do with the fact that the android target is added from a different convention plugin than the one which applies the other targets. And there is some timing issue there where it's not picked up on time. Will tinker with it a bit more now
o
Maybe you could just try with a simple build script on an empty project first. I remember observing AGP lifecycle issues when setting up source sets.
s
Yeah exactly, I gotta work backwards from a working example.
Confetti uses
androidTarget()
actually, not
androidLibrary {}
. Makes me thing basically it only works with that. So I'd have to either go back to using the old android target, or find a way to apply the hierarchy properly using
androidLibrary {}
if there is such a way atm. I don't see anything in the DSL but perhaps there's something custom one can do
o
In
sourceSets
you can add a direct dependency:
Copy code
androidMain {
    dependsOn("jvmAndAndroidMain")
}
s
But that is exactly what we don't have to do if we use hierarchies properly right?
Ahhhh this looks to work fine, replacing withAndroidTarget:
Copy code
//withAndroidTarget()
withCompilations {
  it is KotlinMultiplatformAndroidCompilation
}
Since there is no DSL for the new android library target, it looks like you can use the lower level API
✔️ 2
🦜
Wait nvm. I am now back to being unable to resolve
import kotlinx.datetime.toJavaLocalDate
inside
jvmAndAndroidMain
despite both the android and the jvm targets individually being able to call this function. The jvm just normally, and the android target just by using the jvm target of kotlinx.datetime. I realize now it worked before just because it thought it was only a jvm target and not an android one. Time to give up and just copy-paste the code in both source sets xD
o
Yeah, Android is always a bit strange since it's JVM-like but not really JVM. So that may be the cause why there is no first-party support for such a source set hierarchy. But it would be cool to get a definitive answer on that. Maybe you could ask in #C19FD9681?
s
I could try asking yes.