diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/facet/KotlinVersionInfoProvider.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/facet/KotlinVersionInfoProvider.kt index 668e7f1b74d..53313d27428 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/facet/KotlinVersionInfoProvider.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/facet/KotlinVersionInfoProvider.kt @@ -21,6 +21,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.roots.ModuleRootModel import com.intellij.util.text.VersionComparatorUtil import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.config.TargetPlatformKind @@ -71,4 +72,10 @@ fun getDefaultLanguageLevel( libVersion.startsWith("1.0") -> LanguageVersion.KOTLIN_1_0 else -> LanguageVersion.KOTLIN_1_1 } -} \ No newline at end of file +} + +fun getRuntimeLibraryVersion(module: Module): String? { + val targetPlatform = KotlinFacetSettingsProvider.getInstance(module.project).getSettings(module).versionInfo.targetPlatformKind + val versions = getRuntimeLibraryVersions(module, null, targetPlatform ?: TargetPlatformKind.Jvm[JvmTarget.JVM_1_8]) + return versions.toSet().singleOrNull() +} diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt index 4e55c7288f8..242e6342ee6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt @@ -274,18 +274,33 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { return kotlinBlock.parent } - fun changeLanguageVersion(module: Module, languageVersion: String, forTests: Boolean): PsiElement? { + fun changeLanguageVersion(module: Module, languageVersion: String?, apiVersion: String? = null, forTests: Boolean): PsiElement? { return changeBuildGradle(module) { gradleFile -> - changeLanguageVersion(gradleFile, languageVersion, forTests) + var result: PsiElement? = null + if (languageVersion != null) { + result = changeLanguageVersion(gradleFile, languageVersion, forTests) + } + if (apiVersion != null) { + result = changeApiVersion(gradleFile, apiVersion, forTests) + } + result } } fun changeLanguageVersion(gradleFile: GroovyFile, languageVersion: String, forTests: Boolean): PsiElement? { - val snippet = "languageVersion = \"$languageVersion\"" + return changeVersion(gradleFile, languageVersion, forTests, "languageVersion") + } + + fun changeApiVersion(gradleFile: GroovyFile, apiVersion: String, forTests: Boolean): PsiElement? { + return changeVersion(gradleFile, apiVersion, forTests, "apiVersion") + } + + private fun changeVersion(gradleFile: GroovyFile, version: String, forTests: Boolean, versionParameter: String): PsiElement? { + val snippet = "$versionParameter = \"$version\"" val kotlinBlock = getBlockOrCreate(gradleFile, if (forTests) "compileTestKotlin" else "compileKotlin") val experimentalBlock = getBlockOrCreate(kotlinBlock, "kotlinOptions") addOrReplaceExpression(experimentalBlock, snippet) { stmt -> - (stmt as? GrAssignmentExpression)?.lValue?.text == "languageVersion" + (stmt as? GrAssignmentExpression)?.lValue?.text == versionParameter } return kotlinBlock.parent } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCoroutineSupportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCoroutineSupportFix.kt index ecf0edab328..ed90f73646d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCoroutineSupportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeCoroutineSupportFix.kt @@ -23,15 +23,22 @@ import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.openapi.roots.ex.ProjectRootManagerEx +import com.intellij.openapi.ui.Messages import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.ApiVersion import org.jetbrains.kotlin.config.CoroutineSupport import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.KotlinPluginUtil import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator import org.jetbrains.kotlin.idea.facet.KotlinFacet +import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion +import org.jetbrains.kotlin.idea.util.projectStructure.allModules +import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary +import org.jetbrains.kotlin.idea.versions.updateLibraries import org.jetbrains.kotlin.psi.KtFile sealed class ChangeCoroutineSupportFix( @@ -43,7 +50,19 @@ sealed class ChangeCoroutineSupportFix( override fun invoke(project: Project, editor: Editor?, file: KtFile) { val module = ModuleUtilCore.findModuleForPsiElement(file) ?: return + + val runtimeUpdateRequired = coroutineSupport != CoroutineSupport.DISABLED && + (getRuntimeLibraryVersion(module)?.startsWith("1.0") ?: false) + if (KotlinPluginUtil.isGradleModule(module)) { + if (runtimeUpdateRequired) { + Messages.showErrorDialog(project, + "Coroutines support requires version 1.1 or later of the Kotlin runtime library. " + + "Please update the version in your build script.", + super.getText()) + return + } + val element = KotlinWithGradleConfigurator.changeCoroutineConfiguration(module, coroutineSupport.compilerArgument) element?.let { OpenFileDescriptor(project, it.containingFile.virtualFile, it.textRange.startOffset).navigate(true) @@ -51,9 +70,24 @@ sealed class ChangeCoroutineSupportFix( return } + if (runtimeUpdateRequired) { + val library = findKotlinRuntimeLibrary(module) + if (library != null) { + val rc = Messages.showOkCancelDialog(project, + "Coroutines support requires version 1.1 or later of the Kotlin runtime library. " + + "Would you like to update the runtime library in your project?", + super.getText(), + Messages.getQuestionIcon()) + if (rc != Messages.OK) return + updateLibraries(project, listOf(library)) + } + } + val facetSettings = KotlinFacet.get(module)?.configuration?.settings ?: return ModuleRootModificationUtil.updateModel(module) { facetSettings.compilerInfo.coroutineSupport = coroutineSupport + facetSettings.versionInfo.apiLevel = LanguageVersion.KOTLIN_1_1 + facetSettings.versionInfo.languageLevel = LanguageVersion.KOTLIN_1_1 } } } @@ -62,6 +96,10 @@ sealed class ChangeCoroutineSupportFix( override fun getText() = "${super.getText()} in the project" override fun invoke(project: Project, editor: Editor?, file: KtFile) { + if (coroutineSupport != CoroutineSupport.DISABLED) { + if (!checkUpdateRuntime(project, LanguageFeature.Coroutines.sinceApiVersion)) return + } + with (KotlinCommonCompilerArgumentsHolder.getInstance(project).settings) { coroutinesEnable = coroutineSupport == CoroutineSupport.ENABLED coroutinesWarn = coroutineSupport == CoroutineSupport.ENABLED_WITH_WARNING @@ -69,6 +107,7 @@ sealed class ChangeCoroutineSupportFix( } ProjectRootManagerEx.getInstanceEx(project).makeRootsChange({}, false, true) } + } override fun getFamilyName() = "Enable/Disable coroutine support" diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt index 4bd3f10be25..90f1feae298 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/EnableUnsupportedFeatureFix.kt @@ -23,7 +23,10 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.openapi.roots.ex.ProjectRootManagerEx +import com.intellij.openapi.ui.Messages import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersion import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors @@ -31,47 +34,97 @@ import org.jetbrains.kotlin.idea.KotlinPluginUtil import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator import org.jetbrains.kotlin.idea.facet.KotlinFacet +import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion +import org.jetbrains.kotlin.idea.util.projectStructure.allModules +import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary +import org.jetbrains.kotlin.idea.versions.updateLibraries import org.jetbrains.kotlin.psi.KtFile sealed class EnableUnsupportedFeatureFix( element: PsiElement, - protected val targetVersion: LanguageVersion + protected val feature: LanguageFeature, + protected val apiVersionOnly: Boolean ) : KotlinQuickFixAction(element) { - class InModule(element: PsiElement, targetVersion: LanguageVersion) : EnableUnsupportedFeatureFix(element, targetVersion) { - override fun getFamilyName() = "Increase module language level" + class InModule(element: PsiElement, feature: LanguageFeature, apiVersionOnly: Boolean) : EnableUnsupportedFeatureFix(element, feature, apiVersionOnly) { + override fun getFamilyName() = "Increase module " + if (apiVersionOnly) "API version" else "language version" - override fun getText() = "Set module language level to ${targetVersion.versionString}" + override fun getText() = if (apiVersionOnly) + "Set module API version to ${feature.sinceApiVersion.versionString}" + else + "Set module language version to ${feature.sinceVersion!!.versionString}" override fun invoke(project: Project, editor: Editor?, file: KtFile) { val module = ModuleUtilCore.findModuleForPsiElement(file) ?: return + val targetVersion = feature.sinceVersion!! + + val runtimeUpdateRequired = getRuntimeLibraryVersion(module)?.let { ApiVersion.parse(it) }?.let { runtimeVersion -> + runtimeVersion < feature.sinceApiVersion + } ?: false + + val facetSettings = KotlinFacet.get(module)?.configuration?.settings ?: return + val targetApiLevel = facetSettings.versionInfo.apiLevel?.let { apiLevel -> + if (ApiVersion.createByLanguageVersion(apiLevel) < feature.sinceApiVersion) + feature.sinceApiVersion.versionString + else + null + } + if (KotlinPluginUtil.isGradleModule(module)) { + if (runtimeUpdateRequired) { + Messages.showErrorDialog(project, + "This language feature requires version ${feature.sinceApiVersion} or later of the Kotlin runtime library. " + + "Please update the version in your build script.", + "Update Language Level") + return + } + val forTests = ModuleRootManager.getInstance(module).fileIndex.isInTestSourceContent(file.virtualFile) - val element = KotlinWithGradleConfigurator.changeLanguageVersion(module, targetVersion.versionString, forTests) + val element = KotlinWithGradleConfigurator.changeLanguageVersion(module, + if (apiVersionOnly) null else targetVersion.versionString, + targetApiLevel, forTests) + element?.let { OpenFileDescriptor(project, it.containingFile.virtualFile, it.textRange.startOffset).navigate(true) } return } - val facetSettings = KotlinFacet.get(module)?.configuration?.settings ?: return ModuleRootModificationUtil.updateModel(module) { with(facetSettings.versionInfo) { - languageLevel = targetVersion - apiLevel = targetVersion + if (!apiVersionOnly) { + languageLevel = targetVersion + } + if (targetApiLevel != null) { + apiLevel = LanguageVersion.fromVersionString(targetApiLevel) + } } } } } - class InProject(element: PsiElement, targetVersion: LanguageVersion) : EnableUnsupportedFeatureFix(element, targetVersion) { - override fun getFamilyName() = "Increase project language level" + class InProject(element: PsiElement, feature: LanguageFeature, apiVersionOnly: Boolean) + : EnableUnsupportedFeatureFix(element, feature, apiVersionOnly) + { + override fun getFamilyName() = "Increase project " + if (apiVersionOnly) "API version" else "language version" - override fun getText() = "Set project language level to ${targetVersion.versionString}" + override fun getText() = if (apiVersionOnly) + "Set project API version to ${feature.sinceApiVersion.versionString}" + else + "Set project language version to ${feature.sinceVersion!!.versionString}" override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val targetVersion = feature.sinceVersion!! + with(KotlinCommonCompilerArgumentsHolder.getInstance(project).settings) { - languageVersion = targetVersion.versionString - apiVersion = targetVersion.versionString + val parsedApiVersion = ApiVersion.parse(apiVersion) + if (parsedApiVersion != null && feature.sinceApiVersion > parsedApiVersion) { + if (!checkUpdateRuntime(project, feature.sinceApiVersion)) return + apiVersion = feature.sinceApiVersion.versionString + } + + if (!apiVersionOnly) { + languageVersion = targetVersion.versionString + } } ProjectRootManagerEx.getInstanceEx(project).makeRootsChange({}, false, true) } @@ -79,14 +132,38 @@ sealed class EnableUnsupportedFeatureFix( companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): EnableUnsupportedFeatureFix? { - val targetVersion = Errors.UNSUPPORTED_FEATURE.cast(diagnostic).a.first.sinceVersion ?: return null + val (feature, languageFeatureSettings) = Errors.UNSUPPORTED_FEATURE.cast(diagnostic).a + + val sinceVersion = feature.sinceVersion ?: return null + val apiVersionOnly = sinceVersion <= languageFeatureSettings.languageVersion && + feature.sinceApiVersion > languageFeatureSettings.apiVersion + val module = ModuleUtilCore.findModuleForPsiElement(diagnostic.psiElement) ?: return null if (KotlinPluginUtil.isMavenModule(module)) return null if (!KotlinPluginUtil.isGradleModule(module)) { val facetSettings = KotlinFacet.get(module)?.configuration?.settings - if (facetSettings == null || facetSettings.useProjectSettings) return InProject(diagnostic.psiElement, targetVersion) + if (facetSettings == null || facetSettings.useProjectSettings) return InProject(diagnostic.psiElement, feature, apiVersionOnly) } - return InModule(diagnostic.psiElement, targetVersion) + return InModule(diagnostic.psiElement, feature, apiVersionOnly) } } } + +fun checkUpdateRuntime(project: Project, requiredVersion: ApiVersion): Boolean { + val modulesWithOutdatedRuntime = project.allModules().filter { module -> + val parsedModuleRuntimeVersion = getRuntimeLibraryVersion(module)?.let { ApiVersion.parse(it) } + parsedModuleRuntimeVersion != null && parsedModuleRuntimeVersion < requiredVersion + } + if (modulesWithOutdatedRuntime.isNotEmpty()) { + val rc = Messages.showOkCancelDialog(project, + "This language feature requires version $requiredVersion or later of the Kotlin runtime library. " + + "Would you like to update the runtime library in your project?", + "Update runtime library", + Messages.getQuestionIcon()) + if (rc != Messages.OK) return false + + val librariesToUpdate = modulesWithOutdatedRuntime.mapNotNull(::findKotlinRuntimeLibrary) + updateLibraries(project, librariesToUpdate) + } + return true +} diff --git a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt index dbcc4edb0b5..cf2c1b83e31 100644 --- a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt @@ -50,6 +50,7 @@ import org.jetbrains.kotlin.idea.configuration.createConfigureKotlinNotification import org.jetbrains.kotlin.idea.configuration.getConfiguratorByName import org.jetbrains.kotlin.idea.framework.* import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.idea.util.projectStructure.allModules import org.jetbrains.kotlin.idea.util.runWithAlternativeResolveEnabled import org.jetbrains.kotlin.idea.vfilefinder.JsVirtualFileFinderFactory import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion @@ -70,6 +71,20 @@ fun getLibraryRootsWithAbiIncompatibleForKotlinJs(module: Module): Collection) { + if (project.allModules().any { module -> KotlinPluginUtil.isMavenModule(module) }) { + Messages.showMessageDialog(project, "Automatic library version update for Maven projects is currently unsupported. Please update your pom.xml manually.", + "Update Kotlin Runtime Library", + Messages.getErrorIcon()) + return + } + + if (project.allModules().any { module -> KotlinPluginUtil.isGradleModule(module) }) { + Messages.showMessageDialog(project, "Automatic library version update for Gradle projects is currently unsupported. Please update your build.gradle manually.", + "Update Kotlin Runtime Library", + Messages.getErrorIcon()) + return + } + ApplicationManager.getApplication().invokeLater { val kJvmConfigurator = getConfiguratorByName(KotlinJavaModuleConfigurator.NAME) as KotlinJavaModuleConfigurator? ?: error("Configurator with given name doesn't exists: " + KotlinJavaModuleConfigurator.NAME) diff --git a/idea/src/org/jetbrains/kotlin/idea/versions/OutdatedKotlinRuntimeChecker.kt b/idea/src/org/jetbrains/kotlin/idea/versions/OutdatedKotlinRuntimeChecker.kt index 64152f545a5..0d3a0baf10a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/versions/OutdatedKotlinRuntimeChecker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/versions/OutdatedKotlinRuntimeChecker.kt @@ -24,6 +24,8 @@ import com.intellij.notification.Notifications import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.libraries.Library import com.intellij.util.PathUtil.getLocalFile import com.intellij.util.text.VersionComparatorUtil @@ -47,25 +49,35 @@ fun findOutdatedKotlinLibraries(project: Project): List { val outdatedLibraries = arrayListOf() for ((library, modules) in findAllUsedLibraries(project).entrySet()) { - val libraryVersionProperties = - getLibraryProperties(JavaRuntimePresentationProvider.getInstance(), library) ?: - getLibraryProperties(JSLibraryStdPresentationProvider.getInstance(), library) ?: - continue - - val libraryVersion = libraryVersionProperties.versionString - - val runtimeVersion = bundledRuntimeVersion() - - val isOutdated = isRuntimeOutdated(libraryVersion, runtimeVersion) - - if (isOutdated) { - outdatedLibraries.add(VersionedLibrary(library, libraryVersion, modules)) + getOutdatedRuntimeLibraryVersion(library)?.let { version -> + outdatedLibraries.add(VersionedLibrary(library, version, modules)) } } return outdatedLibraries } +fun getOutdatedRuntimeLibraryVersion(library: Library): String? { + val libraryVersionProperties = getKotlinLibraryVersionProperties(library) ?: return null + + val libraryVersion = libraryVersionProperties.versionString + + val runtimeVersion = bundledRuntimeVersion() + + return if (isRuntimeOutdated(libraryVersion, runtimeVersion)) libraryVersion else null +} + +private fun getKotlinLibraryVersionProperties(library: Library) = + getLibraryProperties(JavaRuntimePresentationProvider.getInstance(), library) ?: + getLibraryProperties(JSLibraryStdPresentationProvider.getInstance(), library) + +fun findKotlinRuntimeLibrary(module: Module): Library? { + val orderEntries = ModuleRootManager.getInstance(module).orderEntries.filterIsInstance() + return orderEntries.asSequence() + .mapNotNull { it.library } + .firstOrNull { getKotlinLibraryVersionProperties(it) != null } +} + fun collectModulesWithOutdatedRuntime(libraries: List): List = libraries.flatMap { it.usedInModules }