From b4c88651f6e2a76711d730be3c2b2b13208a7b2f Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 26 Jul 2018 16:55:50 +0300 Subject: [PATCH] Migration detector for external systems (KT-23401) --- .../KotlinExternalSystemSyncListener.kt | 2 + idea/src/META-INF/plugin.xml | 3 + idea/src/META-INF/plugin.xml.172 | 3 + idea/src/META-INF/plugin.xml.173 | 3 + idea/src/META-INF/plugin.xml.182 | 3 + idea/src/META-INF/plugin.xml.as31 | 3 + idea/src/META-INF/plugin.xml.as32 | 3 + idea/src/META-INF/plugin.xml.as33 | 3 + .../KotlinMigrationProjectComponent.kt | 177 ++++++++++++++++++ .../idea/versions/KotlinRuntimeLibraryUtil.kt | 3 +- .../versions/KotlinRuntimeLibraryUtil.kt.172 | 3 +- 11 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt index 87d24190d0b..4a991c1835a 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/configuration/KotlinExternalSystemSyncListener.kt @@ -25,10 +25,12 @@ import org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComp class KotlinExternalSystemSyncListener : ExternalSystemTaskNotificationListenerAdapter() { override fun onStart(id: ExternalSystemTaskId, workingDir: String) { val project = id.findResolvedProject() ?: return + KotlinMigrationProjectComponent.getInstance(project).onImportAboutToStart() KotlinConfigurationCheckerComponent.getInstance(project).syncStarted() } override fun onEnd(id: ExternalSystemTaskId) { + // At this point changes might be still not applied to project structure yet. val project = id.findResolvedProject() ?: return KotlinConfigurationCheckerComponent.getInstance(project).syncDone() } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index d7f36c053d6..6b971be67b5 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -64,6 +64,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index 48db081be0c..6d6d2dc52b0 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -64,6 +64,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index f7feb282dc7..b3af22a346e 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -64,6 +64,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index 77fdde98ce1..6410db0b84e 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -65,6 +65,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 49bdc914a4a..6e59334ba17 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -64,6 +64,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index 03e6c7f9005..36ac4affcf9 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -64,6 +64,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + diff --git a/idea/src/META-INF/plugin.xml.as33 b/idea/src/META-INF/plugin.xml.as33 index bdae3986f3e..ec95911bd7f 100644 --- a/idea/src/META-INF/plugin.xml.as33 +++ b/idea/src/META-INF/plugin.xml.as33 @@ -63,6 +63,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider + + org.jetbrains.kotlin.idea.configuration.KotlinMigrationProjectComponent + diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt new file mode 100644 index 00000000000..6c2de283eab --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinMigrationProjectComponent.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.application.ApplicationManager +import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataImportListener +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.roots.libraries.Library +import com.intellij.util.CommonProcessors +import com.intellij.util.text.VersionComparatorUtil +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl +import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID +import org.jetbrains.kotlin.idea.framework.MAVEN_SYSTEM_ID +import org.jetbrains.kotlin.idea.project.languageVersionSettings +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.idea.versions.LibInfo + +class KotlinMigrationProjectComponent(val project: Project) { + private var old: MigrationState? = null + private var new: MigrationState? = null + + init { + val connection = project.messageBus.connect() + connection.subscribe(ProjectDataImportListener.TOPIC, ProjectDataImportListener { + KotlinMigrationProjectComponent.getInstance(project).onImportFinished() + }) + } + + @Synchronized + fun onImportAboutToStart() { + old = MigrationState.build(project) + } + + @Synchronized + fun onImportFinished() { + new = MigrationState.build(project) + + val migrationInfo = prepareMigrationInfo(old, new) ?: return + + if (ApplicationManager.getApplication().isUnitTestMode) { + return + } + + ApplicationManager.getApplication().invokeLater { + // Notify + } + } + + companion object { + fun getInstance(project: Project): KotlinMigrationProjectComponent = + project.getComponent(KotlinMigrationProjectComponent::class.java)!! + + private fun prepareMigrationInfo(old: MigrationState?, new: MigrationState?): MigrationInfo? { + if (old == null || new == null) { + return null + } + + val oldLibraryVersion = old.stdlibInfo?.version + val newLibraryVersion = new.stdlibInfo?.version + + if (oldLibraryVersion == null || newLibraryVersion == null) { + return null + } + + if (VersionComparatorUtil.COMPARATOR.compare(newLibraryVersion, oldLibraryVersion) > 0 || + old.apiVersion < new.apiVersion || old.languageVersion < new.languageVersion + ) { + return MigrationInfo( + oldLibraryVersion, newLibraryVersion, + old.apiVersion, new.apiVersion, + old.languageVersion, new.languageVersion + ) + } + + return null + } + } +} + +private class MigrationState( + var stdlibInfo: LibInfo?, + var apiVersion: ApiVersion, + var languageVersion: LanguageVersion +) { + companion object { + fun build(project: Project): MigrationState { + val libraries = maxKotlinLibVersion(project) + val languageVersionSettings = collectMaxCompilerSettings(project) + return MigrationState(libraries, languageVersionSettings.apiVersion, languageVersionSettings.languageVersion) + } + } +} + +internal data class MigrationInfo( + val oldStdlibVersion: String, + val newStdlibVersion: String, + val oldApiVersion: ApiVersion, + val newApiVersion: ApiVersion, + val oldLanguageVersion: LanguageVersion, + val newLanguageVersion: LanguageVersion +) + +private const val KOTLIN_GROUP_ID = "org.jetbrains.kotlin" + +private fun maxKotlinLibVersion(project: Project): LibInfo? { + return runReadAction { + var maxStdlibInfo: LibInfo? = null + + val allLibProcessor = CommonProcessors.CollectUniquesProcessor() + ProjectRootManager.getInstance(project).orderEntries().forEachLibrary(allLibProcessor) + + for (library in allLibProcessor.results) { + if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, GRADLE_SYSTEM_ID) && + !ExternalSystemApiUtil.isExternalSystemLibrary(library, MAVEN_SYSTEM_ID) + ) { + continue + } + + if (library.name?.contains(" $KOTLIN_GROUP_ID:kotlin-stdlib") != true) { + continue + } + + val libName = library.name ?: continue + + val version = libName.substringAfterLastNullable(":") ?: continue + val artifactId = libName.substringBeforeLastNullable(":")?.substringAfterLastNullable(":") ?: continue + + if (version.isBlank() || artifactId.isBlank()) continue + + if (maxStdlibInfo == null || VersionComparatorUtil.COMPARATOR.compare(version, maxStdlibInfo.version) > 0) { + maxStdlibInfo = LibInfo(KOTLIN_GROUP_ID, artifactId, version) + } + } + + maxStdlibInfo + } +} + +private fun collectMaxCompilerSettings(project: Project): LanguageVersionSettings { + return runReadAction { + var maxApiVersion: ApiVersion? = null + var maxLanguageVersion: LanguageVersion? = null + + for (module in ModuleManager.getInstance(project).modules) { + val languageVersionSettings = module.languageVersionSettings + + if (maxApiVersion == null || languageVersionSettings.apiVersion > maxApiVersion) { + maxApiVersion = languageVersionSettings.apiVersion + } + + if (maxLanguageVersion == null || languageVersionSettings.languageVersion > maxLanguageVersion) { + maxLanguageVersion = languageVersionSettings.languageVersion + } + } + + LanguageVersionSettingsImpl(maxLanguageVersion ?: LanguageVersion.LATEST_STABLE, maxApiVersion ?: ApiVersion.LATEST_STABLE) + } +} + +fun String.substringBeforeLastNullable(delimiter: String, missingDelimiterValue: String? = null): String? { + val index = lastIndexOf(delimiter) + return if (index == -1) missingDelimiterValue else substring(0, index) +} + +fun String.substringAfterLastNullable(delimiter: String, missingDelimiterValue: String? = null): String? { + val index = lastIndexOf(delimiter) + return if (index == -1) missingDelimiterValue else substring(index + 1, length) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt index 26cff443aee..b469795d1f8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt @@ -280,7 +280,8 @@ val LOG = Logger.getInstance("org.jetbrains.kotlin.idea.versions.KotlinRuntimeLi data class LibInfo( val groupId: String, - val name: String + val name: String, + val version: String = "0.0.0" ) data class DeprecatedLibInfo( diff --git a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt.172 b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt.172 index 4ecba8f836f..f23a1612d54 100644 --- a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt.172 +++ b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt.172 @@ -280,7 +280,8 @@ val LOG = Logger.getInstance("org.jetbrains.kotlin.idea.versions.KotlinRuntimeLi data class LibInfo( val groupId: String, - val name: String + val name: String, + val version: String = "0.0.0" ) data class DeprecatedLibInfo(