Clean-up: Split nativeLibrariesUtil.kt into separate files in idea-gradle

This commit is contained in:
Dmitriy Dolovov
2020-01-26 19:32:41 +07:00
parent ba1415d7d1
commit c9b1c384e6
12 changed files with 325 additions and 297 deletions
@@ -21,7 +21,7 @@ import com.intellij.util.PathUtilRt
import com.intellij.util.concurrency.AppExecutorUtil
import org.jetbrains.concurrency.CancellablePromise
import org.jetbrains.kotlin.idea.caches.project.getModuleInfosFromIdeaModel
import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil
import org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider
import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
@@ -21,7 +21,7 @@ import com.intellij.util.PathUtilRt
import com.intellij.util.concurrency.AppExecutorUtil
import org.jetbrains.concurrency.CancellablePromise
import org.jetbrains.kotlin.idea.caches.project.getModuleInfosFromIdeaModel
import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil
import org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider
import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.gradle.CompilerArgumentsBySourceSet
import org.jetbrains.kotlin.ide.konan.NativeLibraryKind
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.configuration.GradlePropertiesFileFacade.Companion.KOTLIN_CODE_STYLE_GRADLE_SETTING
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX
import org.jetbrains.kotlin.idea.facet.*
import org.jetbrains.kotlin.idea.formatter.ProjectCodeStyleImporter
import org.jetbrains.kotlin.idea.framework.CommonLibraryKind
@@ -35,6 +35,9 @@ import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.idea.configuration.GradlePropertiesFileFacade.Companion.KOTLIN_NOT_IMPORTED_COMMON_SOURCE_SETS_SETTING
import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver.Companion.fullName
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesDependencySubstitutor
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibrariesFixer
import org.jetbrains.kotlin.idea.platform.IdePlatformKindTooling
import org.jetbrains.kotlin.util.removeSuffixIfPresent
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
@@ -0,0 +1,141 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.configuration.klib
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.util.Key
import org.gradle.tooling.model.idea.IdeaModule
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
import org.jetbrains.kotlin.idea.configuration.buildClasspathData
import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.plugins.gradle.ExternalDependencyId
import org.jetbrains.plugins.gradle.model.*
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import java.io.File
// KT-29613, KT-29783
internal class KotlinNativeLibrariesDependencySubstitutor(
mppModel: KotlinMPPGradleModel,
private val gradleModule: IdeaModule,
private val resolverCtx: ProjectResolverContext
) {
// Substitutes `ExternalDependency` entries that represent KLIBs with new dependency entries with proper type and name:
// - every `FileCollectionDependency` is checked whether it points to an existing KLIB, and substituted if it is
// - similarly for every `ExternalLibraryDependency` with `groupId == "Kotlin/Native"` (legacy KLIB provided by Gradle plugin <= 1.3.20)
fun substituteDependencies(dependencies: Collection<ExternalDependency>): List<ExternalDependency> {
val result = ArrayList(dependencies)
for (i in 0 until result.size) {
val dependency = result[i]
val dependencySubstitute = when (dependency) {
is FileCollectionDependency -> getFileCollectionDependencySubstitute(dependency)
is ExternalLibraryDependency -> getExternalLibraryDependencySubstitute(dependency)
else -> DependencySubstitute.NoSubstitute
}
val newDependency = (dependencySubstitute as? DependencySubstitute.YesSubstitute)?.substitute ?: continue
result[i] = newDependency
}
return result
}
private val ProjectResolverContext.dependencySubstitutionCache
get() = getUserData(KLIB_DEPENDENCY_SUBSTITUTION_CACHE) ?: putUserDataIfAbsent(KLIB_DEPENDENCY_SUBSTITUTION_CACHE, HashMap())
private val klibInfoProvider: KlibInfoProvider? by lazy {
val kotlinNativeHome = mppModel.kotlinNativeHome.takeIf { it != KotlinMPPGradleModel.NO_KOTLIN_NATIVE_HOME }?.let(::File)
if (kotlinNativeHome == null) {
LOG.warn(
"""
Can't obtain Kotlin/Native home path in Kotlin Gradle plugin.
${KotlinMPPGradleModel::class.java.simpleName} is $mppModel.
${KotlinNativeLibrariesDependencySubstitutor::class.java.simpleName} will run in idle mode. No dependencies will be substituted.
""".trimIndent()
)
null
} else
KlibInfoProvider(kotlinNativeHome = kotlinNativeHome)
}
private val kotlinVersion: String? by lazy {
// first, try to figure out Kotlin plugin version by classpath (the default approach)
val classpathData = buildClasspathData(gradleModule, resolverCtx)
val versionFromClasspath = findKotlinPluginVersion(classpathData)
if (versionFromClasspath == null) {
// then, examine Kotlin MPP Gradle model
val versionFromModel = mppModel.targets
.asSequence()
.flatMap { it.compilations.asSequence() }
.mapNotNull { it.kotlinTaskProperties.pluginVersion?.takeIf(String::isNotBlank) }
.firstOrNull()
if (versionFromModel == null) {
LOG.warn(
"""
Can't obtain Kotlin Gradle plugin version for ${gradleModule.name} module.
Build classpath is ${classpathData.classpathEntries.flatMap { it.classesFile }}.
${KotlinMPPGradleModel::class.java.simpleName} is $mppModel.
${KotlinNativeLibrariesDependencySubstitutor::class.java.simpleName} will run in idle mode. No dependencies will be substituted.
""".trimIndent()
)
null
} else
versionFromModel
} else
versionFromClasspath
}
private fun getFileCollectionDependencySubstitute(dependency: FileCollectionDependency): DependencySubstitute =
resolverCtx.dependencySubstitutionCache.getOrPut(dependency.id) {
val libraryFile = dependency.files.firstOrNull() ?: return@getOrPut DependencySubstitute.NoSubstitute
buildSubstituteIfNecessary(libraryFile)
}
private fun getExternalLibraryDependencySubstitute(dependency: ExternalLibraryDependency): DependencySubstitute =
resolverCtx.dependencySubstitutionCache.getOrPut(dependency.id) {
val libraryFile = dependency.file ?: return@getOrPut DependencySubstitute.NoSubstitute
buildSubstituteIfNecessary(libraryFile)
}
private fun buildSubstituteIfNecessary(libraryFile: File): DependencySubstitute {
// need to check whether `library` points to a real KLIB,
// and if answer is yes then build a new dependency that will substitute original one
val klib = klibInfoProvider?.getKlibInfo(libraryFile) as? NativeDistributionKlibInfo ?: return DependencySubstitute.NoSubstitute
val nonNullKotlinVersion = kotlinVersion ?: return DependencySubstitute.NoSubstitute
val newLibraryName = KotlinNativeLibraryNameUtil.buildIDELibraryName(nonNullKotlinVersion, klib.name, klib.target?.name)
val substitute = DefaultExternalMultiLibraryDependency().apply {
classpathOrder = if (klib.name == KONAN_STDLIB_NAME) -1 else 0 // keep stdlib upper
name = newLibraryName
packaging = DEFAULT_PACKAGING
files += klib.path
sources += klib.sourcePaths
scope = DependencyScope.PROVIDED.name
}
return DependencySubstitute.YesSubstitute(substitute)
}
companion object {
private const val DEFAULT_PACKAGING = "jar"
private val LOG = Logger.getInstance(KotlinNativeLibrariesDependencySubstitutor::class.java)
private val KLIB_DEPENDENCY_SUBSTITUTION_CACHE =
Key.create<MutableMap<ExternalDependencyId, DependencySubstitute>>("KLIB_DEPENDENCY_SUBSTITUTION_CACHE")
}
}
private sealed class DependencySubstitute {
object NoSubstitute : DependencySubstitute()
class YesSubstitute(val substitute: ExternalMultiLibraryDependency) : DependencySubstitute()
}
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.configuration.klib
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.LibraryData
import com.intellij.openapi.externalSystem.model.project.LibraryDependencyData
import com.intellij.openapi.externalSystem.model.project.LibraryLevel
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.roots.libraries.Library
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.GRADLE_LIBRARY_PREFIX
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil
/**
* Gradle IDE plugin creates [LibraryData] nodes with internal name consisting of two parts:
* - mandatory "Gradle: " prefix
* - and library name
* Then internal name is propagated to IDE [Library] object, and is displayed in IDE as "Gradle: <LIBRARY_NAME>".
* [KotlinNativeLibrariesFixer] removes "Gradle: " prefix from all [LibraryData] items representing KLIBs to make them
* look more friendly.
*
* Also, [KotlinNativeLibrariesFixer] makes sure that all KLIBs from Kotlin/Native distribution are added to IDE project model
* as project-level libraries. This is necessary until the appropriate fix in IDEA will be implemented (for details see IDEA-211451).
*/
internal object KotlinNativeLibrariesFixer {
fun applyTo(ownerNode: DataNode<GradleSourceSetData>, ideProject: DataNode<ProjectData>) {
for (libraryDependencyNode in ExternalSystemApiUtil.findAll(ownerNode, ProjectKeys.LIBRARY_DEPENDENCY)) {
val libraryData = libraryDependencyNode.data.target
// Only KLIBs from Kotlin/Native distribution can have such prefix:
if (libraryData.internalName.startsWith("$GRADLE_LIBRARY_PREFIX$KOTLIN_NATIVE_LIBRARY_PREFIX")) {
fixLibraryName(libraryData)
addLibraryToProjectModel(libraryData, ideProject)
fixLibraryDependencyLevel(libraryDependencyNode)
}
}
}
private fun fixLibraryName(libraryData: LibraryData) {
libraryData.internalName = libraryData.internalName.substringAfter(GRADLE_LIBRARY_PREFIX)
}
private fun addLibraryToProjectModel(libraryData: LibraryData, ideProject: DataNode<ProjectData>) {
GradleProjectResolverUtil.linkProjectLibrary(ideProject, libraryData)
}
private fun fixLibraryDependencyLevel(oldDependencyNode: DataNode<LibraryDependencyData>) {
val oldDependency = oldDependencyNode.data
if (oldDependency.level == LibraryLevel.PROJECT) return // nothing to do
val newDependency = LibraryDependencyData(oldDependency.ownerModule, oldDependency.target, LibraryLevel.PROJECT).apply {
scope = oldDependency.scope
order = oldDependency.order
isExported = oldDependency.isExported
}
val parentNode = oldDependencyNode.parent ?: return
val childNodes = oldDependencyNode.children
val newDependencyNode = parentNode.createChild(oldDependencyNode.key, newDependency)
for (child in childNodes) {
newDependencyNode.addChild(child)
}
oldDependencyNode.clear(true)
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.configuration.klib
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.LibraryData
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.IdeUIModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants
import com.intellij.openapi.externalSystem.util.Order
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.libraries.Library
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE
// KT-30490. This `ProjectDataService` must be executed immediately after
// `com.intellij.openapi.externalSystem.service.project.manage.LibraryDataService` to clean-up KLIBs before any other actions taken on them.
@Order(ExternalSystemConstants.BUILTIN_LIBRARY_DATA_SERVICE_ORDER + 1) // force order
class KotlinNativeLibraryDataService : AbstractProjectDataService<LibraryData, Library>() {
override fun getTargetDataKey() = ProjectKeys.LIBRARY
// See also `com.intellij.openapi.externalSystem.service.project.manage.LibraryDataService.postProcess()`
override fun postProcess(
toImport: MutableCollection<DataNode<LibraryData>>,
projectData: ProjectData?,
project: Project,
modelsProvider: IdeModifiableModelsProvider
) {
if (projectData == null || modelsProvider is IdeUIModifiableModelsProvider) return
val librariesModel = modelsProvider.modifiableProjectLibrariesModel
val potentialOrphans = HashMap<String, Library>()
librariesModel.libraries.forEach { library ->
val libraryName = library.name?.takeIf { it.startsWith(KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) } ?: return@forEach
potentialOrphans[libraryName] = library
}
if (potentialOrphans.isEmpty()) return
modelsProvider.modules.forEach { module ->
modelsProvider.getOrderEntries(module).forEach inner@{ orderEntry ->
val libraryOrderEntry = orderEntry as? LibraryOrderEntry ?: return@inner
if (libraryOrderEntry.isModuleLevel) return@inner
val libraryName = (libraryOrderEntry.library?.name ?: libraryOrderEntry.libraryName)
?.takeIf { it.startsWith(KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) } ?: return@inner
potentialOrphans.remove(libraryName)
}
}
potentialOrphans.keys.forEach { libraryName ->
librariesModel.getLibraryByName(libraryName)?.let { librariesModel.removeLibrary(it) }
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.configuration.klib
object KotlinNativeLibraryNameUtil {
internal const val KOTLIN_NATIVE_LIBRARY_PREFIX = "Kotlin/Native"
internal const val KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE = "$KOTLIN_NATIVE_LIBRARY_PREFIX "
internal const val GRADLE_LIBRARY_PREFIX = "Gradle: "
private val IDE_LIBRARY_NAME_REGEX = Regex("^$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE([^\\s]+) - ([^\\s]+)( \\[([\\w]+)\\])?$")
// Builds the name of Kotlin/Native library that is a part of Kotlin/Native distribution
// as it will be displayed in IDE UI.
fun buildIDELibraryName(kotlinVersion: String, libraryName: String, platform: String?): String {
val platformNamePart = platform?.let { " [$it]" }.orEmpty()
return "$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE$kotlinVersion - $libraryName$platformNamePart"
}
// N.B. Returns null if this is not IDE name of Kotlin/Native library.
fun parseIDELibraryName(ideLibraryName: String): Triple<String, String, String?>? {
val match = IDE_LIBRARY_NAME_REGEX.matchEntire(ideLibraryName) ?: return null
val kotlinVersion = match.groups[1]!!.value
val libraryName = match.groups[2]!!.value
val platform = match.groups[4]?.value
return Triple(kotlinVersion, libraryName, platform)
}
fun isGradleLibraryName(ideLibraryName: String) = ideLibraryName.startsWith(GRADLE_LIBRARY_PREFIX)
}
@@ -1,288 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.configuration
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.LibraryData
import com.intellij.openapi.externalSystem.model.project.LibraryDependencyData
import com.intellij.openapi.externalSystem.model.project.LibraryLevel
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.IdeUIModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.externalSystem.util.ExternalSystemConstants
import com.intellij.openapi.externalSystem.util.Order
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.util.Key
import org.gradle.tooling.model.idea.IdeaModule
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
import org.jetbrains.kotlin.idea.configuration.DependencySubstitute.NoSubstitute
import org.jetbrains.kotlin.idea.configuration.DependencySubstitute.YesSubstitute
import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil.buildIDELibraryName
import org.jetbrains.kotlin.idea.configuration.klib.KlibInfoProvider
import org.jetbrains.kotlin.idea.configuration.klib.NativeDistributionKlibInfo
import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion
import org.jetbrains.kotlin.konan.library.KONAN_STDLIB_NAME
import org.jetbrains.plugins.gradle.ExternalDependencyId
import org.jetbrains.plugins.gradle.model.*
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil
import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext
import java.io.File
// KT-30490. This `ProjectDataService` must be executed immediately after
// `com.intellij.openapi.externalSystem.service.project.manage.LibraryDataService` to clean-up KLIBs before any other actions taken on them.
@Order(ExternalSystemConstants.BUILTIN_LIBRARY_DATA_SERVICE_ORDER + 1) // force order
class KotlinNativeLibraryDataService : AbstractProjectDataService<LibraryData, Library>() {
override fun getTargetDataKey() = ProjectKeys.LIBRARY
// See also `com.intellij.openapi.externalSystem.service.project.manage.LibraryDataService.postProcess()`
override fun postProcess(
toImport: MutableCollection<DataNode<LibraryData>>,
projectData: ProjectData?,
project: Project,
modelsProvider: IdeModifiableModelsProvider
) {
if (projectData == null || modelsProvider is IdeUIModifiableModelsProvider) return
val librariesModel = modelsProvider.modifiableProjectLibrariesModel
val potentialOrphans = HashMap<String, Library>()
librariesModel.libraries.forEach { library ->
val libraryName = library.name?.takeIf { it.startsWith(KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) } ?: return@forEach
potentialOrphans[libraryName] = library
}
if (potentialOrphans.isEmpty()) return
modelsProvider.modules.forEach { module ->
modelsProvider.getOrderEntries(module).forEach inner@{ orderEntry ->
val libraryOrderEntry = orderEntry as? LibraryOrderEntry ?: return@inner
if (libraryOrderEntry.isModuleLevel) return@inner
val libraryName = (libraryOrderEntry.library?.name ?: libraryOrderEntry.libraryName)
?.takeIf { it.startsWith(KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE) } ?: return@inner
potentialOrphans.remove(libraryName)
}
}
potentialOrphans.keys.forEach { libraryName ->
librariesModel.getLibraryByName(libraryName)?.let { librariesModel.removeLibrary(it) }
}
}
}
// KT-29613, KT-29783
internal class KotlinNativeLibrariesDependencySubstitutor(
mppModel: KotlinMPPGradleModel,
private val gradleModule: IdeaModule,
private val resolverCtx: ProjectResolverContext
) {
// Substitutes `ExternalDependency` entries that represent KLIBs with new dependency entries with proper type and name:
// - every `FileCollectionDependency` is checked whether it points to an existing KLIB, and substituted if it is
// - similarly for every `ExternalLibraryDependency` with `groupId == "Kotlin/Native"` (legacy KLIB provided by Gradle plugin <= 1.3.20)
fun substituteDependencies(dependencies: Collection<ExternalDependency>): List<ExternalDependency> {
val result = ArrayList(dependencies)
for (i in 0 until result.size) {
val dependency = result[i]
val dependencySubstitute = when (dependency) {
is FileCollectionDependency -> getFileCollectionDependencySubstitute(dependency)
is ExternalLibraryDependency -> getExternalLibraryDependencySubstitute(dependency)
else -> NoSubstitute
}
val newDependency = (dependencySubstitute as? YesSubstitute)?.substitute ?: continue
result[i] = newDependency
}
return result
}
private val ProjectResolverContext.dependencySubstitutionCache
get() = getUserData(KLIB_DEPENDENCY_SUBSTITUTION_CACHE) ?: putUserDataIfAbsent(KLIB_DEPENDENCY_SUBSTITUTION_CACHE, HashMap())
private val klibInfoProvider: KlibInfoProvider? by lazy {
val kotlinNativeHome = mppModel.kotlinNativeHome.takeIf { it != KotlinMPPGradleModel.NO_KOTLIN_NATIVE_HOME }?.let(::File)
if (kotlinNativeHome == null) {
LOG.warn(
"""
Can't obtain Kotlin/Native home path in Kotlin Gradle plugin.
${KotlinMPPGradleModel::class.java.simpleName} is $mppModel.
${KotlinNativeLibrariesDependencySubstitutor::class.java.simpleName} will run in idle mode. No dependencies will be substituted.
""".trimIndent()
)
null
} else
KlibInfoProvider(kotlinNativeHome = kotlinNativeHome)
}
private val kotlinVersion: String? by lazy {
// first, try to figure out Kotlin plugin version by classpath (the default approach)
val classpathData = buildClasspathData(gradleModule, resolverCtx)
val versionFromClasspath = findKotlinPluginVersion(classpathData)
if (versionFromClasspath == null) {
// then, examine Kotlin MPP Gradle model
val versionFromModel = mppModel.targets
.asSequence()
.flatMap { it.compilations.asSequence() }
.mapNotNull { it.kotlinTaskProperties.pluginVersion?.takeIf(String::isNotBlank) }
.firstOrNull()
if (versionFromModel == null) {
LOG.warn(
"""
Can't obtain Kotlin Gradle plugin version for ${gradleModule.name} module.
Build classpath is ${classpathData.classpathEntries.flatMap { it.classesFile }}.
${KotlinMPPGradleModel::class.java.simpleName} is $mppModel.
${KotlinNativeLibrariesDependencySubstitutor::class.java.simpleName} will run in idle mode. No dependencies will be substituted.
""".trimIndent()
)
null
} else
versionFromModel
} else
versionFromClasspath
}
private fun getFileCollectionDependencySubstitute(dependency: FileCollectionDependency): DependencySubstitute =
resolverCtx.dependencySubstitutionCache.getOrPut(dependency.id) {
val libraryFile = dependency.files.firstOrNull() ?: return@getOrPut NoSubstitute
buildSubstituteIfNecessary(libraryFile)
}
private fun getExternalLibraryDependencySubstitute(dependency: ExternalLibraryDependency): DependencySubstitute =
resolverCtx.dependencySubstitutionCache.getOrPut(dependency.id) {
val libraryFile = dependency.file ?: return@getOrPut NoSubstitute
buildSubstituteIfNecessary(libraryFile)
}
private fun buildSubstituteIfNecessary(libraryFile: File): DependencySubstitute {
// need to check whether `library` points to a real KLIB,
// and if answer is yes then build a new dependency that will substitute original one
val klib = klibInfoProvider?.getKlibInfo(libraryFile) as? NativeDistributionKlibInfo ?: return NoSubstitute
val nonNullKotlinVersion = kotlinVersion ?: return NoSubstitute
val newLibraryName = buildIDELibraryName(nonNullKotlinVersion, klib.name, klib.target?.name)
val substitute = DefaultExternalMultiLibraryDependency().apply {
classpathOrder = if (klib.name == KONAN_STDLIB_NAME) -1 else 0 // keep stdlib upper
name = newLibraryName
packaging = DEFAULT_PACKAGING
files += klib.path
sources += klib.sourcePaths
scope = DependencyScope.PROVIDED.name
}
return YesSubstitute(substitute)
}
companion object {
private const val DEFAULT_PACKAGING = "jar"
private val LOG = Logger.getInstance(KotlinNativeLibrariesDependencySubstitutor::class.java)
private val KLIB_DEPENDENCY_SUBSTITUTION_CACHE =
Key.create<MutableMap<ExternalDependencyId, DependencySubstitute>>("KLIB_DEPENDENCY_SUBSTITUTION_CACHE")
}
}
/**
* Gradle IDE plugin creates [LibraryData] nodes with internal name consisting of two parts:
* - mandatory "Gradle: " prefix
* - and library name
* Then internal name is propagated to IDE [Library] object, and is displayed in IDE as "Gradle: <LIBRARY_NAME>".
* [KotlinNativeLibrariesFixer] removes "Gradle: " prefix from all [LibraryData] items representing KLIBs to make them
* look more friendly.
*
* Also, [KotlinNativeLibrariesFixer] makes sure that all KLIBs from Kotlin/Native distribution are added to IDE project model
* as project-level libraries. This is necessary until the appropriate fix in IDEA will be implemented (for details see IDEA-211451).
*/
internal object KotlinNativeLibrariesFixer {
fun applyTo(ownerNode: DataNode<GradleSourceSetData>, ideProject: DataNode<ProjectData>) {
for (libraryDependencyNode in ExternalSystemApiUtil.findAll(ownerNode, ProjectKeys.LIBRARY_DEPENDENCY)) {
val libraryData = libraryDependencyNode.data.target
// Only KLIBs from Kotlin/Native distribution can have such prefix:
if (libraryData.internalName.startsWith("$GRADLE_LIBRARY_PREFIX$KOTLIN_NATIVE_LIBRARY_PREFIX")) {
fixLibraryName(libraryData)
addLibraryToProjectModel(libraryData, ideProject)
fixLibraryDependencyLevel(libraryDependencyNode)
}
}
}
private fun fixLibraryName(libraryData: LibraryData) {
libraryData.internalName = libraryData.internalName.substringAfter(GRADLE_LIBRARY_PREFIX)
}
private fun addLibraryToProjectModel(libraryData: LibraryData, ideProject: DataNode<ProjectData>) {
GradleProjectResolverUtil.linkProjectLibrary(ideProject, libraryData)
}
private fun fixLibraryDependencyLevel(oldDependencyNode: DataNode<LibraryDependencyData>) {
val oldDependency = oldDependencyNode.data
if (oldDependency.level == LibraryLevel.PROJECT) return // nothing to do
val newDependency = LibraryDependencyData(oldDependency.ownerModule, oldDependency.target, LibraryLevel.PROJECT).apply {
scope = oldDependency.scope
order = oldDependency.order
isExported = oldDependency.isExported
}
val parentNode = oldDependencyNode.parent ?: return
val childNodes = oldDependencyNode.children
val newDependencyNode = parentNode.createChild(oldDependencyNode.key, newDependency)
for (child in childNodes) {
newDependencyNode.addChild(child)
}
oldDependencyNode.clear(true)
}
}
private sealed class DependencySubstitute {
object NoSubstitute : DependencySubstitute()
class YesSubstitute(val substitute: ExternalMultiLibraryDependency) : DependencySubstitute()
}
object KotlinNativeLibraryNameUtil {
private val IDE_LIBRARY_NAME_REGEX = Regex("^$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE([^\\s]+) - ([^\\s]+)( \\[([\\w]+)\\])?$")
// Builds the name of Kotlin/Native library that is a part of Kotlin/Native distribution
// as it will be displayed in IDE UI.
fun buildIDELibraryName(kotlinVersion: String, libraryName: String, platform: String?): String {
val platformNamePart = platform?.let { " [$it]" }.orEmpty()
return "$KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE$kotlinVersion - $libraryName$platformNamePart"
}
// N.B. Returns null if this is not IDE name of Kotlin/Native library.
fun parseIDELibraryName(ideLibraryName: String): Triple<String, String, String?>? {
val match = IDE_LIBRARY_NAME_REGEX.matchEntire(ideLibraryName) ?: return null
val kotlinVersion = match.groups[1]!!.value
val libraryName = match.groups[2]!!.value
val platform = match.groups[4]?.value
return Triple(kotlinVersion, libraryName, platform)
}
fun isGradleLibraryName(ideLibraryName: String) = ideLibraryName.startsWith(GRADLE_LIBRARY_PREFIX)
}
internal const val KOTLIN_NATIVE_LIBRARY_PREFIX = "Kotlin/Native"
private const val KOTLIN_NATIVE_LIBRARY_PREFIX_PLUS_SPACE = "$KOTLIN_NATIVE_LIBRARY_PREFIX "
private const val GRADLE_LIBRARY_PREFIX = "Gradle: "
@@ -1,14 +1,14 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.configuration
package org.jetbrains.kotlin.idea.configuration.klib
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil.buildIDELibraryName
import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil.isGradleLibraryName
import org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryNameUtil.parseIDELibraryName
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.buildIDELibraryName
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.isGradleLibraryName
import org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryNameUtil.parseIDELibraryName
class KotlinNativeLibraryNameUtilTest : TestCase() {
+1 -1
View File
@@ -25,7 +25,7 @@
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleLibraryDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinTargetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.KotlinJavaMPPSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryDataService"/>
<externalSystemTaskNotificationListener implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsUpdater"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptOutOfSourceNotificationProvider"/>
+1 -1
View File
@@ -22,7 +22,7 @@
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinGradleLibraryDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinTargetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.KotlinJavaMPPSourceSetDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.KotlinNativeLibraryDataService"/>
<externalProjectDataService implementation="org.jetbrains.kotlin.idea.configuration.klib.KotlinNativeLibraryDataService"/>
<externalSystemTaskNotificationListener implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsUpdater"/>
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptOutOfSourceNotificationProvider"/>