https://kotlinlang.org logo
a

Alexandre Brown

12/17/2021, 6:12 PM
Hello, is it possible to reference an interface (that has no impl) in a kotlin MPP library commonMain from a different Java/Kotlin only Module? myJavaModule -> MPP Library/commonMain/MyInterface Thanks
j

Javier

12/17/2021, 6:14 PM
if the kmp project has jvm target, yes
a

Alexandre Brown

12/17/2021, 6:14 PM
It has, but I'm not sure I understand how to do it because it is not working
The Core (KMP library)
The Kotlin/Jvm Module (it has references to the Core/UseCases module)
Yet inside the Kotlin/Jvm module I cannot reference the interfaces...
@Javier Here is a better picture of the structure. I would like
MyUseCaseImpl.kt
to be able to implement the
UseCaseInterface.kt
j

Javier

12/17/2021, 6:22 PM
you can reference it
a

Alexandre Brown

12/17/2021, 6:23 PM
What do you mean exactly? Could you elaborate? It does not let me reference it (see photo above).
@Big Chungus Hey if you have an idea by any chance I'd love your feedback.
👀 1
b

Big Chungus

12/17/2021, 6:36 PM
Let me have a look at UseCaseInterface, MyUseCaseImpl and dependencies of root-clasification (in build.gradle.kts)
🆗 1
a

Alexandre Brown

12/17/2021, 6:42 PM
ok give me a moment
@Big Chungus Hopefully this helps
b

Big Chungus

12/17/2021, 6:47 PM
add this to the top of
MyUseCaseImpl
Copy code
import UseCaseInterface
Even though you have no package declared, it still needs to be imported
There's no way to add things to global context like stdlib
a

Alexandre Brown

12/17/2021, 6:49 PM
But we cannot create packages in non-jvm environment no?
I tried that and simply having the import you suggested but it does not work. It keeps suggesting me to add coreusecases:commonMain as a dependency
It seems like the issue can be boiled down to "referencing a KMP library from a jvm only module"
b

Big Chungus

12/17/2021, 7:02 PM
Shouldn't be an issue, really. Can you send me the content of build.gradle.kts of both modules?
a

Alexandre Brown

12/17/2021, 7:05 PM
backend/road-classification/usecases/build.gradle.kts
Copy code
dependencies {
	implementation(project(":core:entities"))
	implementation(project(":core:usecases"))
}
backend/road-classification/build.gradle.kts
EMPTY
backend/build.gradle.kts
Copy code
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

val testImplementation by configurations
val implementation by configurations

val kotlinxCoroutinesVersion: String by project
val kotlinxDatetimeVersion: String by project
val kotestVersion: String by project
val mockkVersion: String by project
val logbackVersion: String by project
val kodeinVersion: String by project
val ktorVersion: String by project

val entryPointPath = "MainKt"

plugins {
	application
	kotlin("jvm") version "1.6.0" apply false
	kotlin("plugin.serialization") version "1.6.0" apply false
}

application {
	mainClass.set(entryPointPath)
}

repositories {
	maven {
		url = uri("<https://maven.pkg.jetbrains.space/public/p/ktor/eap>")
	}
}

dependencies {
	project(":backend:road-classification").dependencyProject.allprojects.forEach(::implementation)

	implementation("io.ktor:ktor-serialization:$ktorVersion")
	implementation("io.ktor:ktor-server-core:$ktorVersion")
	implementation("io.ktor:ktor-server-cio:$ktorVersion")
	implementation("io.ktor:ktor-server-locations:$ktorVersion")
	implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
	implementation("io.ktor:ktor-server-websockets:$ktorVersion")
	implementation("ch.qos.logback:logback-classic:$logbackVersion")
	implementation("org.kodein.di:kodein-di-framework-ktor-server-jvm:$kodeinVersion")

	testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
}

tasks.withType<KotlinCompile> {
	kotlinOptions.freeCompilerArgs += listOf("-Xopt-in=io.ktor.server.locations.KtorExperimentalLocationsAPI")
}

allprojects {

	apply {
		plugin("org.jetbrains.kotlin.jvm")
	}

	tasks.withType<KotlinCompile> {
		kotlinOptions.jvmTarget = "11"
	}

	dependencies {
		implementation("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDatetimeVersion")
		implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")

		testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
		testImplementation("io.kotest:kotest-property:$kotestVersion")
		testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
		testImplementation("io.mockk:mockk:$mockkVersion")
	}

	tasks.withType<Test> {
		useJUnitPlatform()
	}
}
core/usecases/build.gradle.kts
EMPTY
core/build.gradle.kts
Copy code
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

plugins {
	kotlin("multiplatform") version "1.6.0" apply false
}

subprojects {
	apply {
		plugin("org.jetbrains.kotlin.multiplatform")
	}

	extensions.getByType<KotlinMultiplatformExtension>().apply {
		/* Targets configuration omitted.
		*  To find out how to configure the targets, please follow the link:
		*  <https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets> */

		jvm {
			compilations.all {
				kotlinOptions.jvmTarget = "11"
			}
			withJava()
			testRuns["test"].executionTask.configure {
				useJUnitPlatform()
			}
		}
		val hostOs = System.getProperty("os.name")
		val isMingwX64 = hostOs.startsWith("Windows")
		val nativeTarget = when {
			hostOs == "Mac OS X" -> macosX64("native")
			hostOs == "Linux" -> linuxX64("native")
			isMingwX64 -> mingwX64("native")
			else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
		}

		sourceSets {
			val commonMain by getting {
				dependencies {
					implementation(kotlin("stdlib-common"))
				}
			}
			val commonTest by getting {
				dependencies {
					implementation(kotlin("test-common"))
					implementation(kotlin("test-annotations-common"))
				}
			}
			val jvmMain by getting
			val jvmTest by getting
			val nativeMain by getting
			val nativeTest by getting
		}
	}
}
b

Big Chungus

12/17/2021, 7:07 PM
Ok, so turns out you are not actually applying kotlin plugin to any of the modules
in your allprojects blocks, you have this
Copy code
apply {
		plugin("org.jetbrains.kotlin.jvm")
	}
Which does absolutely nothing To correctly apply plugins you need this
Copy code
apply(plugin = "plugin.id")
Which in turn explains why your subprojects are not able to resolve correct kotlin artefacts via gradle metadata
a

Alexandre Brown

12/17/2021, 7:08 PM
Ok wow let me try!
Hmm not sure If I applied it correctly
Copy code
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension

plugins {
	kotlin("multiplatform") version "1.6.0" apply false
}

subprojects {
	apply(plugin = "org.jetbrains.kotlin.multiplatform")

	extensions.getByType<KotlinMultiplatformExtension>().apply {
		/* Targets configuration omitted.
		*  To find out how to configure the targets, please follow the link:
		*  <https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets> */

		jvm {
			compilations.all {
				kotlinOptions.jvmTarget = "11"
			}
			withJava()
			testRuns["test"].executionTask.configure {
				useJUnitPlatform()
			}
		}
		val hostOs = System.getProperty("os.name")
		val isMingwX64 = hostOs.startsWith("Windows")
		val nativeTarget = when {
			hostOs == "Mac OS X" -> macosX64("native")
			hostOs == "Linux" -> linuxX64("native")
			isMingwX64 -> mingwX64("native")
			else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
		}

		sourceSets {
			val commonMain by getting {
				dependencies {
					implementation(kotlin("stdlib-common"))
				}
			}
			val commonTest by getting {
				dependencies {
					implementation(kotlin("test-common"))
					implementation(kotlin("test-annotations-common"))
				}
			}
			val jvmMain by getting
			val jvmTest by getting
			val nativeMain by getting
			val nativeTest by getting
		}
	}
}
Copy code
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

val testImplementation by configurations
val implementation by configurations

val kotlinxCoroutinesVersion: String by project
val kotlinxDatetimeVersion: String by project
val kotestVersion: String by project
val mockkVersion: String by project
val logbackVersion: String by project
val kodeinVersion: String by project
val ktorVersion: String by project

val entryPointPath = "MainKt"

plugins {
	application
	kotlin("jvm") version "1.6.0" apply false
	kotlin("plugin.serialization") version "1.6.0" apply false
}

application {
	mainClass.set(entryPointPath)
}

repositories {
	maven {
		url = uri("<https://maven.pkg.jetbrains.space/public/p/ktor/eap>")
	}
}

dependencies {
	project(":backend:road-classification").dependencyProject.allprojects.forEach(::implementation)

	implementation("io.ktor:ktor-serialization:$ktorVersion")
	implementation("io.ktor:ktor-server-core:$ktorVersion")
	implementation("io.ktor:ktor-server-cio:$ktorVersion")
	implementation("io.ktor:ktor-server-locations:$ktorVersion")
	implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion")
	implementation("io.ktor:ktor-server-websockets:$ktorVersion")
	implementation("ch.qos.logback:logback-classic:$logbackVersion")
	implementation("org.kodein.di:kodein-di-framework-ktor-server-jvm:$kodeinVersion")

	testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
}

tasks.withType<KotlinCompile> {
	kotlinOptions.freeCompilerArgs += listOf("-Xopt-in=io.ktor.server.locations.KtorExperimentalLocationsAPI")
}

allprojects {

	apply(plugin = "org.jetbrains.kotlin.jvm")

	tasks.withType<KotlinCompile> {
		kotlinOptions.jvmTarget = "11"
	}

	dependencies {
		implementation("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDatetimeVersion")
		implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")

		testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
		testImplementation("io.kotest:kotest-property:$kotestVersion")
		testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
		testImplementation("io.mockk:mockk:$mockkVersion")
	}

	tasks.withType<Test> {
		useJUnitPlatform()
	}
}
Does not seem to solve the issue 😕
b

Big Chungus

12/17/2021, 7:15 PM
send me build file of the module where you want to have core as a dependency. In the ones above i don't see it added as a dependency
a

Alexandre Brown

12/17/2021, 7:16 PM
Copy code
dependencies {
	implementation(project(":core:entities"))
	implementation(project(":core:usecases"))
}
b

Big Chungus

12/17/2021, 7:17 PM
That's not the buildfile and I cannot see them in either of the above snippets of buildfiles
a

Alexandre Brown

12/17/2021, 7:17 PM
Here is the entire project, it's pretty new so I can send it directly if that's better
j

Javier

12/17/2021, 7:17 PM
you can set packages to non jvm targets
b

Big Chungus

12/17/2021, 7:17 PM
Is it public on github by any chance?
a

Alexandre Brown

12/17/2021, 7:17 PM
Here I sent the zip, does it show up on slack ?
b

Big Chungus

12/17/2021, 7:18 PM
GH is just easier to work with 😄
Looking at the zip now
🙏 1
a

Alexandre Brown

12/17/2021, 7:22 PM
We use gitlab private repos but I can upload it to GH since it's fresh if that's easier for you
b

Big Chungus

12/17/2021, 7:22 PM
Nah, zip will do
Here's your issue
Copy code
Circular dependency between the following tasks:
:backend:road-classification:usecases:classes
\--- :backend:road-classification:usecases:compileJava
     +--- :backend:road-classification:usecases:compileKotlin
     |    \--- :backend:road-classification:usecases:jar
     |         +--- :backend:road-classification:usecases:classes (*)
     |         +--- :backend:road-classification:usecases:compileKotlin (*)
     |         \--- :backend:road-classification:usecases:inspectClassesForKotlinIC
     |              +--- :backend:road-classification:usecases:classes (*)
     |              \--- :backend:road-classification:usecases:compileKotlin (*)
     \--- :backend:road-classification:usecases:jar (*)
a

Alexandre Brown

12/17/2021, 7:41 PM
hmmmm
backend:road-classification:usecases
references core which references nobody...
b

Big Chungus

12/17/2021, 7:44 PM
There's something leaking through somewhere in your many allprojects and subprrojects blocks. One of the reasons why those are not recommended to use.
Gotta go now, but I'll try to help tomorrow if you cannot figure it out by then.
🙏 1
👍 1
a

Alexandre Brown

12/17/2021, 7:45 PM
I didnt know it was bad practice, should I duplicate the code inside each sub module's build.gradle.kts instead?
b

Big Chungus

12/17/2021, 7:46 PM
Not necessarily. You can use precompiled plugins or convention plugins to share logic (have a look at buildSrc in my projects)
a

Alexandre Brown

12/17/2021, 7:46 PM
Ok will do, thanks again!
@Big Chungus I just fixed the issue. It was very sneaky. I had this in the root
build.gradle.kts
Copy code
subprojects {
    group = "com.mdai"
    version = "1.0.0"
    
    repositories {
        mavenCentral()
    }
}
The fact that all sub projects had the same group caused a name clash. Moving this out of the root
build.gradle.kts
Copy code
group = "com.mdai"
version = "1.0.0"
And putting it inside the
core/build.gradle.kts
and inside
backend/build.gradle.kts
and of course making sure they both have unique group names fixed my issue. Eg:
backend build.gradle.kts
:
Copy code
subprojects {

	group = "com.mdai.backend"
	version = "1.0.0"
core build.gradle.kts
:
Copy code
subprojects {

	group = "com.mdai.core"
	version = "1.0.0"
It actually didn't make sense to have a single group name for multiple apps/libs, I suspect this was a leftover from the initial project setup 🤦 Thanks again for your help!
b

Big Chungus

12/17/2021, 9:17 PM
I'm glad it works, but group actually should be the same
This way it "groups" related artefacts together. Your issue was non-uniqye module names
e.g. core:usecases and backend:usecases both produce identically named artefacts
a

Alexandre Brown

12/17/2021, 9:25 PM
The thing is,
backend
,
core
and
library
could all be in separate repos if I wanted to. For simplicity I put them in the same repo since they both share
core
. But I see
backend
as being an artifact,
core
as being another one and
library
as another. They can all be deployed independently.
But yes if we keep the same group name then the clash comes from the same module name. This is how I discovered it, I tried creating an interface in another module of
core
(eg:
core/entities
) and I was able to reference it indeed. This is how I figured out it had to do with a name clash.
But maybe my understanding of what
group
represents is not right. What do you think? I could also keep the same group and have the module named differently like
core/core-usecases
instead of
core/usecases
b

Big Chungus

12/17/2021, 9:50 PM
Well maven artefact is defined by group + name Knowing that, you need to ensure that all modules under the same group have unique names (not just paths)
I think for you it would make most sense to have three subgroups containing multiple modules com.mdai.backend com.mdai.core com.mdai.library The modules in each of the subgroup would share the group, but have unique names within that group.
Which is close to where you're at already
a

Alexandre Brown

12/17/2021, 9:54 PM
Yes this is pretty much what I ended up having ! 🙂
Thanks for sharing your thoughts, it really helped
b

Big Chungus

12/17/2021, 9:55 PM
I'm glad.
cheers 1
I actually got stuck on this exact same issue with unique artefact coordinates once before, but it was so long ago that I've managed to forget all about it 😀
a

Alexandre Brown

12/17/2021, 10:02 PM
Haha wow, not too surprising, it's a very sneaky error!
But of course we forget about those 😆.
3 Views