[IR] Enhance error reporting for IR linking issues
^KT-44626 Typical use case: - There are two KLIB libraries: A and B. - Library A has two versions: A.v1 (older) and A.v2 (newer). - A.v2 is ABI-incompatible with A.v1. - B depends on A and was compiled against A.v1. - An attempt to build the application with A.v2 and B fails with weird error message. It's unclear for end user what's wrong and what needs to be done to fix the issue. The fix improves error reporting for the following particular cases: - A symbol that is gone (KT-41378) - A class that became a typealias (KT-47285, KT-46697) - A typealias that became a class (KT-46340)
This commit is contained in:
+13
-2
@@ -6,6 +6,8 @@
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.UserVisibleIrModulesSupport
|
||||
import org.jetbrains.kotlin.backend.konan.serialization.KonanUserVisibleIrModulesSupport
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
@@ -91,6 +93,15 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
|
||||
val resolvedLibraries get() = resolve.resolvedLibraries
|
||||
|
||||
internal val userVisibleIrModulesSupport = KonanUserVisibleIrModulesSupport(
|
||||
externalDependenciesLoader = UserVisibleIrModulesSupport.ExternalDependenciesLoader.from(
|
||||
externalDependenciesFile = configuration.get(KonanConfigKeys.EXTERNAL_DEPENDENCIES)?.let(::File),
|
||||
onMalformedExternalDependencies = { warningMessage ->
|
||||
configuration.report(CompilerMessageSeverity.STRONG_WARNING, warningMessage)
|
||||
}),
|
||||
konanKlibDir = File(distribution.klib)
|
||||
)
|
||||
|
||||
internal val cacheSupport = CacheSupport(configuration, resolvedLibraries, target, produce)
|
||||
|
||||
internal val cachedLibraries: CachedLibraries
|
||||
@@ -206,10 +217,10 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
File(distribution.defaultNatives(target)).child("exceptionsSupport.bc").absolutePath
|
||||
|
||||
internal val nativeLibraries: List<String> =
|
||||
configuration.getList(KonanConfigKeys.NATIVE_LIBRARY_FILES)
|
||||
configuration.getList(KonanConfigKeys.NATIVE_LIBRARY_FILES)
|
||||
|
||||
internal val includeBinaries: List<String> =
|
||||
configuration.getList(KonanConfigKeys.INCLUDED_BINARY_FILES)
|
||||
configuration.getList(KonanConfigKeys.INCLUDED_BINARY_FILES)
|
||||
|
||||
internal val languageVersionSettings =
|
||||
configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS)!!
|
||||
|
||||
+2
-1
@@ -101,7 +101,8 @@ internal fun Context.psiToIr(
|
||||
stubGenerator,
|
||||
irProviderForCEnumsAndCStructs,
|
||||
exportedDependencies,
|
||||
config.cachedLibraries
|
||||
config.cachedLibraries,
|
||||
config.userVisibleIrModulesSupport
|
||||
).also { linker ->
|
||||
|
||||
// context.config.librariesWithDependencies could change at each iteration.
|
||||
|
||||
+4
-2
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.backend.common.overrides.FakeOverrideBuilder
|
||||
import org.jetbrains.kotlin.backend.common.overrides.FakeOverrideClassFilter
|
||||
import org.jetbrains.kotlin.backend.common.serialization.*
|
||||
import org.jetbrains.kotlin.backend.common.serialization.encodings.BinarySymbolData
|
||||
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.UserVisibleIrModulesSupport
|
||||
import org.jetbrains.kotlin.backend.konan.CachedLibraries
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInteropLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.ir.interop.IrProviderForCEnumAndCStructStubs
|
||||
@@ -72,7 +73,8 @@ internal class KonanIrLinker(
|
||||
private val stubGenerator: DeclarationStubGenerator,
|
||||
private val cenumsProvider: IrProviderForCEnumAndCStructStubs,
|
||||
exportedDependencies: List<ModuleDescriptor>,
|
||||
private val cachedLibraries: CachedLibraries
|
||||
private val cachedLibraries: CachedLibraries,
|
||||
override val userVisibleIrModulesSupport: UserVisibleIrModulesSupport
|
||||
) : KotlinIrLinker(currentModule, messageLogger, builtIns, symbolTable, exportedDependencies) {
|
||||
|
||||
companion object {
|
||||
@@ -204,7 +206,7 @@ internal class KonanIrLinker(
|
||||
val descriptor = resolveDescriptor(idSig)
|
||||
val actualModule = descriptor.module
|
||||
if (actualModule !== moduleDescriptor) {
|
||||
val moduleDeserializer = deserializersForModules[actualModule.name.asString()] ?: error("No module deserializer for $actualModule")
|
||||
val moduleDeserializer = resolveModuleDeserializer(actualModule, idSig)
|
||||
moduleDeserializer.addModuleReachableTopLevel(idSig)
|
||||
return symbolTable.referenceClassFromLinker(idSig)
|
||||
}
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.backend.konan.serialization
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.IrModuleDeserializer
|
||||
import org.jetbrains.kotlin.backend.common.serialization.linkerissues.UserVisibleIrModulesSupport
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
import org.jetbrains.kotlin.konan.library.KONAN_PLATFORM_LIBS_NAME_PREFIX
|
||||
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
|
||||
import org.jetbrains.kotlin.library.KotlinLibrary
|
||||
import org.jetbrains.kotlin.library.RequiredUnresolvedLibrary
|
||||
import org.jetbrains.kotlin.library.unresolvedDependencies
|
||||
import org.jetbrains.kotlin.utils.ResolvedDependency
|
||||
import org.jetbrains.kotlin.utils.ResolvedDependencyArtifactPath
|
||||
import org.jetbrains.kotlin.utils.ResolvedDependencyId
|
||||
import org.jetbrains.kotlin.utils.ResolvedDependencyVersion
|
||||
|
||||
class KonanUserVisibleIrModulesSupport(
|
||||
externalDependenciesLoader: ExternalDependenciesLoader,
|
||||
private val konanKlibDir: File
|
||||
) : UserVisibleIrModulesSupport(externalDependenciesLoader) {
|
||||
override fun getUserVisibleModules(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> {
|
||||
return compressedModules(deserializers)
|
||||
}
|
||||
|
||||
override fun modulesFromDeserializers(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> {
|
||||
val moduleToCompilerVersion: MutableMap<ResolvedDependencyId, ResolvedDependencyVersion> = mutableMapOf()
|
||||
val kotlinNativeBundledLibraries: MutableSet<ResolvedDependencyId> = mutableSetOf()
|
||||
|
||||
// Transform deserializers to [ModuleWithUninitializedDependencies]s.
|
||||
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.mapNotNull { deserializer ->
|
||||
val library: KotlinLibrary = deserializer.kotlinLibrary ?: return@mapNotNull null
|
||||
|
||||
val compilerVersion: ResolvedDependencyVersion = library.compilerVersion
|
||||
val isDefaultLibrary = library.isDefaultLibrary
|
||||
|
||||
// For default libraries the version is the same as the version of the compiler.
|
||||
// Note: Empty string means missing (unknown) version.
|
||||
val libraryVersion: ResolvedDependencyVersion = if (isDefaultLibrary) compilerVersion else ResolvedDependencyVersion.EMPTY
|
||||
|
||||
val moduleId = getUserVisibleModuleId(deserializer)
|
||||
val module = ResolvedDependency(
|
||||
id = moduleId,
|
||||
selectedVersion = libraryVersion,
|
||||
requestedVersionsByIncomingDependencies = mutableMapOf(), // To be initialized in a separate pass below.
|
||||
artifactPaths = mutableSetOf(ResolvedDependencyArtifactPath(library.libraryFile.absolutePath))
|
||||
)
|
||||
|
||||
moduleToCompilerVersion[moduleId] = compilerVersion
|
||||
if (isDefaultLibrary) kotlinNativeBundledLibraries += moduleId
|
||||
|
||||
// Don't rely on dependencies in IrModuleDeserializer. In Kotlin/Native each module depends on all other modules,
|
||||
// and this contradicts with the real module dependencies as written in KLIB manifest files.
|
||||
val outgoingDependencyIds = library.unresolvedDependencies.map { it.moduleId }
|
||||
|
||||
moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds)
|
||||
}.toMap()
|
||||
|
||||
// Stamp dependencies.
|
||||
return modules.mapValues { (moduleId, moduleWithUninitializedDependencies) ->
|
||||
val (module, outgoingDependencyIds) = moduleWithUninitializedDependencies
|
||||
outgoingDependencyIds.forEach { outgoingDependencyId ->
|
||||
val outgoingDependencyModule = modules.getValue(outgoingDependencyId).module
|
||||
if (outgoingDependencyId in kotlinNativeBundledLibraries) {
|
||||
outgoingDependencyModule.requestedVersionsByIncomingDependencies[moduleId] = moduleToCompilerVersion.getValue(moduleId)
|
||||
} else {
|
||||
outgoingDependencyModule.requestedVersionsByIncomingDependencies[moduleId] = outgoingDependencyModule.selectedVersion
|
||||
}
|
||||
}
|
||||
module
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an optimization to avoid displaying 100+ Kotlin/Native platform libraries to the user.
|
||||
* Instead, lets compress them into a single row and avoid excessive output.
|
||||
*/
|
||||
private fun compressedModules(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> {
|
||||
val compressedModules: MutableMap<ResolvedDependencyId, ResolvedDependency> = mergedModules(deserializers)
|
||||
|
||||
var platformLibrariesVersion: ResolvedDependencyVersion? = null // Must be the same version to succeed.
|
||||
val platformLibraries: MutableList<ResolvedDependency> = mutableListOf() // All platform libraries to be patched.
|
||||
val outgoingDependencyIds: MutableSet<ResolvedDependencyId> = mutableSetOf() // All outgoing dependencies from platform libraries.
|
||||
|
||||
for ((moduleId, module) in compressedModules) {
|
||||
if (moduleId.isKonanPlatformLibrary) {
|
||||
if (ResolvedDependencyId.SOURCE_CODE_MODULE_ID !in module.requestedVersionsByIncomingDependencies) {
|
||||
continue
|
||||
}
|
||||
|
||||
platformLibrariesVersion = when (platformLibrariesVersion) {
|
||||
null, module.selectedVersion -> module.selectedVersion
|
||||
else -> {
|
||||
// Multiple versions of platform libs. Give up.
|
||||
return compressedModules
|
||||
}
|
||||
}
|
||||
|
||||
platformLibraries += module
|
||||
} else {
|
||||
module.requestedVersionsByIncomingDependencies.keys.forEach { incomingDependencyId ->
|
||||
if (incomingDependencyId.isKonanPlatformLibrary) {
|
||||
outgoingDependencyIds += moduleId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (platformLibraries.isNotEmpty()) {
|
||||
platformLibraries.forEach { it.visibleAsFirstLevelDependency = false }
|
||||
|
||||
val compressedModuleId = ResolvedDependencyId("$KONAN_PLATFORM_LIBS_NAME_PREFIX* (${platformLibraries.size} libraries)")
|
||||
val compressedModule = ResolvedDependency(
|
||||
id = compressedModuleId,
|
||||
selectedVersion = platformLibrariesVersion!!,
|
||||
requestedVersionsByIncomingDependencies = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to platformLibrariesVersion),
|
||||
artifactPaths = mutableSetOf()
|
||||
)
|
||||
|
||||
outgoingDependencyIds.forEach { outgoingDependencyId ->
|
||||
val outgoingDependency = compressedModules.getValue(outgoingDependencyId)
|
||||
outgoingDependency.requestedVersionsByIncomingDependencies[compressedModuleId] = compressedModule.selectedVersion
|
||||
}
|
||||
|
||||
compressedModules[compressedModuleId] = compressedModule
|
||||
}
|
||||
|
||||
return compressedModules
|
||||
}
|
||||
|
||||
private val KotlinLibrary.compilerVersion: ResolvedDependencyVersion
|
||||
get() = ResolvedDependencyVersion(versions.compilerVersion.orEmpty())
|
||||
|
||||
// This is much safer check then KotlinLibrary.isDefault, which may return false even for "stdlib" when
|
||||
// Kotlin/Native compiler is running with "-nostdlib", "-no-endorsed-libs", "-no-default-libs" arguments.
|
||||
private val KotlinLibrary.isDefaultLibrary: Boolean
|
||||
get() = libraryFile.startsWith(konanKlibDir)
|
||||
|
||||
override val ResolvedDependencyId.isKotlinLibrary: Boolean
|
||||
get() = uniqueNames.any { uniqueName -> uniqueName == KONAN_STDLIB_NAME || uniqueName.startsWith(KOTLIN_LIBRARY_PREFIX) }
|
||||
|
||||
companion object {
|
||||
private val RequiredUnresolvedLibrary.moduleId: ResolvedDependencyId
|
||||
get() = ResolvedDependencyId(path) // Yep, it's named "path" but in fact holds unique name of the library.
|
||||
|
||||
private val ResolvedDependencyId.isKonanPlatformLibrary: Boolean
|
||||
get() = uniqueNames.any { it.startsWith(KONAN_PLATFORM_LIBS_NAME_PREFIX) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user