Fix: Use more precise KLIB compatibility check (by metadata version)

Issue #KT-34811
This commit is contained in:
Dmitriy Dolovov
2019-12-12 14:49:38 +03:00
parent 34e1ab4e7a
commit deb4025987
5 changed files with 55 additions and 39 deletions
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.library.KLIB_PROPERTY_METADATA_VERSION
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
class KlibMetadataVersion(major: Int, minor: Int, patch: Int) : BinaryVersion(major, minor, patch) {
class KlibMetadataVersion(vararg numbers: Int) : BinaryVersion(*numbers) {
override fun isCompatible(): Boolean = isCompatibleTo(INSTANCE)
@@ -18,19 +18,13 @@ class KlibMetadataVersion(major: Int, minor: Int, patch: Int) : BinaryVersion(ma
val INSTANCE = KlibMetadataVersion(1, 0, 0)
@JvmField
val INVALID_VERSION = KlibMetadataVersion(-1, -1, -1)
val INVALID_VERSION = KlibMetadataVersion()
}
}
fun KlibMetadataVersion(vararg values: Int): KlibMetadataVersion {
if (values.size != 3) error("Metadata version should be in major.minor.patch format: $values")
return KlibMetadataVersion(values[0], values[1], values[2])
}
val KotlinLibrary.metadataVersion: KlibMetadataVersion
val KotlinLibrary.metadataVersion: KlibMetadataVersion?
get() {
val versionString = manifestProperties.getProperty(KLIB_PROPERTY_METADATA_VERSION)
val versionIntArray = BinaryVersion.parseVersionArray(versionString)
?: error("Could not parse metadata version: $versionString")
val versionString = manifestProperties.getProperty(KLIB_PROPERTY_METADATA_VERSION) ?: return null
val versionIntArray = BinaryVersion.parseVersionArray(versionString) ?: return null
return KlibMetadataVersion(*versionIntArray)
}
}
@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil
import org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider
import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.kotlin.library.KotlinAbiVersion
/** TODO: merge [KotlinNativeABICompatibilityChecker] in the future with [UnsupportedAbiVersionNotificationPanelProvider], KT-34525 */
class KotlinNativeABICompatibilityChecker(private val project: Project) : ProjectComponent, Disposable {
@@ -84,7 +83,7 @@ class KotlinNativeABICompatibilityChecker(private val project: Project) : Projec
private fun getLibrariesToNotifyAbout(): Map<String, NativeLibraryInfo> = synchronized(this) {
val incompatibleLibraries = getModuleInfosFromIdeaModel(project).asSequence()
.filterIsInstance<NativeLibraryInfo>()
.filter { it.safeAbiVersion != KotlinAbiVersion.CURRENT }
.filter { !it.metadataInfo.isCompatible }
.associateBy { it.libraryRoot }
val newEntries = if (cachedIncompatibleLibraries.isNotEmpty())
@@ -104,23 +103,23 @@ class KotlinNativeABICompatibilityChecker(private val project: Project) : Projec
val librariesByGroups = HashMap<Pair<LibraryGroup, Boolean>, MutableList<Pair<String, String>>>()
librariesToNotify.forEach { (libraryRoot, libraryInfo) ->
val isOldAbi = (libraryInfo.safeAbiVersion?.version ?: 0) < KotlinAbiVersion.CURRENT.version
val isOldMetadata = (libraryInfo.metadataInfo as? NativeLibraryInfo.MetadataInfo.Incompatible)?.isOlder ?: true
val (libraryName, libraryGroup) = parseIDELibraryName(libraryInfo)
librariesByGroups.computeIfAbsent(libraryGroup to isOldAbi) { mutableListOf() } += libraryName to libraryRoot
librariesByGroups.computeIfAbsent(libraryGroup to isOldMetadata) { mutableListOf() } += libraryName to libraryRoot
}
return librariesByGroups.keys.sortedWith(
compareBy(
{ (libraryGroup, _) -> libraryGroup },
{ (_, isOldAbi) -> isOldAbi }
{ (_, isOldMetadata) -> isOldMetadata }
)
).map { key ->
val (libraryGroup, isOldAbi) = key
val (libraryGroup, isOldMetadata) = key
val libraries =
librariesByGroups.getValue(key).sortedWith(compareBy(LIBRARY_NAME_COMPARATOR) { (libraryName, _) -> libraryName })
val compilerVersionText = if (isOldAbi) "an older" else "a newer"
val compilerVersionText = if (isOldMetadata) "an older" else "a newer"
val message = when (libraryGroup) {
is LibraryGroup.FromDistribution -> {
@@ -16,6 +16,8 @@ import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.PlatformAnalysisParameters
import org.jetbrains.kotlin.analyzer.ResolverForModuleFactory
import org.jetbrains.kotlin.analyzer.getCapability
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.metadataVersion
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.functions.functionInterfacePackageFragmentProvider
@@ -27,10 +29,10 @@ import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin
import org.jetbrains.kotlin.descriptors.konan.KlibModuleOrigin
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.Companion.safeAbiVersion
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.Companion.isCompatible
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.Companion.safeMetadataVersion
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.MetadataInfo.Compatible
import org.jetbrains.kotlin.ide.konan.NativeLibraryInfo.MetadataInfo.Incompatible
import org.jetbrains.kotlin.ide.konan.analyzer.NativeResolverForModuleFactory
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.project.LibraryInfo
import org.jetbrains.kotlin.idea.caches.project.SdkInfo
import org.jetbrains.kotlin.idea.caches.project.lazyClosure
@@ -57,7 +59,7 @@ fun KotlinLibrary.createPackageFragmentProvider(
moduleDescriptor: ModuleDescriptor
): PackageFragmentProvider? {
if (!safeAbiVersion.isCompatible) return null
if (safeMetadataVersion?.isCompatible() != true) return null
val libraryProto = CachingIdeKonanLibraryMetadataLoader.loadModuleHeader(this)
@@ -187,14 +189,33 @@ private fun createKotlinNativeBuiltIns(moduleInfo: ModuleInfo, projectContext: P
private fun ModuleInfo.findNativeStdlib(): NativeLibraryInfo? =
dependencies().lazyClosure { it.dependencies() }
.filterIsInstance<NativeLibraryInfo>()
.firstOrNull { it.isStdlib && it.safeAbiVersion.isCompatible }
.firstOrNull { it.isStdlib && it.metadataInfo.isCompatible }
class NativeLibraryInfo(project: Project, library: Library, val libraryRoot: String) : LibraryInfo(project, library) {
sealed class MetadataInfo {
abstract val isCompatible: Boolean
object Compatible : MetadataInfo() {
override val isCompatible get() = true
}
class Incompatible(val isOlder: Boolean) : MetadataInfo() {
override val isCompatible get() = false
}
}
private val nativeLibrary = createKotlinLibrary(File(libraryRoot))
val isStdlib get() = libraryRoot.endsWith(KONAN_STDLIB_NAME)
val safeAbiVersion get() = nativeLibrary.safeAbiVersion
val metadataInfo by lazy {
val metadataVersion = nativeLibrary.safeMetadataVersion
when {
metadataVersion == null -> Incompatible(true) // too old KLIB format, even doesn't have metadata version
!metadataVersion.isCompatible() -> Incompatible(!metadataVersion.isAtLeast(KlibMetadataVersion.INSTANCE))
else -> Compatible
}
}
override fun getLibraryRoots() = listOf(libraryRoot)
@@ -215,8 +236,7 @@ class NativeLibraryInfo(project: Project, library: Library, val libraryRoot: Str
companion object {
val NATIVE_LIBRARY_CAPABILITY = ModuleDescriptor.Capability<KotlinLibrary>("KotlinNativeLibrary")
internal val KotlinLibrary.safeAbiVersion get() = this.readSafe(null) { versions.abiVersion }
internal val KotlinAbiVersion?.isCompatible get() = this == KotlinAbiVersion.CURRENT
internal val KotlinLibrary.safeMetadataVersion get() = this.readSafe(null) { metadataVersion }
private fun <T> KotlinLibrary.readSafe(defaultValue: T, action: KotlinLibrary.() -> T) = try {
action()
@@ -9,11 +9,13 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.BaseComponent
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.ContainerUtil.createConcurrentWeakValueMap
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf
import org.jetbrains.kotlin.library.metadata.parseModuleHeader
import org.jetbrains.kotlin.library.metadata.parsePackageFragment
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
import java.io.IOException
import java.util.*
@@ -39,7 +41,7 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
private val packageFragmentCache = createConcurrentWeakValueMap<CacheKey, CacheValue<ProtoBuf.PackageFragment>>()
private val moduleHeaderCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataProtoBuf.Header>>()
private val libraryVersioningCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KotlinLibraryVersioning>>()
private val libraryMetadataVersionCache = createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataVersion>>()
fun getCachedPackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
check(packageFragmentFile.extension == KLIB_METADATA_FILE_EXTENSION) {
@@ -61,18 +63,18 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
}.value
}
private fun isAbiCompatible(libraryRoot: VirtualFile): Boolean {
private fun isMetadataCompatible(libraryRoot: VirtualFile): Boolean {
val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return false
val versioning = libraryVersioningCache.computeIfAbsent(CacheKey(manifestFile)) {
CacheValue(computeLibraryVersioning(manifestFile))
}.value
val metadataVersion = libraryMetadataVersionCache.computeIfAbsent(CacheKey(manifestFile)) {
CacheValue(computeLibraryMetadataVersion(manifestFile))
}.value ?: return false
return versioning?.abiVersion == KotlinAbiVersion.CURRENT
return metadataVersion.isCompatible()
}
private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? {
if (!isAbiCompatible(packageFragmentFile.parent.parent.parent))
if (!isMetadataCompatible(packageFragmentFile.parent.parent.parent))
return null
return try {
@@ -83,7 +85,7 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
}
private fun computeModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? {
if (!isAbiCompatible(moduleHeaderFile.parent.parent))
if (!isMetadataCompatible(moduleHeaderFile.parent.parent))
return null
return try {
@@ -93,8 +95,9 @@ class KotlinNativeLoadingMetadataCache : BaseComponent {
}
}
private fun computeLibraryVersioning(manifestFile: VirtualFile): KotlinLibraryVersioning? = try {
Properties().apply { manifestFile.inputStream.use { load(it) } }.readKonanLibraryVersioning()
private fun computeLibraryMetadataVersion(manifestFile: VirtualFile): KlibMetadataVersion? = try {
val versioning = Properties().apply { manifestFile.inputStream.use { load(it) } }.readKonanLibraryVersioning()
versioning.metadataVersion?.let(BinaryVersion.Companion::parseVersionArray)?.let(::KlibMetadataVersion)
} catch (_: IOException) {
// ignore and cache null value
null
@@ -179,10 +179,10 @@ private fun loadLibrary(location: File): KotlinLibrary {
printErrorAndExit("library does not have metadata version specified in manifest: $location")
val metadataVersion = library.metadataVersion
if (!metadataVersion.isCompatible())
if (metadataVersion?.isCompatible() != true)
printErrorAndExit(
"""
library has incompatible metadata version $metadataVersion: $location,
library has incompatible metadata version ${metadataVersion ?: "\"unknown\""}: $location,
please make sure that all libraries passed to commonizer compatible metadata version ${KlibMetadataVersion.INSTANCE}
""".trimIndent()
)