Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add explicit module-info.java for JPMS compatibility #3297

Closed
wants to merge 1 commit into from

Conversation

lion7
Copy link
Contributor

@lion7 lion7 commented May 19, 2022

No description provided.

@lion7
Copy link
Contributor Author

lion7 commented May 19, 2022

Note that there is still a highlighting issue with IntelliJ IDEA when using Java modules in a multi-platform Kotlin project.
The relevant issues are https://youtrack.jetbrains.com/issue/KTIJ-15498 and https://youtrack.jetbrains.com/issue/KTIJ-19614.
Right now this results in highlighting errors like these:
image

@qwwdfsad qwwdfsad self-requested a review June 1, 2022 15:44
Copy link
Collaborator

@qwwdfsad qwwdfsad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking care of it!

This is a change we'll be able to ship in the next major release as it carries compatibility risks and may require and RC first, so I will properly investigate the PR around kotlinx.coroutines 1.7.0

Unfortunately, IDEA issues won't be fixed in a foreseeable future, so could you please conditionally enable modules if and only if Idea.isActive is false? In kotlinx.serialization we had plenty of IDEA issues due to that in JVM-nly modules

build.gradle Outdated
// kotlinx.coroutines.debug and kotlinx.coroutines.test
// because they export packages that also exist in kotlinx.coroutines.core.
// Note that only 1 module can export a package.
def invalidModules = ["kotlinx-coroutines-debug", "kotlinx-coroutines-test"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kotlinx-coroutines-test has been fixed in 1.6.2 and no longer contains split package

@qwwdfsad qwwdfsad self-assigned this Jun 1, 2022
@lion7
Copy link
Contributor Author

lion7 commented Jun 1, 2022

Unfortunately I cannot use Idea.isActive because whatever the Gradle script configures is ignored by IDEA.
It just immediately starts analyzing the module graph if it detects a module-info.java in the source root
(if I understood the logic in https://github.com/JetBrains/intellij-community/blob/master/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaModuleGraphUtil.java correctly).

For now I just moved the module-info.java files to a separate java9 source folder which is included explicitly when compile the module descriptor, similar to how it's done in the kotlinx.serialization project.

@elizarov
Copy link
Contributor

elizarov commented Jun 2, 2022

AFAIU, module declarations must also declare all the services with uses and provides declarations or the corresponding functionality (like the main dispatcher) will not work. For this purse, it also makes sense to start running all the tests with a module path. This way, these kinds of omissions will be caught during testing.

@qwwdfsad
Copy link
Collaborator

A short update: the corresponding support of JPMS should land in IDEA 2022.3, so we are ready to proceed with the change.
Could you please rebase it on the most recent develop when you have time to spare?

@lion7
Copy link
Contributor Author

lion7 commented Oct 27, 2022

I just rebased and can confirm that the highlighting issues described in https://youtrack.jetbrains.com/issue/KTIJ-19614 are resolved in IDEA 2022.3 (EAP)

exports kotlinx.coroutines.debug;
exports kotlinx.coroutines.debug.internal;
exports kotlinx.coroutines.flow;
exports kotlinx.coroutines.flow.internal;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate on how exactly you got these packages?

It seems like some of them should not be exported in the first place -- all internal (though I have to double check some of them), scheduling and intrinsics

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These packages are mostly used in reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt. So I need to export them from core so I don't get a compile error in kotlinx-coroutines-reactive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered exporting the internal packages just to the kotlinx.coroutines modules that depend on them? For example:

Suggested change
exports kotlinx.coroutines.flow.internal;
exports kotlinx.coroutines.flow.internal to kotlinx.coroutines.reactive;

requires kotlin.stdlib;
requires kotlinx.coroutines.core;
requires net.bytebuddy;
requires net.bytebuddy.agent;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, probably not. I will check this out further.

@qwwdfsad
Copy link
Collaborator

@lion7
Copy link
Contributor Author

lion7 commented Nov 7, 2022

AFAIU, module declarations must also declare all the services with uses and provides declarations or the corresponding functionality (like the main dispatcher) will not work. For this purse, it also makes sense to start running all the tests with a module path. This way, these kinds of omissions will be caught during testing.

I also added the necessary uses and provides declarations to the module descriptors. I haven't touched any tests yet, so I have no guarantee that it works as intended though.

@qwwdfsad
Copy link
Collaborator

qwwdfsad commented Nov 15, 2022

@lion7 Is it ready for review or are you still working on it? (I'm not sure if "re-request review" button is available for external contributors, thus the question)

@qwwdfsad qwwdfsad self-requested a review December 7, 2022 12:52
@CodeMason
Copy link

Hey @lion7 -- is there any holdup on this? Having proper JPMS support would really be fantastic.

@lion7
Copy link
Contributor Author

lion7 commented Jan 7, 2023

@qwwdfsad I think this is ready enough for review, I'm not working on it anymore and just rebased it on the latest develop. The only thing that still bothers me is that there are currently no tests executed with a module path, especially after re-reading the comment from @elizarov.
I did do some manual testing and it should all work, but there might be regressions or that I missed something. Should that kind of testing also be part of this PR or is it ok to do that in a separate PR?

@qwwdfsad
Copy link
Collaborator

qwwdfsad commented Jan 9, 2023

Thanks! Will take care of it during this week

@qwwdfsad
Copy link
Collaborator

I've started the internal testing (mostly our internal projects, Ktor and some Android apps) process and, as soon as all perturbations are resolved, will publish the dev build for everybody interested in trying it out.

For now, it seems like JavaFx ahs some non-trivial setup issues: none of the tests is able to start and fails with java.lang.ClassNotFoundException: kotlinx.coroutines.javafx.JavaFxStressTest (or any other tests).
@lion7 could you please provide any additional pointers here while I'm looking at other modules?


@JvmStatic
fun configure(project: Project) = with(project) {
val javaToolchains = extensions.getByName("javaToolchains") as JavaToolchainService
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change it to

Suggested change
val javaToolchains = extensions.getByName("javaToolchains") as JavaToolchainService
val javaToolchains = extensions.findByType(JavaToolchainService::class.java)
?: error("Gradle JavaToolchainService is not available")

val invalidModules = listOf("kotlinx-coroutines-play-services")

configure(subprojects.filter {
!unpublished.contains(it.name) && !invalidModules.contains(it.name) && it.extensions.findByName("kotlin") != null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be more correct to run the configuration once some Kotlin plugin is applied, like

plugins.withType(org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin::class) {
    // perform some configuration
}

instead of checking for the extension presence right now. The order of plugin appliance should not matter.


// Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.
val kotlinCompileDestinationDir = compileKotlinTask.destinationDirectory.asFile.get()
options.compilerArgs.addAll(listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please try to preserve laziness here, e.g. like

val destinationDirProperty = compileKotlinTask.destinationDirectory.asFile
options.compilerArgumentProviders.add({
    val kotlinCompileDestinationDir = destinationDirProperty.get()
    listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir")
})

if (!sourceFile.exists()) {
throw IllegalStateException("$sourceFile not found in $project")
}
val compileKotlinTask = compilation.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compilation.compileKotlinTask is deprecated.
You could use

            val compileKotlinTask = compilation.compileTaskProvider.get()
                as? org.jetbrains.kotlin.gradle.tasks.KotlinCompile
                ?: error("Cannot access Kotlin compile task ${compilation.compileKotlinTaskName}")

// but it currently won't compile to a module-info.class file.
// Note that module checking only works on JDK 9+,
// because the JDK built-in base modules are not available in earlier versions.
val javaVersion = compileKotlinTask.kotlinJavaToolchain.javaVersion.orNull
Copy link
Member

@ALikhachev ALikhachev Feb 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to keep it lazy as well.

You could do it by defining such class

private class ModuleInfoFilter(
    private val compileKotlinTaskPath: String,
    private val javaVersionProvider: Provider<JavaVersion>,
    private val moduleInfoFile: File,
    private val logger: Logger
) : Spec<FileTreeElement> {
    private val isJava9Compatible
        get() = javaVersionProvider.orNull?.isJava9Compatible == true
    private var logged = false

    private fun logStatusOnce() {
        if (logged) return
        if (isJava9Compatible) {
            logger.info("Module-info checking is enabled; $compileKotlinTaskPath is compiled using Java ${javaVersionProvider.get()}")
        } else {
            logger.info("Module-info checking is disabled")
        }
        logged = true
    }

    override fun isSatisfiedBy(element: FileTreeElement): Boolean {
        logStatusOnce()
        if (isJava9Compatible) return false
        return element.file == moduleInfoFile
    }
}

and then use it like

val javaVersionProvider = compileKotlinTask.kotlinJavaToolchain.javaVersion
compileKotlinTask.exclude(ModuleInfoFilter(compileKotlinTask.path, javaVersionProvider, sourceFile, logger))

Yes, it's more verbose, but performs the check lazily. Otherwise it depends on the order, so if the task configured before toolchains, then it may get an incorrect Java version.

modularity.inferModulePath.set(true)
}

tasks.getByName<Jar>(target.artifactsTaskName) {
Copy link
Member

@ALikhachev ALikhachev Feb 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change it to

Suggested change
tasks.getByName<Jar>(target.artifactsTaskName) {
tasks.named<Jar>(target.artifactsTaskName) {

You're triggering unconditional task configuration even on running ./gradlew help:
Screenshot 2023-02-16 at 14 25 16

@qwwdfsad
Copy link
Collaborator

I've applied all the proposed suggestions in #3629, will keep you posted regarding further actions

@qwwdfsad
Copy link
Collaborator

I've run a few out integration tests, so far so good. Our JPMS-related testing capabilities are limited (because we are not really using JPMS ourselves anywhere), so I've published 1.7.0-jpms-preview to https://maven.pkg.jetbrains.space/public/p/kotlinx-coroutines/maven repository; if you are relying on JPMS in your project, consider checking it out.

@qwwdfsad
Copy link
Collaborator

Merged manually. Thanks!

@qwwdfsad qwwdfsad closed this Feb 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants