Extract common logic of detecting klibs and creating LibraryInfo by klib

This commit is contained in:
Dmitry Savvinov
2020-03-03 18:29:57 +03:00
committed by Dmitriy Dolovov
parent 6ccb8cd0a3
commit b6d811c69c
5 changed files with 107 additions and 121 deletions
@@ -23,22 +23,18 @@ import org.jetbrains.kotlin.idea.caches.project.LibraryInfo
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.resolve.BuiltInsCacheKey
import org.jetbrains.kotlin.idea.framework.CommonLibraryKind
import org.jetbrains.kotlin.idea.klib.getCompatibilityInfo
import org.jetbrains.kotlin.idea.util.IJLoggerAdapter
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.idea.klib.AbstractKlibLibraryInfo
import org.jetbrains.kotlin.idea.klib.isKlibLibraryRootForPlatform
import org.jetbrains.kotlin.platform.CommonPlatforms
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.impl.CommonIdePlatformKind
import org.jetbrains.kotlin.resolve.TargetEnvironment
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment
import java.io.IOException
import java.util.*
import org.jetbrains.kotlin.konan.file.File as KFile
class CommonPlatformKindResolution : IdePlatformKindResolution {
override fun isLibraryFileForPlatform(virtualFile: VirtualFile): Boolean {
return virtualFile.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION || virtualFile.isMetadataKlib
return virtualFile.extension == MetadataPackageFragment.METADATA_FILE_EXTENSION ||
virtualFile.isKlibLibraryRootForPlatform(CommonPlatforms.defaultCommonPlatform)
}
override val libraryKind: PersistentLibraryKind<*>?
@@ -53,7 +49,9 @@ class CommonPlatformKindResolution : IdePlatformKindResolution {
}
override fun createLibraryInfo(project: Project, library: Library): List<LibraryInfo> {
val klibFiles = library.getFiles(OrderRootType.CLASSES).filter { it.isMetadataKlib }
val klibFiles = library.getFiles(OrderRootType.CLASSES).filter {
it.isKlibLibraryRootForPlatform(CommonPlatforms.defaultCommonPlatform)
}
return if (klibFiles.isNotEmpty()) {
klibFiles.mapNotNull {
@@ -80,56 +78,7 @@ class CommonPlatformKindResolution : IdePlatformKindResolution {
}
}
// TODO(dsavvinov): unify with NativeLibraryInfo
class CommonKlibLibraryInfo(project: Project, library: Library, val libraryRoot: String) : LibraryInfo(project, library) {
val commonLibrary = resolveSingleFileKlib(
libraryFile = KFile(libraryRoot),
logger = LOG,
strategy = ToolingSingleFileKlibResolveStrategy
)
val compatibilityInfo by lazy { commonLibrary.getCompatibilityInfo() }
override fun getLibraryRoots() = listOf(libraryRoot)
class CommonKlibLibraryInfo(project: Project, library: Library, libraryRoot: String) : AbstractKlibLibraryInfo(project, library, libraryRoot) {
override val platform: TargetPlatform
get() = CommonPlatforms.defaultCommonPlatform
override fun toString() = "CommonKlib" + super.toString()
companion object {
private val LOG = IJLoggerAdapter.getInstance(CommonKlibLibraryInfo::class.java)
}
}
val VirtualFile.isMetadataKlib: Boolean
get() {
val extension = extension
if (!extension.isNullOrEmpty() && extension != KLIB_FILE_EXTENSION) return false
fun checkComponent(componentFile: VirtualFile): Boolean {
val manifestFile = componentFile.findChild(KLIB_MANIFEST_FILE_NAME)?.takeIf { !it.isDirectory } ?: return false
val manifestProperties = try {
manifestFile.inputStream.use { Properties().apply { load(it) } }
} catch (_: IOException) {
return false
}
if (!manifestProperties.containsKey(KLIB_PROPERTY_UNIQUE_NAME)) return false
// No builtins_platform property => either a new common klib (we don't write builtins_platform for common) or old Native klib
return manifestProperties.getProperty(KLIB_PROPERTY_BUILTINS_PLATFORM) == null && !componentFile.isLegacyNativeKlibComponent
}
// run check for library root too
// this is necessary to recognize old style KLIBs that do not have components, and report them to user appropriately
return checkComponent(this) || children?.any(::checkComponent) == true
}
private val VirtualFile.isLegacyNativeKlibComponent: Boolean
get() {
val irFolder = findChild(KLIB_IR_FOLDER_NAME)
return irFolder != null && irFolder.children.isNotEmpty()
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.klib
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.Library
import org.jetbrains.kotlin.idea.caches.project.LibraryInfo
import org.jetbrains.kotlin.idea.util.IJLoggerAdapter
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.library.resolveSingleFileKlib
abstract class AbstractKlibLibraryInfo(project: Project, library: Library, protected val libraryRoot: String) : LibraryInfo(project, library) {
val resolvedKotlinLibrary = resolveSingleFileKlib(
libraryFile = File(libraryRoot),
logger = LOG,
strategy = ToolingSingleFileKlibResolveStrategy
)
val compatibilityInfo by lazy { resolvedKotlinLibrary.getCompatibilityInfo() }
override fun getLibraryRoots() = listOf(libraryRoot)
companion object {
private val LOG = IJLoggerAdapter.getInstance(AbstractKlibLibraryInfo::class.java)
}
}
@@ -5,8 +5,66 @@
package org.jetbrains.kotlin.idea.klib
import org.jetbrains.kotlin.library.KotlinLibrary
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.platform.js.isJs
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.platform.konan.isNative
import java.io.IOException
import java.util.*
fun VirtualFile.isKlibLibraryRootForPlatform(targetPlatform: TargetPlatform): Boolean {
// The virtual file for a library packed in a ZIP file will have path like "/some/path/to/the/file.klib!/",
// and therefore will be recognized by VFS as a directory (isDirectory == true).
// So, first, let's check the extension.
val extension = extension
if (!extension.isNullOrEmpty() && extension != KLIB_FILE_EXTENSION) return false
// run check for library root too
// this is necessary to recognize old style KLIBs that do not have components, and report tem to user appropriately
val requestedBuiltInsPlatform = targetPlatform.toBuiltInsPlatform()
return checkKlibComponent(this, requestedBuiltInsPlatform) ||
children?.any { checkKlibComponent(it, requestedBuiltInsPlatform) } == true
}
private fun checkKlibComponent(componentFile: VirtualFile, requestedBuiltInsPlatform: BuiltInsPlatform): Boolean {
val manifestFile = componentFile.findChild(KLIB_MANIFEST_FILE_NAME)?.takeIf { !it.isDirectory } ?: return false
val manifestProperties = try {
manifestFile.inputStream.use { Properties().apply { load(it) } }
} catch (_: IOException) {
return false
}
if (!manifestProperties.containsKey(KLIB_PROPERTY_UNIQUE_NAME)) return false
// No builtins_platform property => either a new common klib (we don't write builtins_platform for common) or old Native klib
val builtInsPlatformProperty = manifestProperties.getProperty(KLIB_PROPERTY_BUILTINS_PLATFORM)
// TODO(dsavvinov): drop additional legacy check after 1.4
?: return requestedBuiltInsPlatform == BuiltInsPlatform.NATIVE && componentFile.isLegacyNativeKlibComponent
val builtInsPlatform = BuiltInsPlatform.parseFromString(builtInsPlatformProperty) ?: return false
return builtInsPlatform == requestedBuiltInsPlatform
}
private fun TargetPlatform.toBuiltInsPlatform() = when {
isCommon() -> BuiltInsPlatform.COMMON
isNative() -> BuiltInsPlatform.NATIVE
isJvm() -> BuiltInsPlatform.JVM
isJs() -> BuiltInsPlatform.JS
else -> throw IllegalArgumentException("Unknown platform $this")
}
private val VirtualFile.isLegacyNativeKlibComponent: Boolean
get() {
val irFolder = findChild(KLIB_IR_FOLDER_NAME)
return irFolder != null && irFolder.children.isNotEmpty()
}
fun <T> KotlinLibrary.readSafe(defaultValue: T, action: KotlinLibrary.() -> T) = try {
action()
@@ -78,42 +78,3 @@ internal object CachingIdeKonanLibraryMetadataLoader : PackageAccessHandler {
private val KotlinLibrary.isZipped
get() = (this as KotlinLibraryImpl).base.access.layout.isZipped
}
internal val VirtualFile.isKonanLibraryRoot: Boolean
get() {
// The virtual file for a library packed in a ZIP file will have path like "/some/path/to/the/file.klib!/",
// and therefore will be recognized by VFS as a directory (isDirectory == true).
// So, first, let's check the extension.
val extension = extension
if (!extension.isNullOrEmpty() && extension != KLIB_FILE_EXTENSION) return false
fun checkComponent(componentFile: VirtualFile): Boolean {
val manifestFile = componentFile.findChild(KLIB_MANIFEST_FILE_NAME)?.takeIf { !it.isDirectory } ?: return false
val manifestProperties = try {
manifestFile.inputStream.use { Properties().apply { load(it) } }
} catch (_: IOException) {
return false
}
if (!manifestProperties.containsKey(KLIB_PROPERTY_UNIQUE_NAME)) return false
// No builtins_platform property => either a new common klib (we don't write builtins_platform for common) or old Native klib
val builtInsPlatformProperty = manifestProperties.getProperty(KLIB_PROPERTY_BUILTINS_PLATFORM)
?: return componentFile.isLegacyNativeKlibComponent // TODO(dsavvinov): drop additional legacy check after 1.4
val builtInsPlatform = BuiltInsPlatform.parseFromString(builtInsPlatformProperty) ?: return false
return builtInsPlatform == BuiltInsPlatform.NATIVE
}
// run check for library root too
// this is necessary to recognize old style KLIBs that do not have components, and report tem to user appropriately
return checkComponent(this) || children?.any(::checkComponent) == true
}
private val VirtualFile.isLegacyNativeKlibComponent: Boolean
get() {
val irFolder = findChild(KLIB_IR_FOLDER_NAME)
return irFolder != null && irFolder.children.isNotEmpty()
}
@@ -33,14 +33,10 @@ import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.project.lazyClosure
import org.jetbrains.kotlin.idea.caches.resolve.BuiltInsCacheKey
import org.jetbrains.kotlin.idea.compiler.IDELanguageSettingsProvider
import org.jetbrains.kotlin.idea.util.IJLoggerAdapter
import org.jetbrains.kotlin.konan.file.File as KFile
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.kotlin.konan.util.KlibMetadataFactories
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.ToolingSingleFileKlibResolveStrategy
import org.jetbrains.kotlin.library.isInterop
import org.jetbrains.kotlin.library.resolveSingleFileKlib
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.impl.NativeIdePlatformKind
import org.jetbrains.kotlin.platform.konan.KonanPlatforms
@@ -52,6 +48,8 @@ import org.jetbrains.kotlin.serialization.konan.impl.KlibMetadataModuleDescripto
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.idea.klib.getCompatibilityInfo
import org.jetbrains.kotlin.idea.klib.readSafe
import org.jetbrains.kotlin.idea.klib.AbstractKlibLibraryInfo
import org.jetbrains.kotlin.idea.klib.isKlibLibraryRootForPlatform
private val NativeFactories = KlibMetadataFactories(::KonanBuiltIns, NullFlexibleTypeDeserializer)
@@ -101,7 +99,8 @@ class NativePlatformKindResolution : IdePlatformKindResolution {
)
}
override fun isLibraryFileForPlatform(virtualFile: VirtualFile): Boolean = virtualFile.isKonanLibraryRoot
override fun isLibraryFileForPlatform(virtualFile: VirtualFile): Boolean =
virtualFile.isKlibLibraryRootForPlatform(KonanPlatforms.defaultKonanPlatform)
override fun createResolverForModuleFactory(
settings: PlatformAnalysisParameters,
@@ -175,26 +174,16 @@ private fun ModuleInfo.findNativeStdlib(): NativeLibraryInfo? =
.filterIsInstance<NativeLibraryInfo>()
.firstOrNull { it.isStdlib && it.compatibilityInfo.isCompatible }
class NativeLibraryInfo(project: Project, library: Library, val libraryRoot: String) : LibraryInfo(project, library) {
private val nativeLibrary = resolveSingleFileKlib(
libraryFile = KFile(libraryRoot),
logger = LOG,
strategy = ToolingSingleFileKlibResolveStrategy
)
class NativeLibraryInfo(project: Project, library: Library, libraryRoot: String) : AbstractKlibLibraryInfo(project, library, libraryRoot) {
val isStdlib get() = libraryRoot.endsWith(KONAN_STDLIB_NAME)
val compatibilityInfo by lazy { nativeLibrary.getCompatibilityInfo() }
override fun getLibraryRoots() = listOf(libraryRoot)
override val capabilities: Map<ModuleDescriptor.Capability<*>, Any?>
get() {
val capabilities = super.capabilities.toMutableMap()
capabilities += KlibModuleOrigin.CAPABILITY to DeserializedKlibModuleOrigin(nativeLibrary)
capabilities += NATIVE_LIBRARY_CAPABILITY to nativeLibrary
capabilities += ImplicitIntegerCoercion.MODULE_CAPABILITY to nativeLibrary.readSafe(false) { isInterop }
capabilities += KlibModuleOrigin.CAPABILITY to DeserializedKlibModuleOrigin(resolvedKotlinLibrary)
capabilities += NATIVE_LIBRARY_CAPABILITY to resolvedKotlinLibrary
capabilities += ImplicitIntegerCoercion.MODULE_CAPABILITY to resolvedKotlinLibrary.readSafe(false) { isInterop }
return capabilities
}
@@ -204,8 +193,6 @@ class NativeLibraryInfo(project: Project, library: Library, val libraryRoot: Str
override fun toString() = "Native" + super.toString()
companion object {
private val LOG = IJLoggerAdapter.getInstance(NativeLibraryInfo::class.java)
val NATIVE_LIBRARY_CAPABILITY = ModuleDescriptor.Capability<KotlinLibrary>("KotlinNativeLibrary")
}
}