From c9b1c384e690e5d821c17da03da38e098255edc7 Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Sun, 26 Jan 2020 19:32:41 +0700 Subject: [PATCH] Clean-up: Split nativeLibrariesUtil.kt into separate files in idea-gradle --- .../KotlinNativeABICompatibilityChecker.kt | 2 +- ...KotlinNativeABICompatibilityChecker.kt.192 | 2 +- .../KotlinGradleSourceSetDataService.kt | 1 + .../KotlinMPPGradleProjectResolver.kt | 3 + ...linNativeLibrariesDependencySubstitutor.kt | 141 +++++++++ .../klib/KotlinNativeLibrariesFixer.kt | 74 +++++ .../klib/KotlinNativeLibraryDataService.kt | 63 ++++ .../klib/KotlinNativeLibraryNameUtil.kt | 34 +++ .../idea/configuration/nativeLibrariesUtil.kt | 288 ------------------ .../KotlinNativeLibraryNameUtilTest.kt | 10 +- idea/resources/META-INF/gradle-java.xml | 2 +- idea/resources/META-INF/gradle-java.xml.191 | 2 +- 12 files changed, 325 insertions(+), 297 deletions(-) create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesDependencySubstitutor.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesFixer.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryDataService.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryNameUtil.kt delete mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt rename idea/idea-gradle/tests/org/jetbrains/kotlin/idea/configuration/{ => klib}/KotlinNativeLibraryNameUtilTest.kt (78%) diff --git a/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt b/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt index 321f0d454a7..533a363b75c 100644 --- a/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt +++ b/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt @@ -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 diff --git a/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt.192 b/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt.192 index 00124f6a205..cfe68061ce5 100644 --- a/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt.192 +++ b/idea/idea-gradle-native/src/org/jetbrains/kotlin/ide/konan/KotlinNativeABICompatibilityChecker.kt.192 @@ -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 diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt index 4f4f74c0828..c737fb28624 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt @@ -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 diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt index f4c0e9b9022..989af60477e 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt @@ -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 diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesDependencySubstitutor.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesDependencySubstitutor.kt new file mode 100644 index 00000000000..3d565b9d1d0 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesDependencySubstitutor.kt @@ -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): List { + 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>("KLIB_DEPENDENCY_SUBSTITUTION_CACHE") + } +} + +private sealed class DependencySubstitute { + object NoSubstitute : DependencySubstitute() + class YesSubstitute(val substitute: ExternalMultiLibraryDependency) : DependencySubstitute() +} diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesFixer.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesFixer.kt new file mode 100644 index 00000000000..770ad0a298d --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibrariesFixer.kt @@ -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: ". + * [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, ideProject: DataNode) { + 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) { + GradleProjectResolverUtil.linkProjectLibrary(ideProject, libraryData) + } + + private fun fixLibraryDependencyLevel(oldDependencyNode: DataNode) { + 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) + } +} diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryDataService.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryDataService.kt new file mode 100644 index 00000000000..c43fa3e75f3 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryDataService.kt @@ -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() { + override fun getTargetDataKey() = ProjectKeys.LIBRARY + + // See also `com.intellij.openapi.externalSystem.service.project.manage.LibraryDataService.postProcess()` + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + if (projectData == null || modelsProvider is IdeUIModifiableModelsProvider) return + + val librariesModel = modelsProvider.modifiableProjectLibrariesModel + val potentialOrphans = HashMap() + + 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) } + } + } +} diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryNameUtil.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryNameUtil.kt new file mode 100644 index 00000000000..c7c4ca72fc4 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryNameUtil.kt @@ -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? { + 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) +} diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt deleted file mode 100644 index 76605b7ddaa..00000000000 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/nativeLibrariesUtil.kt +++ /dev/null @@ -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() { - override fun getTargetDataKey() = ProjectKeys.LIBRARY - - // See also `com.intellij.openapi.externalSystem.service.project.manage.LibraryDataService.postProcess()` - override fun postProcess( - toImport: MutableCollection>, - projectData: ProjectData?, - project: Project, - modelsProvider: IdeModifiableModelsProvider - ) { - if (projectData == null || modelsProvider is IdeUIModifiableModelsProvider) return - - val librariesModel = modelsProvider.modifiableProjectLibrariesModel - val potentialOrphans = HashMap() - - 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): List { - 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>("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: ". - * [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, ideProject: DataNode) { - 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) { - GradleProjectResolverUtil.linkProjectLibrary(ideProject, libraryData) - } - - private fun fixLibraryDependencyLevel(oldDependencyNode: DataNode) { - 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? { - 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: " diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/configuration/KotlinNativeLibraryNameUtilTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryNameUtilTest.kt similarity index 78% rename from idea/idea-gradle/tests/org/jetbrains/kotlin/idea/configuration/KotlinNativeLibraryNameUtilTest.kt rename to idea/idea-gradle/tests/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryNameUtilTest.kt index a64f086fcda..1a17b0f2bfb 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/configuration/KotlinNativeLibraryNameUtilTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/configuration/klib/KotlinNativeLibraryNameUtilTest.kt @@ -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() { diff --git a/idea/resources/META-INF/gradle-java.xml b/idea/resources/META-INF/gradle-java.xml index 4933fa29e8d..8f920688c46 100644 --- a/idea/resources/META-INF/gradle-java.xml +++ b/idea/resources/META-INF/gradle-java.xml @@ -25,7 +25,7 @@ - + diff --git a/idea/resources/META-INF/gradle-java.xml.191 b/idea/resources/META-INF/gradle-java.xml.191 index c95d93cae39..fc3a3e4f89c 100644 --- a/idea/resources/META-INF/gradle-java.xml.191 +++ b/idea/resources/META-INF/gradle-java.xml.191 @@ -22,7 +22,7 @@ - +