[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
@@ -31,6 +31,7 @@ class NativeExternalDependenciesIT : BaseGradleIT() {
assertEquals(
"""
|0 native-external-dependencies
|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-cinterop-bits.klib
@@ -55,6 +56,7 @@ class NativeExternalDependenciesIT : BaseGradleIT() {
assertEquals(
"""
|0 native-external-dependencies
|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-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.findKotlinNativeTargetName
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.junit.Assume
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Test
import java.io.File
@@ -23,84 +24,16 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired
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
fun `ktor 1_5_4 and coroutines 1_5_0-RC-native-mt (KT-46697)`() {
// Run this test only on macOS.
assumeTrue(HostManager.hostIsMac)
buildApplicationAndFail(
directory = null,
directoryPrefix = null,
projectName = "native-ir-linker-issues-ktor-and-coroutines",
localRepo = null
localRepo = null,
useCache = true
) { kotlinNativeCompilerVersion ->
"""
|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(
directory: String?,
projectName: String,
localRepo: File?,
@Test
fun `declaration that is gone (KT-41378) - with cache`() {
// Don't run it on Windows. Caches are not supported there yet.
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
) {
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") {
assertFailed()
@@ -192,6 +266,16 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
val errorMessage = ERROR_LINE_REGEX.findAll(getOutputForTask("linkDebugExecutableNative"))
.map { matchResult -> matchResult.groupValues[1] }
.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")
assertEquals(expectedErrorMessage(kotlinNativeCompilerVersion), errorMessage)
@@ -199,8 +283,8 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
}
}
private fun buildAndPublishLibrary(directory: String, projectName: String, localRepo: File) {
prepareProject(directory, projectName, localRepo) {
private fun buildAndPublishLibrary(directoryPrefix: String, projectName: String, localRepo: File) {
prepareProject(directoryPrefix, projectName, localRepo, useCache = true) {
build("publish") {
assertSuccessful()
}
@@ -208,12 +292,13 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
}
private fun prepareProject(
directory: String?,
directoryPrefix: String?,
projectName: String,
localRepo: File?,
useCache: Boolean,
block: Project.() -> Unit
) {
with(transformNativeTestProjectWithPluginDsl(directoryPrefix = directory, projectName = projectName)) {
with(transformNativeTestProjectWithPluginDsl(directoryPrefix = directoryPrefix, projectName = projectName)) {
if (localRepo != null) {
val localRepoUri = localRepo.absoluteFile.toURI().toString()
gradleBuildScript().apply {
@@ -221,12 +306,16 @@ class NativeIrLinkerIssuesIT : BaseGradleIT() {
}
}
gradleProperties().appendText("\nkotlin.native.cacheKind=${if (useCache) "static" else "none"}\n")
block()
}
}
private companion object {
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()
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCommonToolOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind
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.mpp.*
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.utils.ResolvedDependency as KResolvedDependency
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.ResolvedDependencyArtifactPath as KResolvedDependencyArtifactPath
import org.jetbrains.kotlin.utils.ResolvedDependencyVersion as KResolvedDependencyVersion
@@ -610,7 +612,7 @@ constructor(
override fun buildCompilerArgs(): List<String> = mutableListOf<String>().apply {
addAll(super.buildCompilerArgs())
val externalDependenciesArgs = ExternalDependenciesBuilder(project, binary).buildCompilerArgs()
val externalDependenciesArgs = ExternalDependenciesBuilder(project, compilation).buildCompilerArgs()
addAll(externalDependenciesArgs)
addAll(CacheBuilder(project, binary, konanTarget, externalDependenciesArgs).buildCompilerArgs())
@@ -689,13 +691,21 @@ constructor(
}
}
private class ExternalDependenciesBuilder(val project: Project, val binary: NativeBinary) {
private val compilation: KotlinNativeCompilation
get() = binary.compilation
private class ExternalDependenciesBuilder(
val project: Project,
val compilation: KotlinCompilation<*>,
intermediateLibraryName: String?
) {
constructor(project: Project, compilation: KotlinNativeCompilation) : this(
project, compilation, compilation.compileKotlinTask.moduleName
)
private val compileDependencyConfiguration: Configuration
get() = project.configurations.getByName(compilation.compileDependencyConfigurationName)
private val sourceCodeModuleId: KResolvedDependencyId =
intermediateLibraryName?.let { KResolvedDependencyId(it) } ?: KResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID
fun buildCompilerArgs(): List<String> {
val konanVersion = Distribution(project.konanHome).compilerVersion?.let(CompilerVersion.Companion::fromString)
?: project.konanVersion
@@ -771,7 +781,7 @@ private class ExternalDependenciesBuilder(val project: Project, val binary: Nati
}
compileDependencyConfiguration.incoming.resolutionResult.root.dependencies.forEach { dependencyResult ->
processModule(dependencyResult, incomingDependencyId = KResolvedDependencyId.SOURCE_CODE_MODULE_ID)
processModule(dependencyResult, incomingDependencyId = sourceCodeModuleId)
}
if (moduleIdsToMerge.isEmpty())
@@ -821,6 +831,15 @@ private class ExternalDependenciesBuilder(val project: Project, val binary: Nati
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
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.
@JvmStatic
fun buildExternalDependenciesFileForTests(project: Project): File? {
val executable = project.tasks.asSequence()
val compilation = project.tasks.asSequence()
.filterIsInstance<KotlinNativeLink>()
.map { it.binary }
.filterIsInstance<Executable>() // Not TestExecutable or any other kind of NativeBinary. Strictly Executable!
.firstOrNull()
?.compilation
?: return null
val dependencies = ExternalDependenciesBuilder(project, executable)
.buildDependencies()
.sortedBy { it.id.toString() }
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
return with(ExternalDependenciesBuilder(project, compilation)) {
val dependencies = buildDependencies().sortedBy { it.id.toString() }
writeDependenciesFile(dependencies, deleteOnExit = false)
}
}
}
}