[IR] Enhance error reporting for IR linking issues

^KT-44626

Support correct error reporting both with native static caches and without native static caches.
This commit is contained in:
Dmitriy Dolovov
2021-08-12 11:09:32 +03:00
committed by Space
parent 3cd43dced4
commit af12d61388
9 changed files with 381 additions and 149 deletions
@@ -67,6 +67,7 @@ class SignatureIdNotFoundInModuleWithDependencies(
allModules = allModules, allModules = allModules,
problemModuleIds = setOf(problemModuleId), problemModuleIds = setOf(problemModuleId),
problemCause = "This module requires symbol ${idSignature.render()}", problemCause = "This module requires symbol ${idSignature.render()}",
sourceCodeModuleId = userVisibleIrModulesSupport.sourceCodeModuleId,
moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator
) )
} }
@@ -113,6 +114,7 @@ class SymbolTypeMismatch(
potentiallyConflictingDependencies = findPotentiallyConflictingIncomingDependencies( potentiallyConflictingDependencies = findPotentiallyConflictingIncomingDependencies(
problemModuleId = declaringModuleId, problemModuleId = declaringModuleId,
allModules = allModules, allModules = allModules,
sourceCodeModuleId = userVisibleIrModulesSupport.sourceCodeModuleId,
), ),
moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator
) )
@@ -125,6 +127,7 @@ class SymbolTypeMismatch(
problemCause = "This module contains ${ problemCause = "This module contains ${
idSignature?.render()?.let { "symbol $it" } ?: "a symbol" idSignature?.render()?.let { "symbol $it" } ?: "a symbol"
} that is the cause of the conflict", } that is the cause of the conflict",
sourceCodeModuleId = userVisibleIrModulesSupport.sourceCodeModuleId,
moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator
) )
} }
@@ -159,6 +162,7 @@ private fun StringBuilder.appendProjectDependencies(
allModules: Map<ResolvedDependencyId, ResolvedDependency>, allModules: Map<ResolvedDependencyId, ResolvedDependency>,
problemModuleIds: Set<ResolvedDependencyId>, problemModuleIds: Set<ResolvedDependencyId>,
problemCause: String, problemCause: String,
sourceCodeModuleId: ResolvedDependencyId,
moduleIdComparator: Comparator<ResolvedDependencyId> moduleIdComparator: Comparator<ResolvedDependencyId>
) { ) {
append("\n\nProject dependencies:") append("\n\nProject dependencies:")
@@ -195,8 +199,7 @@ private fun StringBuilder.appendProjectDependencies(
append('\n').append(data.regularLinePrefix) append('\n').append(data.regularLinePrefix)
append(module.id) append(module.id)
val incomingDependencyId: ResolvedDependencyId = parentData?.incomingDependencyId val incomingDependencyId: ResolvedDependencyId = parentData?.incomingDependencyId ?: sourceCodeModuleId
?: ResolvedDependencyId.SOURCE_CODE_MODULE_ID
val requestedVersion: ResolvedDependencyVersion = module.requestedVersionsByIncomingDependencies.getValue(incomingDependencyId) val requestedVersion: ResolvedDependencyVersion = module.requestedVersionsByIncomingDependencies.getValue(incomingDependencyId)
if (!requestedVersion.isEmpty() || !module.selectedVersion.isEmpty()) { if (!requestedVersion.isEmpty() || !module.selectedVersion.isEmpty()) {
append(": ") append(": ")
@@ -240,8 +243,7 @@ private fun StringBuilder.appendProjectDependencies(
} }
// Find first-level dependencies. I.e. the modules that the source code module directly depends on. // Find first-level dependencies. I.e. the modules that the source code module directly depends on.
val firstLevelDependencies: Collection<ResolvedDependency> = val firstLevelDependencies: Collection<ResolvedDependency> = incomingDependencyIdToDependencies.getValue(sourceCodeModuleId)
incomingDependencyIdToDependencies.getValue(ResolvedDependencyId.SOURCE_CODE_MODULE_ID)
renderModules(firstLevelDependencies, parentData = null) renderModules(firstLevelDependencies, parentData = null)
@@ -375,7 +377,8 @@ private fun findPotentiallyConflictingOutgoingDependencies(
*/ */
private fun findPotentiallyConflictingIncomingDependencies( private fun findPotentiallyConflictingIncomingDependencies(
problemModuleId: ResolvedDependencyId, problemModuleId: ResolvedDependencyId,
allModules: Map<ResolvedDependencyId, ResolvedDependency> allModules: Map<ResolvedDependencyId, ResolvedDependency>,
sourceCodeModuleId: ResolvedDependencyId
): Map<ResolvedDependencyId, PotentialConflictDescription> { ): Map<ResolvedDependencyId, PotentialConflictDescription> {
val dependencyStatesMap: MutableMap<ResolvedDependencyId, MutableSet<DependencyState>> = mutableMapOf() val dependencyStatesMap: MutableMap<ResolvedDependencyId, MutableSet<DependencyState>> = mutableMapOf()
@@ -384,7 +387,7 @@ private fun findPotentiallyConflictingIncomingDependencies(
val module = allModules.findMatchingModule(moduleId) val module = allModules.findMatchingModule(moduleId)
module.requestedVersionsByIncomingDependencies.forEach { (incomingDependencyId, requestedVersion) -> module.requestedVersionsByIncomingDependencies.forEach { (incomingDependencyId, requestedVersion) ->
if (incomingDependencyId == ResolvedDependencyId.SOURCE_CODE_MODULE_ID) return@forEach if (incomingDependencyId == sourceCodeModuleId) return@forEach
val dependencyState: DependencyState = when { val dependencyState: DependencyState = when {
aboveConflictingDependency -> { aboveConflictingDependency -> {
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.uniqueName import org.jetbrains.kotlin.library.uniqueName
import org.jetbrains.kotlin.utils.* import org.jetbrains.kotlin.utils.*
open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader: ExternalDependenciesLoader) { open class UserVisibleIrModulesSupport(externalDependenciesLoader: ExternalDependenciesLoader) {
/** /**
* Load external [ResolvedDependency]s provided by the build system. These dependencies: * Load external [ResolvedDependency]s provided by the build system. These dependencies:
* - all have [ResolvedDependency.selectedVersion] specified * - all have [ResolvedDependency.selectedVersion] specified
@@ -23,24 +23,24 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
* not visible to the build system * not visible to the build system
*/ */
interface ExternalDependenciesLoader { interface ExternalDependenciesLoader {
fun load(): Collection<ResolvedDependency> fun load(): ResolvedDependencies
companion object { companion object {
val EMPTY = object : ExternalDependenciesLoader { val EMPTY = object : ExternalDependenciesLoader {
override fun load(): Collection<ResolvedDependency> = emptyList() override fun load(): ResolvedDependencies = ResolvedDependencies.EMPTY
} }
fun from(externalDependenciesFile: File?, onMalformedExternalDependencies: (String) -> Unit): ExternalDependenciesLoader = fun from(externalDependenciesFile: File?, onMalformedExternalDependencies: (String) -> Unit): ExternalDependenciesLoader =
if (externalDependenciesFile != null) if (externalDependenciesFile != null)
object : ExternalDependenciesLoader { object : ExternalDependenciesLoader {
override fun load(): Collection<ResolvedDependency> { override fun load(): ResolvedDependencies {
return if (externalDependenciesFile.exists) { return if (externalDependenciesFile.exists) {
// Deserialize external dependencies from the [externalDependenciesFile]. // Deserialize external dependencies from the [externalDependenciesFile].
val externalDependenciesText = String(externalDependenciesFile.readBytes()) val externalDependenciesText = String(externalDependenciesFile.readBytes())
ResolvedDependenciesSupport.deserialize(externalDependenciesText) { lineNo, line -> ResolvedDependenciesSupport.deserialize(externalDependenciesText) { lineNo, line ->
onMalformedExternalDependencies("Malformed external dependencies at $externalDependenciesFile:$lineNo: $line") onMalformedExternalDependencies("Malformed external dependencies at $externalDependenciesFile:$lineNo: $line")
} }
} else emptyList() } else ResolvedDependencies.EMPTY
} }
} }
else else
@@ -48,6 +48,16 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
} }
} }
private val externalDependencies: ResolvedDependencies by lazy {
externalDependenciesLoader.load()
}
private val externalDependencyModules: Collection<ResolvedDependency>
get() = externalDependencies.modules
val sourceCodeModuleId: ResolvedDependencyId
get() = externalDependencies.sourceCodeModuleId
fun getUserVisibleModuleId(deserializer: IrModuleDeserializer): ResolvedDependencyId { fun getUserVisibleModuleId(deserializer: IrModuleDeserializer): ResolvedDependencyId {
val nameFromMetadataModuleHeader: String = deserializer.moduleFragment.name.asStringStripSpecialMarkers() val nameFromMetadataModuleHeader: String = deserializer.moduleFragment.name.asStringStripSpecialMarkers()
val nameFromKlibManifest: String? = deserializer.kotlinLibrary?.uniqueName val nameFromKlibManifest: String? = deserializer.kotlinLibrary?.uniqueName
@@ -65,23 +75,30 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
* (and therefore are missing in [ExternalDependenciesLoader.load]) but are provided by the compiler. For Kotlin/Native such * (and therefore are missing in [ExternalDependenciesLoader.load]) but are provided by the compiler. For Kotlin/Native such
* libraries are stdlib, endorsed and platform libraries. * libraries are stdlib, endorsed and platform libraries.
*/ */
protected open fun modulesFromDeserializers(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> { protected open fun modulesFromDeserializers(
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.associate { deserializer -> deserializers: Collection<IrModuleDeserializer>,
excludedModuleIds: Set<ResolvedDependencyId>
): Map<ResolvedDependencyId, ResolvedDependency> {
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.mapNotNull { deserializer ->
val moduleId = getUserVisibleModuleId(deserializer) val moduleId = getUserVisibleModuleId(deserializer)
if (moduleId in excludedModuleIds) return@mapNotNull null
val module = ResolvedDependency( val module = ResolvedDependency(
id = moduleId, id = moduleId,
// TODO: support extracting all the necessary details for non-Native libs: selectedVersion, requestedVersions, artifacts // TODO: support extracting all the necessary details for non-Native libs: selectedVersion, requestedVersions, artifacts
selectedVersion = ResolvedDependencyVersion.EMPTY, selectedVersion = ResolvedDependencyVersion.EMPTY,
// Assumption: As we don't know for sure which modules the source code module depends on directly and which modules // Assumption: As we don't know for sure which modules the source code module depends on directly and which modules
// it depends on transitively, so let's assume it depends on all modules directly. // it depends on transitively, so let's assume it depends on all modules directly.
requestedVersionsByIncomingDependencies = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to ResolvedDependencyVersion.EMPTY), requestedVersionsByIncomingDependencies = mutableMapOf(
ResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID to ResolvedDependencyVersion.EMPTY
),
artifactPaths = mutableSetOf() artifactPaths = mutableSetOf()
) )
val outgoingDependencyIds = deserializer.moduleDependencies.map { getUserVisibleModuleId(it) } val outgoingDependencyIds = deserializer.moduleDependencies.map { getUserVisibleModuleId(it) }
moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds) moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds)
} }.toMap()
// Stamp dependencies. // Stamp dependencies.
return modules.stampDependenciesWithRequestedVersionEqualToSelectedVersion() return modules.stampDependenciesWithRequestedVersionEqualToSelectedVersion()
@@ -91,9 +108,6 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
* The result of the merge of [ExternalDependenciesLoader.load] and [modulesFromDeserializers]. * The result of the merge of [ExternalDependenciesLoader.load] and [modulesFromDeserializers].
*/ */
protected fun mergedModules(deserializers: Collection<IrModuleDeserializer>): MutableMap<ResolvedDependencyId, ResolvedDependency> { protected fun mergedModules(deserializers: Collection<IrModuleDeserializer>): MutableMap<ResolvedDependencyId, ResolvedDependency> {
// First, load external dependencies.
val externalDependencyModules: Collection<ResolvedDependency> = externalDependenciesLoader.load()
val externalDependencyModulesByNames: Map</* unique name */ String, ResolvedDependency> = val externalDependencyModulesByNames: Map</* unique name */ String, ResolvedDependency> =
mutableMapOf<String, ResolvedDependency>().apply { mutableMapOf<String, ResolvedDependency>().apply {
externalDependencyModules.forEach { externalDependency -> externalDependencyModules.forEach { externalDependency ->
@@ -118,7 +132,10 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
val providedModules = mutableListOf<ResolvedDependency>() val providedModules = mutableListOf<ResolvedDependency>()
// Next, merge external dependencies with dependencies from deserializers. // Next, merge external dependencies with dependencies from deserializers.
modulesFromDeserializers(deserializers).forEach { (moduleId, module) -> modulesFromDeserializers(
deserializers = deserializers,
excludedModuleIds = setOf(sourceCodeModuleId)
).forEach { (moduleId, module) ->
val externalDependencyModule = findMatchingExternalDependencyModule(moduleId) val externalDependencyModule = findMatchingExternalDependencyModule(moduleId)
if (externalDependencyModule != null) { if (externalDependencyModule != null) {
// Just add missing dependencies to the same module in [mergedModules]. // Just add missing dependencies to the same module in [mergedModules].
@@ -146,7 +163,7 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
// Just keep the module as is. If it has no incoming dependencies, then treat it as // Just keep the module as is. If it has no incoming dependencies, then treat it as
// the first-level dependency module (i.e. the module that only the source code module depends on). // the first-level dependency module (i.e. the module that only the source code module depends on).
if (module.requestedVersionsByIncomingDependencies.isEmpty()) { if (module.requestedVersionsByIncomingDependencies.isEmpty()) {
module.requestedVersionsByIncomingDependencies[ResolvedDependencyId.SOURCE_CODE_MODULE_ID] = module.selectedVersion module.requestedVersionsByIncomingDependencies[sourceCodeModuleId] = module.selectedVersion
} }
} }
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.util package org.jetbrains.kotlin.util
import org.jetbrains.kotlin.utils.ResolvedDependencies
import org.jetbrains.kotlin.utils.ResolvedDependenciesSupport import org.jetbrains.kotlin.utils.ResolvedDependenciesSupport
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.fail import org.junit.Assert.fail
@@ -14,6 +15,7 @@ class ResolvedDependenciesSupportTest {
@Test @Test
fun success1() { fun success1() {
val originalText = """ val originalText = """
|0 \
|1 io.ktor:ktor-io,io.ktor:ktor-io-macosx64[1.5.4] #0[1.5.4] |1 io.ktor:ktor-io,io.ktor:ktor-io-macosx64[1.5.4] #0[1.5.4]
|${'\t'}/some/path/ktor-io.klib |${'\t'}/some/path/ktor-io.klib
|${'\t'}/some/path/ktor-io-cinterop-bits.klib |${'\t'}/some/path/ktor-io-cinterop-bits.klib
@@ -28,10 +30,10 @@ class ResolvedDependenciesSupportTest {
| |
""".trimMargin() """.trimMargin()
val deserializedModules = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line -> val (deserializedModules, sourceCodeModuleId) = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
fail("Unexpected failure at line $lineNo: $line") fail("Unexpected failure at line $lineNo: $line")
} }
val restoredText = ResolvedDependenciesSupport.serialize(deserializedModules) val restoredText = ResolvedDependenciesSupport.serialize(ResolvedDependencies(deserializedModules, sourceCodeModuleId))
assertEquals(originalText, restoredText) assertEquals(originalText, restoredText)
} }
@@ -39,6 +41,7 @@ class ResolvedDependenciesSupportTest {
@Test @Test
fun success2() { fun success2() {
val originalText = """ val originalText = """
|0 \
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0] |1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0]
|${'\t'}/some/path/liba.klib |${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native[1.0] #0[1.0] |2 org.sample:libb,org.sample:libb-native[1.0] #0[1.0]
@@ -46,10 +49,10 @@ class ResolvedDependenciesSupportTest {
| |
""".trimMargin() """.trimMargin()
val deserializedModules = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line -> val (deserializedModules, sourceCodeModuleId) = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
fail("Unexpected failure at line $lineNo: $line") fail("Unexpected failure at line $lineNo: $line")
} }
val restoredText = ResolvedDependenciesSupport.serialize(deserializedModules) val restoredText = ResolvedDependenciesSupport.serialize(ResolvedDependencies(deserializedModules, sourceCodeModuleId))
assertEquals(originalText, restoredText) assertEquals(originalText, restoredText)
} }
@@ -57,6 +60,7 @@ class ResolvedDependenciesSupportTest {
@Test @Test
fun success3() { fun success3() {
val originalText = """ val originalText = """
|0 \
|1 org.jetbrains.kotlin:kotlin-stdlib-common[1.5.0] #2[1.4.32] #3[1.5.0] #4[1.5.0] #5[1.4.32] #6[1.4.32] #7[1.4.32] #8[1.4.32] |1 org.jetbrains.kotlin:kotlin-stdlib-common[1.5.0] #2[1.4.32] #3[1.5.0] #4[1.5.0] #5[1.4.32] #6[1.4.32] #7[1.4.32] #8[1.4.32]
|${'\t'}/some/path/kotlin-stdlib-common-1.5.0.jar |${'\t'}/some/path/kotlin-stdlib-common-1.5.0.jar
|2 io.ktor:ktor-client-core,io.ktor:ktor-client-core-macosx64[1.5.4] #0[1.5.4] |2 io.ktor:ktor-client-core,io.ktor:ktor-client-core-macosx64[1.5.4] #0[1.5.4]
@@ -80,10 +84,29 @@ class ResolvedDependenciesSupportTest {
| |
""".trimMargin() """.trimMargin()
val deserializedModules = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line -> val (deserializedModules, sourceCodeModuleId) = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
fail("Unexpected failure at line $lineNo: $line") fail("Unexpected failure at line $lineNo: $line")
} }
val restoredText = ResolvedDependenciesSupport.serialize(deserializedModules) val restoredText = ResolvedDependenciesSupport.serialize(ResolvedDependencies(deserializedModules, sourceCodeModuleId))
assertEquals(originalText, restoredText)
}
@Test
fun success4() {
val originalText = """
|0 baz-native,foo,org.sample.bar
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0]
|${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native[1.0] #0[1.0]
|${'\t'}/some/path/libb.klib
|
""".trimMargin()
val (deserializedModules, sourceCodeModuleId) = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
fail("Unexpected failure at line $lineNo: $line")
}
val restoredText = ResolvedDependenciesSupport.serialize(ResolvedDependencies(deserializedModules, sourceCodeModuleId))
assertEquals(originalText, restoredText) assertEquals(originalText, restoredText)
} }
@@ -92,6 +115,7 @@ class ResolvedDependenciesSupportTest {
fun failure1() { fun failure1() {
// There is no record with number 42! // There is no record with number 42!
val originalText = """ val originalText = """
|0 \
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0] #42[42.42] |1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0] #42[42.42]
|${'\t'}/some/path/liba.klib |${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native[1.0] #0[1.0] |2 org.sample:libb,org.sample:libb-native[1.0] #0[1.0]
@@ -99,13 +123,19 @@ class ResolvedDependenciesSupportTest {
| |
""".trimMargin() """.trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { _, _ -> throw MyException() } ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(1, lineNo)
throw MyException()
}
fail()
} }
@Test(expected = MyException::class) @Test(expected = MyException::class)
fun failure2() { fun failure2() {
// Name not specified. // Name not specified.
val originalText = """ val originalText = """
|0 \
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0] |1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0]
|${'\t'}/some/path/liba.klib |${'\t'}/some/path/liba.klib
|2 [1.0] #0[1.0] |2 [1.0] #0[1.0]
@@ -113,13 +143,58 @@ class ResolvedDependenciesSupportTest {
| |
""".trimMargin() """.trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { _, _ -> throw MyException() } ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(3, lineNo)
throw MyException()
}
fail()
} }
@Test(expected = MyException::class) @Test(expected = MyException::class)
fun failure3() { fun failure3() {
// Version not specified. // Version not specified.
val originalText = """ val originalText = """
|0 \
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0]
|${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native #0[1.0]
|${'\t'}/some/path/libb.klib
|
""".trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(3, lineNo)
throw MyException()
}
fail()
}
@Test(expected = MyException::class)
fun failure4() {
// Source code module ID not specified.
val originalText = """
|0
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0]
|${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native #0[1.0]
|${'\t'}/some/path/libb.klib
|
""".trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(0, lineNo)
throw MyException()
}
fail()
}
@Test(expected = MyException::class)
fun failure5() {
// Source code module ID not specified.
val originalText = """
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0] |1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0]
|${'\t'}/some/path/liba.klib |${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native #0[1.0] |2 org.sample:libb,org.sample:libb-native #0[1.0]
@@ -127,7 +202,12 @@ class ResolvedDependenciesSupportTest {
| |
""".trimMargin() """.trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { _, _ -> throw MyException() } ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(0, lineNo)
throw MyException()
}
fail()
} }
private class MyException : Exception() private class MyException : Exception()
@@ -47,7 +47,7 @@ class ResolvedDependencyIdTest {
@Test @Test
fun toStringImplementation() { fun toStringImplementation() {
assertEquals("/", ResolvedDependencyId.SOURCE_CODE_MODULE_ID.toString()) assertEquals("/", ResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID.toString())
assertEquals("foo", ResolvedDependencyId("foo").toString()) assertEquals("foo", ResolvedDependencyId("foo").toString())
assertEquals("bar (foo)", ResolvedDependencyId("foo", "bar").toString()) assertEquals("bar (foo)", ResolvedDependencyId("foo", "bar").toString())
assertEquals("bar (baz, foo)", ResolvedDependencyId("foo", "bar", "baz").toString()) assertEquals("bar (baz, foo)", ResolvedDependencyId("foo", "bar", "baz").toString())
@@ -45,7 +45,7 @@ class ResolvedDependencyId private constructor(val uniqueNames: Set<String>) {
} }
companion object { companion object {
val SOURCE_CODE_MODULE_ID = ResolvedDependencyId("/") val DEFAULT_SOURCE_CODE_MODULE_ID = ResolvedDependencyId("/")
} }
} }
@@ -58,8 +58,8 @@ class ResolvedDependencyId private constructor(val uniqueNames: Set<String>) {
* - [requestedVersionsByIncomingDependencies] the mapping between ID of the dependee module (i.e. the module that depends on this module) * - [requestedVersionsByIncomingDependencies] the mapping between ID of the dependee module (i.e. the module that depends on this module)
* and the version (requested version) that the dependee module requires from this module. The requested version can be different * and the version (requested version) that the dependee module requires from this module. The requested version can be different
* for different dependee modules, which is absolutely legal. The [selectedVersion] is always equal to one of the requested versions * for different dependee modules, which is absolutely legal. The [selectedVersion] is always equal to one of the requested versions
* (the one that wins among others, which is typically handled inside of the build system). If the module is the top-level module, * (the one that wins among others, which is typically handled inside the build system). If the module is the top-level module,
* then it has [ResolvedDependencyId.SOURCE_CODE_MODULE_ID] in the mapping. * then it has [ResolvedDependencies.sourceCodeModuleId] in the mapping.
* - [artifactPaths] absolute paths to every artifact represented by this module * - [artifactPaths] absolute paths to every artifact represented by this module
*/ */
class ResolvedDependency( class ResolvedDependency(
@@ -75,14 +75,27 @@ class ResolvedDependency(
override fun toString() = moduleIdWithVersion override fun toString() = moduleIdWithVersion
} }
data class ResolvedDependencies(
val modules: Collection<ResolvedDependency>,
val sourceCodeModuleId: ResolvedDependencyId
) {
companion object {
val EMPTY = ResolvedDependencies(emptyList(), ResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID)
}
}
object ResolvedDependenciesSupport { object ResolvedDependenciesSupport {
fun serialize(modules: Collection<ResolvedDependency>): String { fun serialize(dependencies: ResolvedDependencies): String {
val moduleIdToIndex = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to 0).apply { val moduleIdToIndex = mutableMapOf(dependencies.sourceCodeModuleId to 0).apply {
modules.forEachIndexed { index, module -> this[module.id] = index + 1 } dependencies.modules.forEachIndexed { index, module -> this[module.id] = index + 1 }
} }
return buildString { return buildString {
modules.forEach { module -> append("0 ")
dependencies.sourceCodeModuleId.uniqueNames.joinTo(this, separator = ",")
append('\n')
dependencies.modules.forEach { module ->
val moduleIndex = moduleIdToIndex.getValue(module.id) val moduleIndex = moduleIdToIndex.getValue(module.id)
append(moduleIndex.toString()).append(' ') append(moduleIndex.toString()).append(' ')
module.id.uniqueNames.joinTo(this, separator = ",") module.id.uniqueNames.joinTo(this, separator = ",")
@@ -100,17 +113,18 @@ object ResolvedDependenciesSupport {
} }
} }
fun deserialize(source: String, onMalformedLine: (lineNo: Int, line: String) -> Unit): Collection<ResolvedDependency> { fun deserialize(source: String, onMalformedLine: (lineNo: Int, line: String) -> Unit): ResolvedDependencies {
val moduleIndexToId: MutableMap<Int, ResolvedDependencyId> = mutableMapOf(0 to ResolvedDependencyId.SOURCE_CODE_MODULE_ID) val moduleIndexToId: MutableMap<Int, ResolvedDependencyId> = mutableMapOf()
val requestedVersionsByIncomingDependenciesIndices: MutableMap<ResolvedDependencyId, Map<Int, ResolvedDependencyVersion>> = val requestedVersionsByIncomingDependenciesIndices: MutableMap<ResolvedDependencyId, Map<Int, ResolvedDependencyVersion>> =
mutableMapOf() mutableMapOf()
var sourceCodeModuleId: ResolvedDependencyId? = null
val modules = mutableListOf<ResolvedDependency>() val modules = mutableListOf<ResolvedDependency>()
source.lines().forEachIndexed { lineNo, line -> source.lines().forEachIndexed { lineNo, line ->
fun malformedLine(): Collection<ResolvedDependency> { fun malformedLine(): ResolvedDependencies {
onMalformedLine(lineNo, line) onMalformedLine(lineNo, line)
return emptyList() return ResolvedDependencies.EMPTY
} }
when { when {
@@ -123,7 +137,13 @@ object ResolvedDependenciesSupport {
else else
return malformedLine() return malformedLine()
} }
line[0] == '0' -> {
val result = SOURCE_CODE_MODULE_REGEX.matchEntire(line) ?: return malformedLine()
sourceCodeModuleId = ResolvedDependencyId(result.groupValues[1].split(','))
}
else -> { else -> {
if (sourceCodeModuleId == null) return malformedLine()
val result = DEPENDENCY_MODULE_REGEX.matchEntire(line) ?: return malformedLine() val result = DEPENDENCY_MODULE_REGEX.matchEntire(line) ?: return malformedLine()
val moduleIndex = result.groupValues[1].toInt() val moduleIndex = result.groupValues[1].toInt()
val moduleId = ResolvedDependencyId(result.groupValues[2].split(',')) val moduleId = ResolvedDependencyId(result.groupValues[2].split(','))
@@ -156,14 +176,18 @@ object ResolvedDependenciesSupport {
// Stamp incoming dependencies & requested versions. // Stamp incoming dependencies & requested versions.
modules.forEach { module -> modules.forEach { module ->
requestedVersionsByIncomingDependenciesIndices[module.id]?.forEach { (incomingDependencyIndex, requestedVersion) -> requestedVersionsByIncomingDependenciesIndices[module.id]?.forEach { (incomingDependencyIndex, requestedVersion) ->
val incomingDependencyId = moduleIndexToId.getValue(incomingDependencyIndex) val incomingDependencyId = if (incomingDependencyIndex == 0)
sourceCodeModuleId!!
else
moduleIndexToId.getValue(incomingDependencyIndex)
module.requestedVersionsByIncomingDependencies[incomingDependencyId] = requestedVersion module.requestedVersionsByIncomingDependencies[incomingDependencyId] = requestedVersion
} }
} }
return modules return ResolvedDependencies(modules, sourceCodeModuleId!!)
} }
private val SOURCE_CODE_MODULE_REGEX = Regex("^0 ([^\\[]+)$")
private val DEPENDENCY_MODULE_REGEX = Regex("^(\\d+) ([^\\[]+)\\[([^]]+)](.*)?$") private val DEPENDENCY_MODULE_REGEX = Regex("^(\\d+) ([^\\[]+)\\[([^]]+)](.*)?$")
private val REQUESTED_VERSION_BY_INCOMING_DEPENDENCY_REGEX = Regex("^#(\\d+)\\[(.*)]$") private val REQUESTED_VERSION_BY_INCOMING_DEPENDENCY_REGEX = Regex("^#(\\d+)\\[(.*)]$")
} }
@@ -26,7 +26,10 @@ class KonanUserVisibleIrModulesSupport(
return compressedModules(deserializers) return compressedModules(deserializers)
} }
override fun modulesFromDeserializers(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> { override fun modulesFromDeserializers(
deserializers: Collection<IrModuleDeserializer>,
excludedModuleIds: Set<ResolvedDependencyId>
): Map<ResolvedDependencyId, ResolvedDependency> {
val moduleToCompilerVersion: MutableMap<ResolvedDependencyId, ResolvedDependencyVersion> = mutableMapOf() val moduleToCompilerVersion: MutableMap<ResolvedDependencyId, ResolvedDependencyVersion> = mutableMapOf()
val kotlinNativeBundledLibraries: MutableSet<ResolvedDependencyId> = mutableSetOf() val kotlinNativeBundledLibraries: MutableSet<ResolvedDependencyId> = mutableSetOf()
@@ -34,6 +37,9 @@ class KonanUserVisibleIrModulesSupport(
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.mapNotNull { deserializer -> val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.mapNotNull { deserializer ->
val library: KotlinLibrary = deserializer.kotlinLibrary ?: return@mapNotNull null val library: KotlinLibrary = deserializer.kotlinLibrary ?: return@mapNotNull null
val moduleId = getUserVisibleModuleId(deserializer)
if (moduleId in excludedModuleIds) return@mapNotNull null
val compilerVersion: ResolvedDependencyVersion = library.compilerVersion val compilerVersion: ResolvedDependencyVersion = library.compilerVersion
val isDefaultLibrary = library.isDefaultLibrary val isDefaultLibrary = library.isDefaultLibrary
@@ -41,7 +47,6 @@ class KonanUserVisibleIrModulesSupport(
// Note: Empty string means missing (unknown) version. // Note: Empty string means missing (unknown) version.
val libraryVersion: ResolvedDependencyVersion = if (isDefaultLibrary) compilerVersion else ResolvedDependencyVersion.EMPTY val libraryVersion: ResolvedDependencyVersion = if (isDefaultLibrary) compilerVersion else ResolvedDependencyVersion.EMPTY
val moduleId = getUserVisibleModuleId(deserializer)
val module = ResolvedDependency( val module = ResolvedDependency(
id = moduleId, id = moduleId,
selectedVersion = libraryVersion, selectedVersion = libraryVersion,
@@ -54,7 +59,9 @@ class KonanUserVisibleIrModulesSupport(
// Don't rely on dependencies in IrModuleDeserializer. In Kotlin/Native each module depends on all other modules, // 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. // and this contradicts with the real module dependencies as written in KLIB manifest files.
val outgoingDependencyIds = library.unresolvedDependencies.map { it.moduleId } val outgoingDependencyIds = library.unresolvedDependencies.mapNotNull { unresolvedDependency ->
unresolvedDependency.moduleId.takeIf { it !in excludedModuleIds }
}
moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds) moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds)
}.toMap() }.toMap()
@@ -87,7 +94,7 @@ class KonanUserVisibleIrModulesSupport(
for ((moduleId, module) in compressedModules) { for ((moduleId, module) in compressedModules) {
if (moduleId.isKonanPlatformLibrary) { if (moduleId.isKonanPlatformLibrary) {
if (ResolvedDependencyId.SOURCE_CODE_MODULE_ID !in module.requestedVersionsByIncomingDependencies) { if (sourceCodeModuleId !in module.requestedVersionsByIncomingDependencies) {
continue continue
} }
@@ -116,7 +123,7 @@ class KonanUserVisibleIrModulesSupport(
val compressedModule = ResolvedDependency( val compressedModule = ResolvedDependency(
id = compressedModuleId, id = compressedModuleId,
selectedVersion = platformLibrariesVersion!!, selectedVersion = platformLibrariesVersion!!,
requestedVersionsByIncomingDependencies = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to platformLibrariesVersion), requestedVersionsByIncomingDependencies = mutableMapOf(sourceCodeModuleId to platformLibrariesVersion),
artifactPaths = mutableSetOf() artifactPaths = mutableSetOf()
) )
@@ -31,6 +31,7 @@ class NativeExternalDependenciesIT : BaseGradleIT() {
assertEquals( assertEquals(
""" """
|0 native-external-dependencies
|1 io.ktor:ktor-io,io.ktor:ktor-io-$MASKED_TARGET_NAME[1.5.4] #0[1.5.4] |1 io.ktor:ktor-io,io.ktor:ktor-io-$MASKED_TARGET_NAME[1.5.4] #0[1.5.4]
|${'\t'}/some/path/ktor-io.klib |${'\t'}/some/path/ktor-io.klib
|${'\t'}/some/path/ktor-io-cinterop-bits.klib |${'\t'}/some/path/ktor-io-cinterop-bits.klib
@@ -55,6 +56,7 @@ class NativeExternalDependenciesIT : BaseGradleIT() {
assertEquals( assertEquals(
""" """
|0 native-external-dependencies
|1 io.ktor:ktor-io,io.ktor:ktor-io-$MASKED_TARGET_NAME[1.5.4] #0[1.5.4] |1 io.ktor:ktor-io,io.ktor:ktor-io-$MASKED_TARGET_NAME[1.5.4] #0[1.5.4]
|${'\t'}/some/path/ktor-io.klib |${'\t'}/some/path/ktor-io.klib
|${'\t'}/some/path/ktor-io-cinterop-bits.klib |${'\t'}/some/path/ktor-io-cinterop-bits.klib
@@ -10,8 +10,9 @@ import org.jetbrains.kotlin.gradle.GradleVersionRequired
import org.jetbrains.kotlin.gradle.native.NativeExternalDependenciesIT.Companion.MASKED_TARGET_NAME import org.jetbrains.kotlin.gradle.native.NativeExternalDependenciesIT.Companion.MASKED_TARGET_NAME
import org.jetbrains.kotlin.gradle.native.NativeExternalDependenciesIT.Companion.findKotlinNativeTargetName import org.jetbrains.kotlin.gradle.native.NativeExternalDependenciesIT.Companion.findKotlinNativeTargetName
import org.jetbrains.kotlin.gradle.native.NativeExternalDependenciesIT.Companion.findParameterInOutput import org.jetbrains.kotlin.gradle.native.NativeExternalDependenciesIT.Companion.findParameterInOutput
import org.jetbrains.kotlin.konan.library.KONAN_PLATFORM_LIBS_NAME_PREFIX
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.Assume import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue import org.junit.Assume.assumeTrue
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
@@ -23,84 +24,16 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.FOR_MPP_SUPPORT get() = GradleVersionRequired.FOR_MPP_SUPPORT
@Test
fun `declaration that is gone (KT-41378)`() {
val repo = setupLocalRepo()
buildAndPublishLibrary("native-ir-linker-issues-gone-declaration", "liba-v1.0", repo)
buildAndPublishLibrary("native-ir-linker-issues-gone-declaration", "liba-v2.0", repo)
buildAndPublishLibrary("native-ir-linker-issues-gone-declaration", "libb", repo)
buildApplicationAndFail(
directory = "native-ir-linker-issues-gone-declaration",
projectName = "app",
localRepo = repo
) { kotlinNativeCompilerVersion ->
"""
|e: Module "org.sample:libb (org.sample:libb-native)" has a reference to symbol sample.liba/C|null[0]. Neither the module itself nor its dependencies contain such declaration.
|
|This could happen if the required dependency is missing in the project. Or if there is a dependency of "org.sample:libb (org.sample:libb-native)" that has a different version in the project than the version that "org.sample:libb (org.sample:libb-native): 1.0" was initially compiled with. Please check that the project configuration is correct and has consistent versions of all required dependencies.
|
|The list of "org.sample:libb (org.sample:libb-native): 1.0" dependencies that may lead to conflicts:
|1. "org.sample:liba (org.sample:liba-native): 2.0" (was initially compiled with "org.sample:liba (org.sample:liba-native): 1.0")
|
|Project dependencies:
|├─── org.sample:liba (org.sample:liba-native): 2.0
|│ └─── stdlib: $kotlinNativeCompilerVersion
|└─── org.sample:libb (org.sample:libb-native): 1.0
| ^^^ This module requires symbol sample.liba/C|null[0].
| ├─── org.sample:liba (org.sample:liba-native): 1.0 -> 2.0 (*)
| └─── stdlib: $kotlinNativeCompilerVersion
|
|(*) - dependencies omitted (listed previously)
""".trimMargin()
}
}
@Test
fun `symbol type mismatch (KT-47285)`() {
val repo = setupLocalRepo()
buildAndPublishLibrary("native-ir-linker-issues-symbol-mismatch", "liba-v1.0", repo)
buildAndPublishLibrary("native-ir-linker-issues-symbol-mismatch", "liba-v2.0", repo)
buildAndPublishLibrary("native-ir-linker-issues-symbol-mismatch", "libb", repo)
buildApplicationAndFail(
directory = "native-ir-linker-issues-symbol-mismatch",
projectName = "app",
localRepo = repo
) { kotlinNativeCompilerVersion ->
"""
|e: The symbol of unexpected type encountered during IR deserialization: IrClassPublicSymbolImpl, sample.liba/B|null[0]. IrTypeAliasSymbol is expected.
|
|This could happen if there are two libraries, where one library was compiled against the different version of the other library than the one currently used in the project. Please check that the project configuration is correct and has consistent versions of dependencies.
|
|The list of libraries that depend on "org.sample:liba (org.sample:liba-native)" and may lead to conflicts:
|1. "org.sample:libb (org.sample:libb-native): 1.0" (was compiled against "org.sample:liba (org.sample:liba-native): 1.0" but "org.sample:liba (org.sample:liba-native): 2.0" is used in the project)
|
|Project dependencies:
|├─── org.sample:liba (org.sample:liba-native): 2.0
|│ ^^^ This module contains symbol sample.liba/B|null[0] that is the cause of the conflict.
|│ └─── stdlib: $kotlinNativeCompilerVersion
|└─── org.sample:libb (org.sample:libb-native): 1.0
| ├─── org.sample:liba (org.sample:liba-native): 1.0 -> 2.0 (*)
| │ ^^^ This module contains symbol sample.liba/B|null[0] that is the cause of the conflict.
| └─── stdlib: $kotlinNativeCompilerVersion
|
|(*) - dependencies omitted (listed previously)
""".trimMargin()
}
}
@Test @Test
fun `ktor 1_5_4 and coroutines 1_5_0-RC-native-mt (KT-46697)`() { fun `ktor 1_5_4 and coroutines 1_5_0-RC-native-mt (KT-46697)`() {
// Run this test only on macOS. // Run this test only on macOS.
assumeTrue(HostManager.hostIsMac) assumeTrue(HostManager.hostIsMac)
buildApplicationAndFail( buildApplicationAndFail(
directory = null, directoryPrefix = null,
projectName = "native-ir-linker-issues-ktor-and-coroutines", projectName = "native-ir-linker-issues-ktor-and-coroutines",
localRepo = null localRepo = null,
useCache = true
) { kotlinNativeCompilerVersion -> ) { kotlinNativeCompilerVersion ->
""" """
|e: The symbol of unexpected type encountered during IR deserialization: IrClassPublicSymbolImpl, kotlinx.coroutines/CancellationException|null[0]. IrTypeAliasSymbol is expected. |e: The symbol of unexpected type encountered during IR deserialization: IrClassPublicSymbolImpl, kotlinx.coroutines/CancellationException|null[0]. IrTypeAliasSymbol is expected.
@@ -173,13 +106,154 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
} }
} }
private fun buildApplicationAndFail( @Test
directory: String?, fun `declaration that is gone (KT-41378) - with cache`() {
projectName: String, // Don't run it on Windows. Caches are not supported there yet.
localRepo: File?, assumeFalse(HostManager.hostIsMingw)
buildConflictingLibrariesAndApplication(
directoryPrefix = "native-ir-linker-issues-gone-declaration",
useCache = true
) { kotlinNativeCompilerVersion ->
"""
|e: Module "org.sample:libb (org.sample:libb-native)" has a reference to symbol sample.liba/C|null[0]. Neither the module itself nor its dependencies contain such declaration.
|
|This could happen if the required dependency is missing in the project. Or if there is a dependency of "org.sample:libb (org.sample:libb-native)" that has a different version in the project than the version that "org.sample:libb (org.sample:libb-native): 1.0" was initially compiled with. Please check that the project configuration is correct and has consistent versions of all required dependencies.
|
|The list of "org.sample:libb (org.sample:libb-native): 1.0" dependencies that may lead to conflicts:
|1. "org.sample:liba (org.sample:liba-native): 2.0" (was initially compiled with "org.sample:liba (org.sample:liba-native): 1.0")
|
|Project dependencies:
|├─── org.sample:liba (org.sample:liba-native): 2.0
|│ └─── stdlib: $kotlinNativeCompilerVersion
|└─── org.sample:libb (org.sample:libb-native): 1.0
| ^^^ This module requires symbol sample.liba/C|null[0].
| ├─── org.sample:liba (org.sample:liba-native): 1.0 -> 2.0 (*)
| └─── stdlib: $kotlinNativeCompilerVersion
|
|(*) - dependencies omitted (listed previously)
""".trimMargin()
}
}
@Test
fun `declaration that is gone (KT-41378) - without cache`() {
buildConflictingLibrariesAndApplication(
directoryPrefix = "native-ir-linker-issues-gone-declaration",
useCache = false
) { kotlinNativeCompilerVersion ->
"""
|e: Module "org.sample:libb (org.sample:libb-native)" has a reference to symbol sample.liba/C|null[0]. Neither the module itself nor its dependencies contain such declaration.
|
|This could happen if the required dependency is missing in the project. Or if there is a dependency of "org.sample:libb (org.sample:libb-native)" that has a different version in the project than the version that "org.sample:libb (org.sample:libb-native): 1.0" was initially compiled with. Please check that the project configuration is correct and has consistent versions of all required dependencies.
|
|The list of "org.sample:libb (org.sample:libb-native): 1.0" dependencies that may lead to conflicts:
|1. "org.sample:liba (org.sample:liba-native): 2.0" (was initially compiled with "org.sample:liba (org.sample:liba-native): 1.0")
|
|Project dependencies:
|├─── org.sample:liba (org.sample:liba-native): 2.0
|│ └─── stdlib: $kotlinNativeCompilerVersion
|├─── org.sample:libb (org.sample:libb-native): 1.0
|│ ^^^ This module requires symbol sample.liba/C|null[0].
|│ ├─── org.sample:liba (org.sample:liba-native): 1.0 -> 2.0 (*)
|│ └─── stdlib: $kotlinNativeCompilerVersion
|└─── org.jetbrains.kotlin.native.platform.* (NNN libraries): $kotlinNativeCompilerVersion
| └─── stdlib: $kotlinNativeCompilerVersion
|
|(*) - dependencies omitted (listed previously)
""".trimMargin()
}
}
@Test
fun `symbol type mismatch (KT-47285) - with cache`() {
// Don't run it on Windows. Caches are not supported there yet.
assumeFalse(HostManager.hostIsMingw)
buildConflictingLibrariesAndApplication(
directoryPrefix = "native-ir-linker-issues-symbol-mismatch",
useCache = true
) { kotlinNativeCompilerVersion ->
"""
|e: The symbol of unexpected type encountered during IR deserialization: IrClassPublicSymbolImpl, sample.liba/B|null[0]. IrTypeAliasSymbol is expected.
|
|This could happen if there are two libraries, where one library was compiled against the different version of the other library than the one currently used in the project. Please check that the project configuration is correct and has consistent versions of dependencies.
|
|The list of libraries that depend on "org.sample:liba (org.sample:liba-native)" and may lead to conflicts:
|1. "org.sample:libb (org.sample:libb-native): 1.0" (was compiled against "org.sample:liba (org.sample:liba-native): 1.0" but "org.sample:liba (org.sample:liba-native): 2.0" is used in the project)
|
|Project dependencies:
|├─── org.sample:liba (org.sample:liba-native): 2.0
|│ ^^^ This module contains symbol sample.liba/B|null[0] that is the cause of the conflict.
|│ └─── stdlib: $kotlinNativeCompilerVersion
|└─── org.sample:libb (org.sample:libb-native): 1.0
| ├─── org.sample:liba (org.sample:liba-native): 1.0 -> 2.0 (*)
| │ ^^^ This module contains symbol sample.liba/B|null[0] that is the cause of the conflict.
| └─── stdlib: $kotlinNativeCompilerVersion
|
|(*) - dependencies omitted (listed previously)
""".trimMargin()
}
}
@Test
fun `symbol type mismatch (KT-47285) - without cache`() {
buildConflictingLibrariesAndApplication(
directoryPrefix = "native-ir-linker-issues-symbol-mismatch",
useCache = false
) { kotlinNativeCompilerVersion ->
"""
|e: The symbol of unexpected type encountered during IR deserialization: IrTypeAliasPublicSymbolImpl, sample.liba/B|null[0]. IrClassifierSymbol is expected.
|
|This could happen if there are two libraries, where one library was compiled against the different version of the other library than the one currently used in the project. Please check that the project configuration is correct and has consistent versions of dependencies.
|
|The list of libraries that depend on "org.sample:liba (org.sample:liba-native)" and may lead to conflicts:
|1. "org.sample:libb (org.sample:libb-native): 1.0" (was compiled against "org.sample:liba (org.sample:liba-native): 1.0" but "org.sample:liba (org.sample:liba-native): 2.0" is used in the project)
|
|Project dependencies:
|├─── org.sample:liba (org.sample:liba-native): 2.0
|│ ^^^ This module contains symbol sample.liba/B|null[0] that is the cause of the conflict.
|│ └─── stdlib: $kotlinNativeCompilerVersion
|├─── org.sample:libb (org.sample:libb-native): 1.0
|│ ├─── org.sample:liba (org.sample:liba-native): 1.0 -> 2.0 (*)
|│ │ ^^^ This module contains symbol sample.liba/B|null[0] that is the cause of the conflict.
|│ └─── stdlib: $kotlinNativeCompilerVersion
|└─── org.jetbrains.kotlin.native.platform.* (NNN libraries): $kotlinNativeCompilerVersion
| └─── stdlib: $kotlinNativeCompilerVersion
|
|(*) - dependencies omitted (listed previously)
""".trimMargin()
}
}
private fun buildConflictingLibrariesAndApplication(
directoryPrefix: String,
useCache: Boolean,
expectedErrorMessage: (compilerVersion: String) -> String expectedErrorMessage: (compilerVersion: String) -> String
) { ) {
prepareProject(directory, projectName, localRepo) { val repo = setupLocalRepo()
buildAndPublishLibrary(directoryPrefix = directoryPrefix, projectName = "liba-v1.0", localRepo = repo)
buildAndPublishLibrary(directoryPrefix = directoryPrefix, projectName = "liba-v2.0", localRepo = repo)
buildAndPublishLibrary(directoryPrefix = directoryPrefix, projectName = "libb", localRepo = repo)
buildApplicationAndFail(
directoryPrefix = directoryPrefix,
projectName = "app",
localRepo = repo,
useCache = useCache,
expectedErrorMessage = expectedErrorMessage
)
}
private fun buildApplicationAndFail(
directoryPrefix: String?,
projectName: String,
localRepo: File?,
useCache: Boolean,
expectedErrorMessage: (compilerVersion: String) -> String
) {
prepareProject(directoryPrefix, projectName, localRepo, useCache) {
build("linkDebugExecutableNative") { build("linkDebugExecutableNative") {
assertFailed() assertFailed()
@@ -192,6 +266,16 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
val errorMessage = ERROR_LINE_REGEX.findAll(getOutputForTask("linkDebugExecutableNative")) val errorMessage = ERROR_LINE_REGEX.findAll(getOutputForTask("linkDebugExecutableNative"))
.map { matchResult -> matchResult.groupValues[1] } .map { matchResult -> matchResult.groupValues[1] }
.map { line -> line.replace(kotlinNativeTargetName, MASKED_TARGET_NAME) } .map { line -> line.replace(kotlinNativeTargetName, MASKED_TARGET_NAME) }
.map { line ->
line.replace(COMPRESSED_PLATFORM_LIBS_REGEX) { result ->
val rangeWithPlatformLibrariesCount = result.groups[1]!!.range
buildString {
append(line.substring(0, rangeWithPlatformLibrariesCount.first))
append("NNN")
append(line.substring(rangeWithPlatformLibrariesCount.last + 1))
}
}
}
.joinToString("\n") .joinToString("\n")
assertEquals(expectedErrorMessage(kotlinNativeCompilerVersion), errorMessage) assertEquals(expectedErrorMessage(kotlinNativeCompilerVersion), errorMessage)
@@ -199,8 +283,8 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
} }
} }
private fun buildAndPublishLibrary(directory: String, projectName: String, localRepo: File) { private fun buildAndPublishLibrary(directoryPrefix: String, projectName: String, localRepo: File) {
prepareProject(directory, projectName, localRepo) { prepareProject(directoryPrefix, projectName, localRepo, useCache = true) {
build("publish") { build("publish") {
assertSuccessful() assertSuccessful()
} }
@@ -208,12 +292,13 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
} }
private fun prepareProject( private fun prepareProject(
directory: String?, directoryPrefix: String?,
projectName: String, projectName: String,
localRepo: File?, localRepo: File?,
useCache: Boolean,
block: Project.() -> Unit block: Project.() -> Unit
) { ) {
with(transformNativeTestProjectWithPluginDsl(directoryPrefix = directory, projectName = projectName)) { with(transformNativeTestProjectWithPluginDsl(directoryPrefix = directoryPrefix, projectName = projectName)) {
if (localRepo != null) { if (localRepo != null) {
val localRepoUri = localRepo.absoluteFile.toURI().toString() val localRepoUri = localRepo.absoluteFile.toURI().toString()
gradleBuildScript().apply { gradleBuildScript().apply {
@@ -221,12 +306,16 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
} }
} }
gradleProperties().appendText("\nkotlin.native.cacheKind=${if (useCache) "static" else "none"}\n")
block() block()
} }
} }
private companion object { private companion object {
private val ERROR_LINE_REGEX = "(?m)^.*\\[ERROR] \\[\\S+] (.*)$".toRegex() private val ERROR_LINE_REGEX = "(?m)^.*\\[ERROR] \\[\\S+] (.*)$".toRegex()
private val COMPRESSED_PLATFORM_LIBS_REGEX =
".*${KONAN_PLATFORM_LIBS_NAME_PREFIX.replace(".", "\\.")}\\* \\((\\d+) libraries\\).*".toRegex()
private fun setupLocalRepo(): File = Files.createTempDirectory("localRepo").toAbsolutePath().toFile() private fun setupLocalRepo(): File = Files.createTempDirectory("localRepo").toAbsolutePath().toFile()
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCommonToolOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName import org.jetbrains.kotlin.gradle.plugin.cocoapods.asValidFrameworkName
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinNativeCompilationData import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinNativeCompilationData
@@ -49,6 +50,7 @@ import org.jetbrains.kotlin.library.*
import org.jetbrains.kotlin.project.model.LanguageSettings import org.jetbrains.kotlin.project.model.LanguageSettings
import org.jetbrains.kotlin.utils.ResolvedDependency as KResolvedDependency import org.jetbrains.kotlin.utils.ResolvedDependency as KResolvedDependency
import org.jetbrains.kotlin.utils.ResolvedDependencyId as KResolvedDependencyId import org.jetbrains.kotlin.utils.ResolvedDependencyId as KResolvedDependencyId
import org.jetbrains.kotlin.utils.ResolvedDependencies as KResolvedDependencies
import org.jetbrains.kotlin.utils.ResolvedDependenciesSupport as KResolvedDependenciesSupport import org.jetbrains.kotlin.utils.ResolvedDependenciesSupport as KResolvedDependenciesSupport
import org.jetbrains.kotlin.utils.ResolvedDependencyArtifactPath as KResolvedDependencyArtifactPath import org.jetbrains.kotlin.utils.ResolvedDependencyArtifactPath as KResolvedDependencyArtifactPath
import org.jetbrains.kotlin.utils.ResolvedDependencyVersion as KResolvedDependencyVersion import org.jetbrains.kotlin.utils.ResolvedDependencyVersion as KResolvedDependencyVersion
@@ -610,7 +612,7 @@ constructor(
override fun buildCompilerArgs(): List<String> = mutableListOf<String>().apply { override fun buildCompilerArgs(): List<String> = mutableListOf<String>().apply {
addAll(super.buildCompilerArgs()) addAll(super.buildCompilerArgs())
val externalDependenciesArgs = ExternalDependenciesBuilder(project, binary).buildCompilerArgs() val externalDependenciesArgs = ExternalDependenciesBuilder(project, compilation).buildCompilerArgs()
addAll(externalDependenciesArgs) addAll(externalDependenciesArgs)
addAll(CacheBuilder(project, binary, konanTarget, externalDependenciesArgs).buildCompilerArgs()) addAll(CacheBuilder(project, binary, konanTarget, externalDependenciesArgs).buildCompilerArgs())
@@ -689,13 +691,21 @@ constructor(
} }
} }
private class ExternalDependenciesBuilder(val project: Project, val binary: NativeBinary) { private class ExternalDependenciesBuilder(
private val compilation: KotlinNativeCompilation val project: Project,
get() = binary.compilation val compilation: KotlinCompilation<*>,
intermediateLibraryName: String?
) {
constructor(project: Project, compilation: KotlinNativeCompilation) : this(
project, compilation, compilation.compileKotlinTask.moduleName
)
private val compileDependencyConfiguration: Configuration private val compileDependencyConfiguration: Configuration
get() = project.configurations.getByName(compilation.compileDependencyConfigurationName) get() = project.configurations.getByName(compilation.compileDependencyConfigurationName)
private val sourceCodeModuleId: KResolvedDependencyId =
intermediateLibraryName?.let { KResolvedDependencyId(it) } ?: KResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID
fun buildCompilerArgs(): List<String> { fun buildCompilerArgs(): List<String> {
val konanVersion = Distribution(project.konanHome).compilerVersion?.let(CompilerVersion.Companion::fromString) val konanVersion = Distribution(project.konanHome).compilerVersion?.let(CompilerVersion.Companion::fromString)
?: project.konanVersion ?: project.konanVersion
@@ -771,7 +781,7 @@ private class ExternalDependenciesBuilder(val project: Project, val binary: Nati
} }
compileDependencyConfiguration.incoming.resolutionResult.root.dependencies.forEach { dependencyResult -> compileDependencyConfiguration.incoming.resolutionResult.root.dependencies.forEach { dependencyResult ->
processModule(dependencyResult, incomingDependencyId = KResolvedDependencyId.SOURCE_CODE_MODULE_ID) processModule(dependencyResult, incomingDependencyId = sourceCodeModuleId)
} }
if (moduleIdsToMerge.isEmpty()) if (moduleIdsToMerge.isEmpty())
@@ -821,6 +831,15 @@ private class ExternalDependenciesBuilder(val project: Project, val binary: Nati
return mergedModules.values return mergedModules.values
} }
private fun writeDependenciesFile(dependencies: Collection<KResolvedDependency>, deleteOnExit: Boolean): File? {
if (dependencies.isEmpty()) return null
val dependenciesFile = Files.createTempFile("kotlin-native-external-dependencies", ".deps").toAbsolutePath().toFile()
if (deleteOnExit) dependenciesFile.deleteOnExit()
dependenciesFile.writeText(KResolvedDependenciesSupport.serialize(KResolvedDependencies(dependencies, sourceCodeModuleId)))
return dependenciesFile
}
private val ModuleComponentIdentifier.uniqueName: String private val ModuleComponentIdentifier.uniqueName: String
get() = "$group:$module" get() = "$group:$module"
@@ -828,27 +847,18 @@ private class ExternalDependenciesBuilder(val project: Project, val binary: Nati
@Suppress("unused") // Used for tests only. Accessed via reflection. @Suppress("unused") // Used for tests only. Accessed via reflection.
@JvmStatic @JvmStatic
fun buildExternalDependenciesFileForTests(project: Project): File? { fun buildExternalDependenciesFileForTests(project: Project): File? {
val executable = project.tasks.asSequence() val compilation = project.tasks.asSequence()
.filterIsInstance<KotlinNativeLink>() .filterIsInstance<KotlinNativeLink>()
.map { it.binary } .map { it.binary }
.filterIsInstance<Executable>() // Not TestExecutable or any other kind of NativeBinary. Strictly Executable! .filterIsInstance<Executable>() // Not TestExecutable or any other kind of NativeBinary. Strictly Executable!
.firstOrNull() .firstOrNull()
?.compilation
?: return null ?: return null
val dependencies = ExternalDependenciesBuilder(project, executable) return with(ExternalDependenciesBuilder(project, compilation)) {
.buildDependencies() val dependencies = buildDependencies().sortedBy { it.id.toString() }
.sortedBy { it.id.toString() } writeDependenciesFile(dependencies, deleteOnExit = false)
}
return writeDependenciesFile(dependencies, deleteOnExit = false)
}
private fun writeDependenciesFile(dependencies: Collection<KResolvedDependency>, deleteOnExit: Boolean): File? {
if (dependencies.isEmpty()) return null
val dependenciesFile = Files.createTempFile("kotlin-native-external-dependencies", ".deps").toAbsolutePath().toFile()
if (deleteOnExit) dependenciesFile.deleteOnExit()
dependenciesFile.writeText(KResolvedDependenciesSupport.serialize(dependencies))
return dependenciesFile
} }
} }
} }