[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

View File

@@ -67,6 +67,7 @@ class SignatureIdNotFoundInModuleWithDependencies(
allModules = allModules,
problemModuleIds = setOf(problemModuleId),
problemCause = "This module requires symbol ${idSignature.render()}",
sourceCodeModuleId = userVisibleIrModulesSupport.sourceCodeModuleId,
moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator
)
}
@@ -113,6 +114,7 @@ class SymbolTypeMismatch(
potentiallyConflictingDependencies = findPotentiallyConflictingIncomingDependencies(
problemModuleId = declaringModuleId,
allModules = allModules,
sourceCodeModuleId = userVisibleIrModulesSupport.sourceCodeModuleId,
),
moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator
)
@@ -125,6 +127,7 @@ class SymbolTypeMismatch(
problemCause = "This module contains ${
idSignature?.render()?.let { "symbol $it" } ?: "a symbol"
} that is the cause of the conflict",
sourceCodeModuleId = userVisibleIrModulesSupport.sourceCodeModuleId,
moduleIdComparator = userVisibleIrModulesSupport.moduleIdComparator
)
}
@@ -159,6 +162,7 @@ private fun StringBuilder.appendProjectDependencies(
allModules: Map<ResolvedDependencyId, ResolvedDependency>,
problemModuleIds: Set<ResolvedDependencyId>,
problemCause: String,
sourceCodeModuleId: ResolvedDependencyId,
moduleIdComparator: Comparator<ResolvedDependencyId>
) {
append("\n\nProject dependencies:")
@@ -195,8 +199,7 @@ private fun StringBuilder.appendProjectDependencies(
append('\n').append(data.regularLinePrefix)
append(module.id)
val incomingDependencyId: ResolvedDependencyId = parentData?.incomingDependencyId
?: ResolvedDependencyId.SOURCE_CODE_MODULE_ID
val incomingDependencyId: ResolvedDependencyId = parentData?.incomingDependencyId ?: sourceCodeModuleId
val requestedVersion: ResolvedDependencyVersion = module.requestedVersionsByIncomingDependencies.getValue(incomingDependencyId)
if (!requestedVersion.isEmpty() || !module.selectedVersion.isEmpty()) {
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.
val firstLevelDependencies: Collection<ResolvedDependency> =
incomingDependencyIdToDependencies.getValue(ResolvedDependencyId.SOURCE_CODE_MODULE_ID)
val firstLevelDependencies: Collection<ResolvedDependency> = incomingDependencyIdToDependencies.getValue(sourceCodeModuleId)
renderModules(firstLevelDependencies, parentData = null)
@@ -375,7 +377,8 @@ private fun findPotentiallyConflictingOutgoingDependencies(
*/
private fun findPotentiallyConflictingIncomingDependencies(
problemModuleId: ResolvedDependencyId,
allModules: Map<ResolvedDependencyId, ResolvedDependency>
allModules: Map<ResolvedDependencyId, ResolvedDependency>,
sourceCodeModuleId: ResolvedDependencyId
): Map<ResolvedDependencyId, PotentialConflictDescription> {
val dependencyStatesMap: MutableMap<ResolvedDependencyId, MutableSet<DependencyState>> = mutableMapOf()
@@ -384,7 +387,7 @@ private fun findPotentiallyConflictingIncomingDependencies(
val module = allModules.findMatchingModule(moduleId)
module.requestedVersionsByIncomingDependencies.forEach { (incomingDependencyId, requestedVersion) ->
if (incomingDependencyId == ResolvedDependencyId.SOURCE_CODE_MODULE_ID) return@forEach
if (incomingDependencyId == sourceCodeModuleId) return@forEach
val dependencyState: DependencyState = when {
aboveConflictingDependency -> {

View File

@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.uniqueName
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:
* - all have [ResolvedDependency.selectedVersion] specified
@@ -23,24 +23,24 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
* not visible to the build system
*/
interface ExternalDependenciesLoader {
fun load(): Collection<ResolvedDependency>
fun load(): ResolvedDependencies
companion object {
val EMPTY = object : ExternalDependenciesLoader {
override fun load(): Collection<ResolvedDependency> = emptyList()
override fun load(): ResolvedDependencies = ResolvedDependencies.EMPTY
}
fun from(externalDependenciesFile: File?, onMalformedExternalDependencies: (String) -> Unit): ExternalDependenciesLoader =
if (externalDependenciesFile != null)
object : ExternalDependenciesLoader {
override fun load(): Collection<ResolvedDependency> {
override fun load(): ResolvedDependencies {
return if (externalDependenciesFile.exists) {
// Deserialize external dependencies from the [externalDependenciesFile].
val externalDependenciesText = String(externalDependenciesFile.readBytes())
ResolvedDependenciesSupport.deserialize(externalDependenciesText) { lineNo, line ->
onMalformedExternalDependencies("Malformed external dependencies at $externalDependenciesFile:$lineNo: $line")
}
} else emptyList()
} else ResolvedDependencies.EMPTY
}
}
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 {
val nameFromMetadataModuleHeader: String = deserializer.moduleFragment.name.asStringStripSpecialMarkers()
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
* libraries are stdlib, endorsed and platform libraries.
*/
protected open fun modulesFromDeserializers(deserializers: Collection<IrModuleDeserializer>): Map<ResolvedDependencyId, ResolvedDependency> {
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.associate { deserializer ->
protected open fun modulesFromDeserializers(
deserializers: Collection<IrModuleDeserializer>,
excludedModuleIds: Set<ResolvedDependencyId>
): Map<ResolvedDependencyId, ResolvedDependency> {
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.mapNotNull { deserializer ->
val moduleId = getUserVisibleModuleId(deserializer)
if (moduleId in excludedModuleIds) return@mapNotNull null
val module = ResolvedDependency(
id = moduleId,
// TODO: support extracting all the necessary details for non-Native libs: selectedVersion, requestedVersions, artifacts
selectedVersion = ResolvedDependencyVersion.EMPTY,
// 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.
requestedVersionsByIncomingDependencies = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to ResolvedDependencyVersion.EMPTY),
requestedVersionsByIncomingDependencies = mutableMapOf(
ResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID to ResolvedDependencyVersion.EMPTY
),
artifactPaths = mutableSetOf()
)
val outgoingDependencyIds = deserializer.moduleDependencies.map { getUserVisibleModuleId(it) }
moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds)
}
}.toMap()
// Stamp dependencies.
return modules.stampDependenciesWithRequestedVersionEqualToSelectedVersion()
@@ -91,9 +108,6 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
* The result of the merge of [ExternalDependenciesLoader.load] and [modulesFromDeserializers].
*/
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> =
mutableMapOf<String, ResolvedDependency>().apply {
externalDependencyModules.forEach { externalDependency ->
@@ -118,7 +132,10 @@ open class UserVisibleIrModulesSupport(protected val externalDependenciesLoader:
val providedModules = mutableListOf<ResolvedDependency>()
// 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)
if (externalDependencyModule != null) {
// 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
// the first-level dependency module (i.e. the module that only the source code module depends on).
if (module.requestedVersionsByIncomingDependencies.isEmpty()) {
module.requestedVersionsByIncomingDependencies[ResolvedDependencyId.SOURCE_CODE_MODULE_ID] = module.selectedVersion
module.requestedVersionsByIncomingDependencies[sourceCodeModuleId] = module.selectedVersion
}
}

View File

@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.util
import org.jetbrains.kotlin.utils.ResolvedDependencies
import org.jetbrains.kotlin.utils.ResolvedDependenciesSupport
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
@@ -14,6 +15,7 @@ class ResolvedDependenciesSupportTest {
@Test
fun success1() {
val originalText = """
|0 \
|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-cinterop-bits.klib
@@ -28,10 +30,10 @@ class ResolvedDependenciesSupportTest {
|
""".trimMargin()
val deserializedModules = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
val (deserializedModules, sourceCodeModuleId) = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
fail("Unexpected failure at line $lineNo: $line")
}
val restoredText = ResolvedDependenciesSupport.serialize(deserializedModules)
val restoredText = ResolvedDependenciesSupport.serialize(ResolvedDependencies(deserializedModules, sourceCodeModuleId))
assertEquals(originalText, restoredText)
}
@@ -39,6 +41,7 @@ class ResolvedDependenciesSupportTest {
@Test
fun success2() {
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[1.0] #0[1.0]
@@ -46,10 +49,10 @@ class ResolvedDependenciesSupportTest {
|
""".trimMargin()
val deserializedModules = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
val (deserializedModules, sourceCodeModuleId) = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
fail("Unexpected failure at line $lineNo: $line")
}
val restoredText = ResolvedDependenciesSupport.serialize(deserializedModules)
val restoredText = ResolvedDependenciesSupport.serialize(ResolvedDependencies(deserializedModules, sourceCodeModuleId))
assertEquals(originalText, restoredText)
}
@@ -57,6 +60,7 @@ class ResolvedDependenciesSupportTest {
@Test
fun success3() {
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]
|${'\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]
@@ -80,10 +84,29 @@ class ResolvedDependenciesSupportTest {
|
""".trimMargin()
val deserializedModules = ResolvedDependenciesSupport.deserialize(originalText) { lineNo, line ->
val (deserializedModules, sourceCodeModuleId) = ResolvedDependenciesSupport.deserialize(originalText) { 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)
}
@@ -92,6 +115,7 @@ class ResolvedDependenciesSupportTest {
fun failure1() {
// There is no record with number 42!
val originalText = """
|0 \
|1 org.sample:liba,org.sample:liba-native[2.0] #0[2.0] #2[1.0] #42[42.42]
|${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native[1.0] #0[1.0]
@@ -99,13 +123,19 @@ class ResolvedDependenciesSupportTest {
|
""".trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { _, _ -> throw MyException() }
ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(1, lineNo)
throw MyException()
}
fail()
}
@Test(expected = MyException::class)
fun failure2() {
// Name 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 [1.0] #0[1.0]
@@ -113,13 +143,58 @@ class ResolvedDependenciesSupportTest {
|
""".trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { _, _ -> throw MyException() }
ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(3, lineNo)
throw MyException()
}
fail()
}
@Test(expected = MyException::class)
fun failure3() {
// Version 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(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]
|${'\t'}/some/path/liba.klib
|2 org.sample:libb,org.sample:libb-native #0[1.0]
@@ -127,7 +202,12 @@ class ResolvedDependenciesSupportTest {
|
""".trimMargin()
ResolvedDependenciesSupport.deserialize(originalText) { _, _ -> throw MyException() }
ResolvedDependenciesSupport.deserialize(originalText) { lineNo, _ ->
assertEquals(0, lineNo)
throw MyException()
}
fail()
}
private class MyException : Exception()

View File

@@ -47,7 +47,7 @@ class ResolvedDependencyIdTest {
@Test
fun toStringImplementation() {
assertEquals("/", ResolvedDependencyId.SOURCE_CODE_MODULE_ID.toString())
assertEquals("/", ResolvedDependencyId.DEFAULT_SOURCE_CODE_MODULE_ID.toString())
assertEquals("foo", ResolvedDependencyId("foo").toString())
assertEquals("bar (foo)", ResolvedDependencyId("foo", "bar").toString())
assertEquals("bar (baz, foo)", ResolvedDependencyId("foo", "bar", "baz").toString())

View File

@@ -45,7 +45,7 @@ class ResolvedDependencyId private constructor(val uniqueNames: Set<String>) {
}
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)
* 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
* (the one that wins among others, which is typically handled inside of the build system). If the module is the top-level module,
* then it has [ResolvedDependencyId.SOURCE_CODE_MODULE_ID] in the mapping.
* (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 [ResolvedDependencies.sourceCodeModuleId] in the mapping.
* - [artifactPaths] absolute paths to every artifact represented by this module
*/
class ResolvedDependency(
@@ -75,14 +75,27 @@ class ResolvedDependency(
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 {
fun serialize(modules: Collection<ResolvedDependency>): String {
val moduleIdToIndex = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to 0).apply {
modules.forEachIndexed { index, module -> this[module.id] = index + 1 }
fun serialize(dependencies: ResolvedDependencies): String {
val moduleIdToIndex = mutableMapOf(dependencies.sourceCodeModuleId to 0).apply {
dependencies.modules.forEachIndexed { index, module -> this[module.id] = index + 1 }
}
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)
append(moduleIndex.toString()).append(' ')
module.id.uniqueNames.joinTo(this, separator = ",")
@@ -100,17 +113,18 @@ object ResolvedDependenciesSupport {
}
}
fun deserialize(source: String, onMalformedLine: (lineNo: Int, line: String) -> Unit): Collection<ResolvedDependency> {
val moduleIndexToId: MutableMap<Int, ResolvedDependencyId> = mutableMapOf(0 to ResolvedDependencyId.SOURCE_CODE_MODULE_ID)
fun deserialize(source: String, onMalformedLine: (lineNo: Int, line: String) -> Unit): ResolvedDependencies {
val moduleIndexToId: MutableMap<Int, ResolvedDependencyId> = mutableMapOf()
val requestedVersionsByIncomingDependenciesIndices: MutableMap<ResolvedDependencyId, Map<Int, ResolvedDependencyVersion>> =
mutableMapOf()
var sourceCodeModuleId: ResolvedDependencyId? = null
val modules = mutableListOf<ResolvedDependency>()
source.lines().forEachIndexed { lineNo, line ->
fun malformedLine(): Collection<ResolvedDependency> {
fun malformedLine(): ResolvedDependencies {
onMalformedLine(lineNo, line)
return emptyList()
return ResolvedDependencies.EMPTY
}
when {
@@ -123,7 +137,13 @@ object ResolvedDependenciesSupport {
else
return malformedLine()
}
line[0] == '0' -> {
val result = SOURCE_CODE_MODULE_REGEX.matchEntire(line) ?: return malformedLine()
sourceCodeModuleId = ResolvedDependencyId(result.groupValues[1].split(','))
}
else -> {
if (sourceCodeModuleId == null) return malformedLine()
val result = DEPENDENCY_MODULE_REGEX.matchEntire(line) ?: return malformedLine()
val moduleIndex = result.groupValues[1].toInt()
val moduleId = ResolvedDependencyId(result.groupValues[2].split(','))
@@ -156,14 +176,18 @@ object ResolvedDependenciesSupport {
// Stamp incoming dependencies & requested versions.
modules.forEach { module ->
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
}
}
return modules
return ResolvedDependencies(modules, sourceCodeModuleId!!)
}
private val SOURCE_CODE_MODULE_REGEX = Regex("^0 ([^\\[]+)$")
private val DEPENDENCY_MODULE_REGEX = Regex("^(\\d+) ([^\\[]+)\\[([^]]+)](.*)?$")
private val REQUESTED_VERSION_BY_INCOMING_DEPENDENCY_REGEX = Regex("^#(\\d+)\\[(.*)]$")
}

View File

@@ -26,7 +26,10 @@ class KonanUserVisibleIrModulesSupport(
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 kotlinNativeBundledLibraries: MutableSet<ResolvedDependencyId> = mutableSetOf()
@@ -34,6 +37,9 @@ class KonanUserVisibleIrModulesSupport(
val modules: Map<ResolvedDependencyId, ModuleWithUninitializedDependencies> = deserializers.mapNotNull { deserializer ->
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 isDefaultLibrary = library.isDefaultLibrary
@@ -41,7 +47,6 @@ class KonanUserVisibleIrModulesSupport(
// 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,
@@ -54,7 +59,9 @@ class KonanUserVisibleIrModulesSupport(
// 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 }
val outgoingDependencyIds = library.unresolvedDependencies.mapNotNull { unresolvedDependency ->
unresolvedDependency.moduleId.takeIf { it !in excludedModuleIds }
}
moduleId to ModuleWithUninitializedDependencies(module, outgoingDependencyIds)
}.toMap()
@@ -87,7 +94,7 @@ class KonanUserVisibleIrModulesSupport(
for ((moduleId, module) in compressedModules) {
if (moduleId.isKonanPlatformLibrary) {
if (ResolvedDependencyId.SOURCE_CODE_MODULE_ID !in module.requestedVersionsByIncomingDependencies) {
if (sourceCodeModuleId !in module.requestedVersionsByIncomingDependencies) {
continue
}
@@ -116,7 +123,7 @@ class KonanUserVisibleIrModulesSupport(
val compressedModule = ResolvedDependency(
id = compressedModuleId,
selectedVersion = platformLibrariesVersion!!,
requestedVersionsByIncomingDependencies = mutableMapOf(ResolvedDependencyId.SOURCE_CODE_MODULE_ID to platformLibrariesVersion),
requestedVersionsByIncomingDependencies = mutableMapOf(sourceCodeModuleId to platformLibrariesVersion),
artifactPaths = mutableSetOf()
)

View File

@@ -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

View File

@@ -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()

View File

@@ -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)
}
}
}
}