diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt index 274a174d5d6..e5bd35f7e64 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt @@ -392,7 +392,7 @@ default: `indy-with-constants` for JVM target 9 or greater, `inline` otherwise"" value = "-Xrelease", valueDescription = "Supported versions depend on used JDK", description = """ - Compile against specified JDK API. Supported versions depend on used JDK + Compile against specified JDK API. Supported versions depend on used JDK. """ ) var release: String? by NullableStringFreezableVar(null) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 128b03f38d3..3cc4677adf6 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -239,15 +239,10 @@ class KotlinCoreEnvironment private constructor( val javaFileManager = ServiceManager.getService(project, CoreJavaFileManager::class.java) as KotlinCliJavaFileManagerImpl val jdkHome = configuration.get(JVMConfigurationKeys.JDK_HOME) - val jrtFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL) val releaseTarget = configuration.get(JVMConfigurationKeys.RELEASE, 0) val javaModuleFinder = CliJavaModuleFinder( - jdkHome?.path?.let { path -> - VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL).findFileByPath(path) - }, - jdkHome?.path?.let { path -> - jrtFileSystem?.findFileByPath(path + URLUtil.JAR_SEPARATOR) - }, + jdkHome, + messageCollector, javaFileManager, project, releaseTarget diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt index 02227baa864..965f9995334 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt @@ -73,17 +73,26 @@ fun CompilerConfiguration.setupJvmSpecificArguments(arguments: K2JVMCompilerArgu } } - val release = arguments.release - if (release != null) { - val value = release.toIntOrNull() + val releaseTarget = arguments.release + if (releaseTarget != null) { + val value = releaseTarget.toIntOrNull() if (value == null) { messageCollector.report( ERROR, - "Can't parse value passed for `-Xrelease`: $release." + "Can't parse value passed for `-Xrelease`: $releaseTarget." ) } else { - // TODO: check - put(JVMConfigurationKeys.RELEASE, value) + if (value < 6) { + messageCollector.report( + ERROR, + "`$value` is not valid value for `-Xrelease` flag." + ) + } else { + //don't use release flag if it equals to compilation JDK version + if (value != getJavaVersion() || arguments.jdkHome != null) { + put(JVMConfigurationKeys.RELEASE, value) + } + } } } @@ -321,3 +330,6 @@ fun CompilerConfiguration.configureKlibPaths(arguments: K2JVMCompilerArguments) private val CompilerConfiguration.messageCollector: MessageCollector get() = getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + +private fun getJavaVersion(): Int = + System.getProperty("java.specification.version")?.substringAfter('.')?.toIntOrNull() ?: 6 diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt index 87e860623a7..a6beb4b1b34 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt @@ -23,29 +23,61 @@ import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiJavaModule import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.io.URLUtil +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo +import java.io.File class CliJavaModuleFinder( - jdkRootFile: VirtualFile?, - jrtFileSystemRoot: VirtualFile?, + private val jdkHome: File?, + private val messageCollector: MessageCollector?, private val javaFileManager: KotlinCliJavaFileManager, project: Project, private val releaseTarget: Int ) : JavaModuleFinder { + + private val jrtFileSystemRoot = jdkHome?.path?.let { path -> + VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL)?.findFileByPath(path + URLUtil.JAR_SEPARATOR) + } + private val modulesRoot = jrtFileSystemRoot?.findChild("modules") - private val ctSymFile = jdkRootFile?.findChild("lib")?.findChild("ct.sym") + private val userModules = linkedMapOf() private val allScope = GlobalSearchScope.allScope(project) - public val compilationJdkVersion by lazy { - //TODO: add test with -jdk-home + private val ctSymFile: VirtualFile? by lazy { + if (jdkHome == null) return@lazy reportError("JDK_HOME path is not specified in compiler configuration") + + val jdkRootFile = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL).findFileByPath(jdkHome.path) + ?: return@lazy reportError("Can't create virtual file for JDK root under ${jdkHome.path}") + + val lib = jdkRootFile.findChild("lib") ?: return@lazy reportError("Can't find `lib` folder under JDK root: ${jdkHome.path}") + + val ctSym = lib.findChild("ct.sym") ?: return@lazy reportError("Can't find `ct.sym` file in ${jdkHome.path}") + + if (!ctSym.isValid) return@lazy reportError("`ct.sym` is invalid: ${ctSym.path}") + + ctSym + } + + private val ctSymRootFolder: VirtualFile? by lazy { + if (ctSymFile != null) { + VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL) + ?.findFileByPath(ctSymFile?.path + URLUtil.JAR_SEPARATOR) + ?: reportError("Can't open `ct.sym` as jar file, file path: ${ctSymFile?.path} ") + } else { + null + } + } + + private val compilationJdkVersion by lazy { // Observe all JDK codes from folder name chars in ct.sym file, // there should be maximal one corresponding to used compilation JDK - ctSymRootFolder()?.children?.maxOf { + ctSymRootFolder?.children?.maxOf { if (it.name == "META-INF") -1 else it.name.substringBeforeLast("-modules").maxOf { char -> char.toString().toIntOrNull(36) ?: -1 @@ -53,7 +85,11 @@ class CliJavaModuleFinder( } ?: -1 } - private val isJDK12OrLater + val ctSymModules by lazy { + collectModuleRoots() + } + + private val isCompilationJDK12OrLater get() = compilationJdkVersion >= 12 private val useLastJdkApi: Boolean @@ -66,10 +102,6 @@ class CliJavaModuleFinder( val allObservableModules: Sequence get() = systemModules + userModules.values - val ctSymModules by lazy { - collectModuleRoots(releaseTarget) - } - val systemModules: Sequence get() = if (useLastJdkApi) modulesRoot?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule) else ctSymModules.values.asSequence().mapNotNull { findSystemModule(it, true) } @@ -101,44 +133,41 @@ class CliJavaModuleFinder( private fun matchesRelease(fileName: String, release: Int) = !fileName.contains("-") && fileName.contains(codeFor(release)) // skip `*-modules` - private fun hasCtSymFile() = ctSymFile != null && ctSymFile.isValid - fun listFoldersForRelease(): List { - if (!hasCtSymFile()) return emptyList() - val root = ctSymRootFolder() ?: return emptyList() - return root.children.filter { matchesRelease(it.name, releaseTarget) }.flatMap { - if (isJDK12OrLater) + if (ctSymRootFolder == null) return emptyList() + return ctSymRootFolder!!.children.filter { matchesRelease(it.name, releaseTarget) }.flatMap { + if (isCompilationJDK12OrLater) it.children.toList() else { listOf(it) } + }.apply { + if (isEmpty()) reportError("'-Xrelease=${releaseTarget}' option is not supported by used JDK: ${jdkHome?.path}") } } - private fun collectModuleRoots(release: Int): Map { - if (!hasCtSymFile()) return emptyMap() + private fun collectModuleRoots(): Map { val result = mutableMapOf() - val root = ctSymRootFolder() ?: return emptyMap() - - - if (isJDK12OrLater) { + if (isCompilationJDK12OrLater) { listFoldersForRelease().forEach { modulesRoot -> modulesRoot.findChild("module-info.sig")?.let { result[modulesRoot.name] = modulesRoot } } } else { - if (releaseTarget > 8) { - val moduleSigs = root.findChild(codeFor(release) + if (!isJDK12OrLater) "-modules" else "") - ?: error("Can't find modules signatures in `ct.sym` file for `-release $release` in ${ctSymFile?.path}") - moduleSigs.children.forEach { - result[it.name] = it - } + if (this.releaseTarget > 8 && ctSymRootFolder != null) { + ctSymRootFolder!!.findChild(codeFor(releaseTarget) + if (!isCompilationJDK12OrLater) "-modules" else "")?.apply { + children.forEach { + result[it.name] = it + } + } ?: reportError("Can't find modules signatures in `ct.sym` file for `-Xrelease=$releaseTarget` in ${ctSymFile!!.path}") } } return result } - private fun ctSymRootFolder() = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL) - ?.findFileByPath(ctSymFile!!.path + URLUtil.JAR_SEPARATOR) + private fun reportError(message: String): VirtualFile? { + messageCollector?.report(CompilerMessageSeverity.ERROR, message) + return null + } } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleResolver.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleResolver.kt index e3ea14f7f90..6afec412dc6 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleResolver.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleResolver.kt @@ -54,7 +54,8 @@ class CliJavaModuleResolver( private val sourceModule: JavaModule? = userModules.firstOrNull(JavaModule::isSourceModule) private fun findJavaModule(file: VirtualFile): JavaModule? { - if (file.fileSystem.protocol == StandardFileSystems.JRT_PROTOCOL) { + //TODO: proper root filtering is required + if (file.fileSystem.protocol == StandardFileSystems.JRT_PROTOCOL /*|| file.extension == "sig"*/) { return systemModules.firstOrNull { module -> file in module } } diff --git a/compiler/testData/javaModules/releaseFlag/module.txt b/compiler/testData/javaModules/releaseFlag/module.txt new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/module.txt @@ -0,0 +1 @@ +OK diff --git a/compiler/testData/javaModules/releaseFlag/module/foo/Foo.kt b/compiler/testData/javaModules/releaseFlag/module/foo/Foo.kt new file mode 100644 index 00000000000..6e0c43fe410 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/module/foo/Foo.kt @@ -0,0 +1,5 @@ +package foo; + +public class Foo { + val z: java.nio.ByteBuffer? = null +} diff --git a/compiler/testData/javaModules/releaseFlag/module/module-info.java b/compiler/testData/javaModules/releaseFlag/module/module-info.java new file mode 100644 index 00000000000..287e0ae2b35 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/module/module-info.java @@ -0,0 +1,5 @@ +module module { + exports foo; + + requires kotlin.stdlib; +} diff --git a/compiler/testData/javaModules/releaseFlag/module11.txt b/compiler/testData/javaModules/releaseFlag/module11.txt new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/module11.txt @@ -0,0 +1 @@ +OK diff --git a/compiler/testData/javaModules/releaseFlag/module11/foo/Foo.kt b/compiler/testData/javaModules/releaseFlag/module11/foo/Foo.kt new file mode 100644 index 00000000000..6e0c43fe410 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/module11/foo/Foo.kt @@ -0,0 +1,5 @@ +package foo; + +public class Foo { + val z: java.nio.ByteBuffer? = null +} diff --git a/compiler/testData/javaModules/releaseFlag/module11/module-info.java b/compiler/testData/javaModules/releaseFlag/module11/module-info.java new file mode 100644 index 00000000000..093339e00e6 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/module11/module-info.java @@ -0,0 +1,5 @@ +module module11 { + exports foo; + + requires kotlin.stdlib; +} diff --git a/compiler/testData/javaModules/releaseFlag/moduleSwing.txt b/compiler/testData/javaModules/releaseFlag/moduleSwing.txt new file mode 100644 index 00000000000..3c90171892d --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/moduleSwing.txt @@ -0,0 +1,4 @@ +compiler/testData/javaModules/releaseFlag/moduleSwing/foo/Foo.kt:5:24: error: symbol is declared in module 'javafx.media' which current module does not depend on + val z: javax.swing.JFrame? = null + ^ +COMPILATION_ERROR diff --git a/compiler/testData/javaModules/releaseFlag/moduleSwing/foo/Foo.kt b/compiler/testData/javaModules/releaseFlag/moduleSwing/foo/Foo.kt new file mode 100644 index 00000000000..6975559a85c --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/moduleSwing/foo/Foo.kt @@ -0,0 +1,6 @@ +package foo; + +public class Foo { + //no requirement + val z: javax.swing.JFrame? = null +} diff --git a/compiler/testData/javaModules/releaseFlag/moduleSwing/foo/Foo2.java b/compiler/testData/javaModules/releaseFlag/moduleSwing/foo/Foo2.java new file mode 100644 index 00000000000..f02c4214229 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/moduleSwing/foo/Foo2.java @@ -0,0 +1,3 @@ +package foo; + +public class Foo2 {} \ No newline at end of file diff --git a/compiler/testData/javaModules/releaseFlag/moduleSwing/module-info.java b/compiler/testData/javaModules/releaseFlag/moduleSwing/module-info.java new file mode 100644 index 00000000000..0443b7ba02f --- /dev/null +++ b/compiler/testData/javaModules/releaseFlag/moduleSwing/module-info.java @@ -0,0 +1,5 @@ +module moduleSwing { + exports foo; + + requires kotlin.stdlib; +} diff --git a/compiler/testData/javaModules/releaseFlagWrongValue/module12.txt b/compiler/testData/javaModules/releaseFlagWrongValue/module12.txt new file mode 100644 index 00000000000..4dfb6cd1612 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlagWrongValue/module12.txt @@ -0,0 +1,3 @@ +error: can't find modules signatures in `ct.sym` file for `-Xrelease=12` in $JDK11/lib/ct.sym +error: module java.base cannot be found in the module graph +COMPILATION_ERROR diff --git a/compiler/testData/javaModules/releaseFlagWrongValue/module12/foo/Foo.kt b/compiler/testData/javaModules/releaseFlagWrongValue/module12/foo/Foo.kt new file mode 100644 index 00000000000..7ed8790ba34 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlagWrongValue/module12/foo/Foo.kt @@ -0,0 +1,6 @@ +package foo; + +public class Foo { + val z: java.nio.ByteBuffer? = null +} + diff --git a/compiler/testData/javaModules/releaseFlagWrongValue/module5.txt b/compiler/testData/javaModules/releaseFlagWrongValue/module5.txt new file mode 100644 index 00000000000..0c82de057db --- /dev/null +++ b/compiler/testData/javaModules/releaseFlagWrongValue/module5.txt @@ -0,0 +1,2 @@ +error: `5` is not valid value for `-Xrelease` flag. +COMPILATION_ERROR diff --git a/compiler/testData/javaModules/releaseFlagWrongValue/module5/foo/Foo.kt b/compiler/testData/javaModules/releaseFlagWrongValue/module5/foo/Foo.kt new file mode 100644 index 00000000000..6e0c43fe410 --- /dev/null +++ b/compiler/testData/javaModules/releaseFlagWrongValue/module5/foo/Foo.kt @@ -0,0 +1,5 @@ +package foo; + +public class Foo { + val z: java.nio.ByteBuffer? = null +} diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/Java11ModulesIntegrationTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/Java11ModulesIntegrationTest.kt index bab357f8138..d362b4acc3e 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/Java11ModulesIntegrationTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/Java11ModulesIntegrationTest.kt @@ -7,6 +7,8 @@ package org.jetbrains.kotlin.jvm.compiler import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.cli.AbstractCliTest +import org.jetbrains.kotlin.cli.AbstractCliTest.getNormalizedCompilerOutput +import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.util.KtTestUtil @@ -59,7 +61,10 @@ class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() { } private fun checkKotlinOutput(moduleName: String): (String) -> Unit = { actual -> - KotlinTestUtils.assertEqualsToFile(File(testDataDirectory, "$moduleName.txt"), actual) + KotlinTestUtils.assertEqualsToFile( + File(testDataDirectory, "$moduleName.txt"), + getNormalizedCompilerOutput(actual, null, testDataPath).replace(System.getenv("JDK_11"), "\$JDK11") + ) } private data class ModuleRunResult(val stdout: String, val stderr: String) @@ -144,6 +149,21 @@ class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() { module("moduleB", listOf(module("moduleA")), addModules = emptyList()) } + fun testReleaseFlagWrongValue() { + // Test that although we have moduleA in the module path, it's not in the module graph + // because we did not provide -Xadd-modules=moduleA + module("module5", additionalKotlinArguments = listOf("-Xrelease=5")) + module("module12", additionalKotlinArguments = listOf("-Xrelease=12")) + } + + fun testReleaseFlag() { + // Test that although we have moduleA in the module path, it's not in the module graph + // because we did not provide -Xadd-modules=moduleA + module("module") + module("module11", additionalKotlinArguments = listOf("-Xrelease=11")) + module("moduleSwing", additionalKotlinArguments = listOf("-Xrelease=9")) + } + fun testNamedReadsTransitive() { val a = module("moduleA") val b = module("moduleB", listOf(a))