I have a project with module-info.java ```plugins ...
# gradle
d
I have a project with module-info.java
Copy code
plugins {
  id("java-library")
  kotlin("jvm") version "1.9.21"
}
The source roots exist as:
Copy code
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
Copy code
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:
Copy code
java {
   compileClassPathHackery.addOutputClassesFrom("kotlinSourceSet")  // so module-info.java can be built
}
The build order looks to be: projectprocessResources projectcompileKotlin projectcompileJava projectclasses projectjar projectassemble
This is what the uglyness of the fix look like so far:
Copy code
java {
    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
}
Any improvements would be appreciated
The goal is for the JavaC when compiling
module-info.java
to have the required visibility of the Kotlin compile output, so as not to error due to packages not existing.
It looks like this mess is being tracked here https://youtrack.jetbrains.com/issue/KT-55200
I have updated tasks in my project that effectively does:
Copy code
Task :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 unexpectedly
v
I didn't read all the code and links. But all you need is a
CompileArgumentProvider
that has the Kotlin compilation result as input files and produces a
--patch-module
parameter with value. Nothing else should be necessary.
d
Thanks for the reply, have you a link to this working for my use case. After digesting the many references, blogs, bug reports (across the kotlin/gradle/java/jcp) ecosystems, at the moment the most compact fix for the issue is to include:
Copy code
kotlin {
  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.
v
Copy code
tasks.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}"
        )
    })
}
It's not more compact than your "most compact solution", but it is the proper solution
d
Thanks this is working on a larger multiple module project. A little more work here to run it though its paces/testing to validate it is a better solution than copy/merge .class dirs (especially when duplicate named files are found, I want it it error which I think this solution will maintain) My use of 'compact' term really means "easier to read than the initial copy *.class files idea tester", I can solve compactness by turning it into a toplevel Gradle plugin, then add in auto-detection of need (compileJava + compileKotlin exist and are active in module), that a module-info.java exists in project (so project is exporting for JPMS), then add auto-configuration to setup "org.foobar.main" automatically by snooping the module-info.java file. With a bunch of @Property overrides, than it can be a one-liner
id("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:
Copy code
kotlin {
  fixupJavaPlatformModuleInfo()
}
v
As far as I know the plan is, that the Kotlin compiler can also compile
module-info.java
. I hope when they care about that, they also consider such split-source cases.