Darryl Miles
12/17/2023, 12:10 PMplugins {
id("java-library")
kotlin("jvm") version "1.9.21"
}
The source roots exist as:
src/main/java/
src/main/java/module-info.java
src/main/java/org/foobar/java/DummyJava.java
src/main/kotlin/
src/main/kotlin/org/foobar/Dummy.kt
src/main/kotlin/org/foobar/java2/DummyJavaK.java // file ignored that is ok it is a visibility test to understand better
The module-info.java
module org.foobar.main {
requires kotlin.stdlib;
//export org.foobar; // MUST BE COMMENTED OUT TO assemble, but has Dummy.class in output JAR
export org.foobar.java;
//export org.foobar.java2; // MUST BE COMMENTED OUT TO assemble, but no *.class here in output JAR
}
I am happy with the output JAR, not containing DummyJavaK.class is ok because I havbe src/main/java and don't need to mix full-qualified-package-names across source roots.
The problem is when this is consumed, the Dummy.kt is not accessible, because the Gradle build forces me to comment out the export org.foobar
otherwise the build will fail.
Maybe I need to hackery with:
java {
compileClassPathHackery.addOutputClassesFrom("kotlinSourceSet") // so module-info.java can be built
}
The build order looks to be:
projectprocessResources
projectcompileKotlin
projectcompileJava
projectclasses
projectjar
projectassembleDarryl Miles
12/17/2023, 1:19 PMjava {
dependencies { // BROKEN IDEA we are trying to provide additional visibility of what we built already, only to the JavaC
logger.info("extra={}", layout.buildDirectory.files("classes/kotlin/**/*.class"))
// Would be good to just output read-only visibility at the folder
//compileClasspath(layout.buildDirectory.files("classes/kotlin/**/*.class"))
//compileClasspath(layout.buildDirectory.files("classes/kotlin"))
}
}
// WORKING IDEA
tasks.create("copyClassesKotlinToJava", Copy::class.java) {
dependsOn("compileKotlin")
from(layout.buildDirectory.dir("classes/kotlin/main"))
into(layout.buildDirectory.dir("classes/java/main"))
}
// Force assurance of task completion order
tasks.findByName("compileJava")!!.dependsOn("copyClassesKotlinToJava")
// But we get duplicates reported by JAR task during file generation, so need to manage this
tasks.withType(Jar::class.java).configureEach {
// Would be nice to suppress identical into one logger entry (reporting identical class count) taking file compare performance hit
// But error when any difference is found DuplicatesStrategy.EXPECT_IDENTICAL
duplicatesStrategy = DuplicatesStrategy.WARN
}
Darryl Miles
12/17/2023, 1:22 PMDarryl Miles
12/17/2023, 1:24 PMmodule-info.java
to have the required visibility of the Kotlin compile output, so as not to error due to packages not existing.Darryl Miles
12/17/2023, 2:46 PMDarryl Miles
12/17/2023, 2:47 PMTask :project:compileKotlin
Task :project:copyClassesKotlinToJavaKludge
Task :project:compileJava
Task :project:classes
Task :project:undoCopyClassesKotlinToJavaKludge
Task :project:jar
Task :project:assemble
This is keeping a list of the file and lastModified timestamp during the copy
Then removing those files before the jar
providing the lastModified is still the same, otherwise considers it an error
So the next step for me is to roll a custom Gradle plugin to hide the mess, so it will auto-configure (maybe by detecting presence of module-info.java in project and compileKotlin task. Hidden enough that it becomes a one line plugin id()
directive.
Also need countermeasures so it never bites unexpectedlyVampire
12/17/2023, 10:48 PMCompileArgumentProvider
that has the Kotlin compilation result as input files and produces a --patch-module
parameter with value. Nothing else should be necessary.Darryl Miles
12/18/2023, 11:45 AMkotlin {
sourceSets.forEach { ss ->
ss.kotlin { // This forces Kotlin to emit *.class into Java output directory
destinationDirectory.set(layout.buildDirectory.dir("classes/java/${ss.name}").get().asFile)
}
}
}
I am interested to explore alternatives, I think one downside to this approach compared to my copyClassesKotlinToJavaKludge
is that if JavaC emits a *.class with the same path/name as a Kotlin it will silently overwrite.
When I did come across use of --patch-module
I discounted it as not being relevant to my use case, so I shall now re-explore if I still think this is the case, having done a bit of a full circle in this area, which is why I am keen to see use of this to solve this specific issue as a link.
The important aspects are that one gradle build.gradle script is compiling both Java and Kotlin classes. It is producing a single JPMS module output JAR that contains multiple packages. The use of --patch-module
looked like it is to allow interoperability with non JPMS capable external dependencies (imports), not to modify my module's own internal content, that can be exported or not based on configuration.
Thanks for your reply.Vampire
12/18/2023, 12:41 PMtasks.compileJava {
options.compilerArgumentProviders.add(object : CommandLineArgumentProvider {
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
val kotlinClasses = tasks.compileKotlin.flatMap { it.destinationDirectory }
override fun asArguments() = listOf(
"--patch-module",
"org.foobar.main=${kotlinClasses.get().asFile.absolutePath}"
)
})
}
Vampire
12/18/2023, 12:41 PMDarryl Miles
12/18/2023, 1:12 PMid("my.plugin.gradle.id.here")
for more use cases.
Thanks for your assistance, maybe post the code snippet to the IJ bug report if you think it can help others, or should be part of the standard gradle support JB provides with plugin("jvm")
even if it requires a directive to activate:
kotlin {
fixupJavaPlatformModuleInfo()
}
Vampire
12/18/2023, 2:03 PMmodule-info.java
. I hope when they care about that, they also consider such split-source cases.