CLI: load module-info from META-INF/versions/... for any version

Not just 9, since at least some libraries are putting module-info to
META-INF/versions/11 nowadays.

 #KT-48027 Fixed
This commit is contained in:
Alexander Udalov
2021-12-15 23:19:40 +01:00
parent f38e1b218d
commit 6acb0c3d06
2 changed files with 43 additions and 24 deletions
@@ -156,13 +156,11 @@ class ClasspathRootsResolver(
private fun modularBinaryRoot(root: VirtualFile): JavaModule? { private fun modularBinaryRoot(root: VirtualFile): JavaModule? {
val isJar = root.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL val isJar = root.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL
val manifest: Attributes? by lazy(NONE) { readManifestAttributes(root) } val manifest = lazy(NONE) { readManifestAttributes(root) }
val moduleInfoFile = val moduleInfoFile =
root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
?: root.takeIf { isJar }?.findFileByRelativePath(MULTI_RELEASE_MODULE_INFO_CLS_FILE)?.takeIf { ?: if (isJar) tryLoadVersionSpecificModuleInfo(root, manifest) else null
manifest?.getValue(IS_MULTI_RELEASE)?.equals("true", ignoreCase = true) == true
}
if (moduleInfoFile != null) { if (moduleInfoFile != null) {
val moduleInfo = JavaModuleInfo.read(moduleInfoFile, javaFileManager, searchScope) ?: return null val moduleInfo = JavaModuleInfo.read(moduleInfoFile, javaFileManager, searchScope) ?: return null
@@ -173,7 +171,7 @@ class ClasspathRootsResolver(
if (isJar) { if (isJar) {
val moduleRoot = listOf(JavaModule.Root(root, isBinary = true)) val moduleRoot = listOf(JavaModule.Root(root, isBinary = true))
val automaticModuleName = manifest?.getValue(AUTOMATIC_MODULE_NAME) val automaticModuleName = manifest.value?.getValue(AUTOMATIC_MODULE_NAME)
if (automaticModuleName != null) { if (automaticModuleName != null) {
return JavaModule.Automatic(automaticModuleName, moduleRoot) return JavaModule.Automatic(automaticModuleName, moduleRoot)
} }
@@ -190,6 +188,23 @@ class ClasspathRootsResolver(
return null return null
} }
private fun tryLoadVersionSpecificModuleInfo(root: VirtualFile, manifest: Lazy<Attributes?>): VirtualFile? {
val versionsDir = root.findChild("META-INF")?.findChild("versions") ?: return null
val isMultiReleaseJar = manifest.value?.getValue(IS_MULTI_RELEASE)?.equals("true", ignoreCase = true)
if (isMultiReleaseJar != true) return null
val versions = versionsDir.children.filter {
val version = it.name.toIntOrNull()
version != null && version >= 9
}.sortedBy { it.name.toInt() }
for (version in versions) {
val file = version.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)
if (file != null) return file
}
return null
}
private fun readManifestAttributes(jarRoot: VirtualFile): Attributes? { private fun readManifestAttributes(jarRoot: VirtualFile): Attributes? {
val manifestFile = jarRoot.findChild("META-INF")?.findChild("MANIFEST.MF") val manifestFile = jarRoot.findChild("META-INF")?.findChild("MANIFEST.MF")
return try { return try {
@@ -323,7 +338,6 @@ class ClasspathRootsResolver(
} }
private companion object { private companion object {
const val MULTI_RELEASE_MODULE_INFO_CLS_FILE = "META-INF/versions/9/${PsiJavaModule.MODULE_INFO_CLS_FILE}"
const val AUTOMATIC_MODULE_NAME = "Automatic-Module-Name" const val AUTOMATIC_MODULE_NAME = "Automatic-Module-Name"
const val IS_MULTI_RELEASE = "Multi-Release" const val IS_MULTI_RELEASE = "Multi-Release"
} }
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.jar.Manifest import java.util.jar.Manifest
import kotlin.test.fail
class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() { class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() {
override val testDataPath: String override val testDataPath: String
@@ -78,13 +79,13 @@ class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() {
) )
} }
private fun createMultiReleaseJar(jdk9Home: File, destination: File, mainRoot: File, java9Root: File): File { private fun createMultiReleaseJar(jdkHome: File, destination: File, mainRoot: File, version: Int, versionSpecificRoot: File): File {
val command = listOf<String>( val command = listOf<String>(
File(jdk9Home, "bin/jar").path, File(jdkHome, "bin/jar").path,
"--create", "--file=$destination", "--create", "--file=$destination",
"-C", mainRoot.path, ".", "-C", mainRoot.path, ".",
"--release", "9", "--release", version.toString(),
"-C", java9Root.path, "." "-C", versionSpecificRoot.path, "."
) )
val process = ProcessBuilder().command(command).inheritIO().start() val process = ProcessBuilder().command(command).inheritIO().start()
@@ -124,8 +125,7 @@ class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() {
fun testAllModulePathAndNamedModule() { fun testAllModulePathAndNamedModule() {
try { try {
module("main", addModules = listOf("ALL-MODULE-PATH")) module("main", addModules = listOf("ALL-MODULE-PATH"))
} } catch (e: JavaCompilationError) {
catch (e: JavaCompilationError) {
// Java compilation should fail, it's expected // Java compilation should fail, it's expected
} }
} }
@@ -176,10 +176,10 @@ class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() {
"-Xmodule-path=${a.path}" "-Xmodule-path=${a.path}"
) )
compileLibrary( compileLibrary(
"moduleB", "moduleB",
additionalOptions = kotlinOptions, additionalOptions = kotlinOptions,
compileJava = { _, _, _ -> error("No .java files in moduleB in this test") }, compileJava = { _, _, _ -> error("No .java files in moduleB in this test") },
checkKotlinOutput = checkKotlinOutput("moduleB") checkKotlinOutput = checkKotlinOutput("moduleB")
) )
} }
@@ -192,12 +192,17 @@ class Java11ModulesIntegrationTest : AbstractKotlinCompilerIntegrationTest() {
libraryOut11.mkdirs() libraryOut11.mkdirs()
File(libraryOut, "module-info.class").renameTo(File(libraryOut11, "module-info.class")) File(libraryOut, "module-info.class").renameTo(File(libraryOut11, "module-info.class"))
// Use the name other from 'library' to prevent it from being loaded as an automatic module if module-info.class is not found for (version in listOf(9, 10, 11)) {
val libraryJar = createMultiReleaseJar( try {
KtTestUtil.getJdk11Home(), File(tmpdir, "multi-release-library.jar"), libraryOut, libraryOut11 // Use the name other from 'library' to prevent it from being loaded as an automatic module if module-info.class is not found.
) val libraryJar = createMultiReleaseJar(
KtTestUtil.getJdk11Home(), File(tmpdir, "multi-release-library-jdk$version.jar"), libraryOut, version, libraryOut11
module("main", listOf(libraryJar)) )
module("main", listOf(libraryJar))
} catch (e: Throwable) {
fail("Fail on testing that module-info.class is loaded from META-INF/versions/$version", e)
}
}
} }
fun testAutomaticModuleNames() { fun testAutomaticModuleNames() {