Clean-up: Split nativeLibrariesUtil.kt into separate files in idea-gradle
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+3
@@ -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
|
||||
|
||||
+141
@@ -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()
|
||||
}
|
||||
+74
@@ -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)
|
||||
}
|
||||
}
|
||||
+63
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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: "
|
||||
+5
-5
@@ -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() {
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user