diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/GSKUtil.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/GSKUtil.kt index b75f1b5f430..eff9da5ea00 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/GSKUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/GSKUtil.kt @@ -24,11 +24,14 @@ import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.utils.addToStdlib.cast -fun getKotlinScriptDependencySnippet(artifactName: String): String = - "compile(${getKotlinModuleDependencySnippet(artifactName)})" +fun getGSKCompileDependencySnippet(groupId: String, artifactId: String, compileScope: String = "compile", version: String? = null): String = + if (groupId == KOTLIN_GROUP_ID) + "$compileScope(${getKotlinModuleDependencySnippet(artifactId, version)})" + else + "$compileScope(\"$groupId:$artifactId:$version\")" -fun getKotlinModuleDependencySnippet(artifactName: String): String = - "kotlinModule(\"${artifactName.removePrefix("kotlin-")}\", $GSK_KOTLIN_VERSION_PROPERTY_NAME)" +fun getKotlinModuleDependencySnippet(artifactId: String, version: String? = null): String = + "kotlinModule(\"${artifactId.removePrefix("kotlin-")}\", ${version?.let { "\"$it\"" } ?: GSK_KOTLIN_VERSION_PROPERTY_NAME})" fun KtFile.containsCompileStdLib(): Boolean = findScriptInitializer("dependencies")?.getBlock()?.findCompileStdLib() != null @@ -36,12 +39,30 @@ fun KtFile.containsCompileStdLib(): Boolean = fun KtFile.containsApplyKotlinPlugin(pluginName: String): Boolean = findScriptInitializer("apply")?.getBlock()?.findPlugin(pluginName) != null +fun KtFile.getKotlinStdlibVersion(): String? { + return findScriptInitializer("dependencies")?.getBlock()?.let { + val expression = it.findCompileStdLib()?.valueArguments?.firstOrNull()?.getArgumentExpression() + when (expression) { + is KtCallExpression -> expression.valueArguments[1].text.trim('\"') + is KtStringTemplateExpression -> expression.text?.trim('\"')?.substringAfterLast(":")?.removePrefix("$") + else -> null + } + } +} + fun KtBlockExpression.findCompileStdLib(): KtCallExpression? { return PsiTreeUtil.getChildrenOfType(this, KtCallExpression::class.java)?.find { it.calleeExpression?.text == "compile" && (it.valueArguments.firstOrNull()?.getArgumentExpression()?.isKotlinStdLib() ?: false) } } +private fun KtExpression.isKotlinStdLib(): Boolean = when (this) { + is KtCallExpression -> calleeExpression?.text == "kotlinModule" && + valueArguments.firstOrNull()?.getArgumentExpression()?.text?.startsWith("\"stdlib") ?: false + is KtStringTemplateExpression -> text.startsWith("\"$STDLIB_ARTIFACT_PREFIX") + else -> false +} + fun KtFile.getRepositoriesBlock(): KtBlockExpression? = findScriptInitializer("repositories")?.getBlock() ?: addTopLevelBlock("repositories") @@ -58,13 +79,6 @@ fun KtFile.createApplyBlock(): KtBlockExpression? { fun KtFile.getApplyBlock(): KtBlockExpression? = findScriptInitializer("apply")?.getBlock() ?: createApplyBlock() -private fun KtExpression.isKotlinStdLib(): Boolean = when (this) { - is KtCallExpression -> calleeExpression?.text == "kotlinModule" && - valueArguments.firstOrNull()?.getArgumentExpression()?.text == "\"stdlib\"" - is KtStringTemplateExpression -> text.startsWith("\"org.jetbrains.kotlin:kotlin-stdlib:") - else -> false -} - private fun KtBlockExpression.findPlugin(pluginName: String): KtCallExpression? { return PsiTreeUtil.getChildrenOfType(this, KtCallExpression::class.java)?.find { it.calleeExpression?.text == "plugin" && it.valueArguments.firstOrNull()?.text == "\"$pluginName\"" @@ -215,7 +229,6 @@ fun KtBlockExpression.addDeclarationIfMissing(text: String, first: Boolean = fal private inline fun KtBlockExpression.addStatementIfMissing( text: String, crossinline factory: (String) -> PsiElement): T { - statements.find { it.text == text }?.let { return it as T } @@ -232,5 +245,7 @@ private val PsiElement.psiFactory: KtPsiFactory private val MAVEN_CENTRAL = "mavenCentral()" private val JCENTER = "jcenter()" +private val STDLIB_ARTIFACT_PREFIX = "org.jetbrains.kotlin:kotlin-stdlib" +val KOTLIN_GROUP_ID = "org.jetbrains.kotlin" val GSK_KOTLIN_VERSION_PROPERTY_NAME = "kotlin_version" \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt index 9bd5f20b527..cfa89f083ec 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt @@ -175,7 +175,7 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { private fun KtBlockExpression.addCompileStdlibIfMissing(sdk: Sdk?, version: String): KtCallExpression? = findCompileStdLib() ?: - addExpressionIfMissing(getKotlinScriptDependencySnippet(getStdlibArtifactName(sdk, version))) as? KtCallExpression + addExpressionIfMissing(getGSKCompileDependencySnippet(KOTLIN_GROUP_ID, getStdlibArtifactName(sdk, version))) as? KtCallExpression protected fun addElementsToModuleGroovyFile(file: GroovyFile, version: String): Boolean { val oldText = file.text @@ -281,41 +281,71 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { fun getGroovyApplyPluginDirective(pluginName: String) = "apply plugin: '$pluginName'" fun addKotlinLibraryToModule(module: Module, scope: DependencyScope, libraryDescriptor: ExternalLibraryDescriptor) { - val gradleFile = module.getBuildScriptPsiFile() as? GroovyFile - if (gradleFile != null && canConfigureFile(gradleFile)) { - gradleFile.project.executeWriteCommand("Add Kotlin library") { - val groovyScope = when (scope) { - DependencyScope.COMPILE -> "compile" - DependencyScope.TEST -> if (KotlinPluginUtil.isAndroidGradleModule(module)) { - // TODO we should add testCompile or androidTestCompile - "compile" - } - else { - "testCompile" - } - DependencyScope.RUNTIME -> "runtime" - DependencyScope.PROVIDED -> "compile" - else -> "compile" - } - - val dependencyString = String.format( - "%s \"%s:%s:%s\"", - groovyScope, libraryDescriptor.libraryGroupId, libraryDescriptor.libraryArtifactId, - libraryDescriptor.maxVersion) - - val dependenciesBlock = getDependenciesBlock(gradleFile) - addLastExpressionInBlockIfNeeded(dependencyString, dependenciesBlock) - - CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(gradleFile) - } - - val virtualFile = gradleFile.virtualFile - if (virtualFile != null) { - createConfigureKotlinNotificationCollector(gradleFile.project) - .addMessage(virtualFile.path + " was modified") - .showNotification() - } + val buildScript = module.getBuildScriptPsiFile() ?: return + if (!canConfigureFile(buildScript)) { + return } + + val isAndroidModule = KotlinPluginUtil.isAndroidGradleModule(module) + when (buildScript) { + is GroovyFile -> addKotlinLibraryToModuleGroovyFile(buildScript, scope, libraryDescriptor, isAndroidModule) + is KtFile -> addKotlinLibraryToModuleGSKFile(buildScript, scope, libraryDescriptor, isAndroidModule) + } + + buildScript.virtualFile?.let { + createConfigureKotlinNotificationCollector(buildScript.project) + .addMessage(it.path + " was modified") + .showNotification() + } + } + + fun addKotlinLibraryToModuleGroovyFile( + gradleFile: GroovyFile, + scope: DependencyScope, + libraryDescriptor: ExternalLibraryDescriptor, + isAndroidModule: Boolean) { + gradleFile.project.executeWriteCommand("Add Kotlin library") { + val dependencyString = String.format( + "%s \"%s:%s:%s\"", + scope.toGradleCompileScope(isAndroidModule), + libraryDescriptor.libraryGroupId, + libraryDescriptor.libraryArtifactId, + libraryDescriptor.maxVersion) + + val dependenciesBlock = getDependenciesBlock(gradleFile) + addLastExpressionInBlockIfNeeded(dependencyString, dependenciesBlock) + + CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(gradleFile) + } + } + + fun addKotlinLibraryToModuleGSKFile( + gradleFile: KtFile, + scope: DependencyScope, + libraryDescriptor: ExternalLibraryDescriptor, + isAndroidModule: Boolean) { + gradleFile.project.executeWriteCommand("Add Kotlin library") { + val kotlinLibraryVersion = libraryDescriptor.maxVersion.takeIf { it != GSK_KOTLIN_VERSION_PROPERTY_NAME } + gradleFile.getDependenciesBlock()?.apply { + addExpressionIfMissing( + getGSKCompileDependencySnippet( + libraryDescriptor.libraryGroupId, + libraryDescriptor.libraryArtifactId, + scope.toGradleCompileScope(isAndroidModule), + kotlinLibraryVersion)) + } + + CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(gradleFile) + } + } + + private fun DependencyScope.toGradleCompileScope(isAndroidModule: Boolean) = when (this) { + DependencyScope.COMPILE -> "compile" + // TODO we should add testCompile or androidTestCompile + DependencyScope.TEST -> if (isAndroidModule) "compile" else "testCompile" + DependencyScope.RUNTIME -> "runtime" + DependencyScope.PROVIDED -> "compile" + else -> "compile" } fun changeCoroutineConfiguration(module: Module, coroutineOption: String): PsiElement? { @@ -419,7 +449,15 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { } fun getKotlinStdlibVersion(module: Module): String? { - val gradleFile = module.getBuildScriptPsiFile() as? GroovyFile ?: return null + val gradleFile = module.getBuildScriptPsiFile() ?: return null + return when (gradleFile) { + is GroovyFile -> getKotlinStdlibVersion(gradleFile) + is KtFile -> gradleFile.getKotlinStdlibVersion() + else -> null + } + } + + private fun getKotlinStdlibVersion(gradleFile: GroovyFile): String? { val versionProperty = "\$kotlin_version" val block = getBuildScriptBlock(gradleFile) if (block.text.contains("ext.kotlin_version = ")) { diff --git a/idea/tests/org/jetbrains/kotlin/gradle/KotlinWithGradleConfiguratorTest.kt b/idea/tests/org/jetbrains/kotlin/gradle/KotlinWithGradleConfiguratorTest.kt index d8dac89ed91..8e085abae43 100644 --- a/idea/tests/org/jetbrains/kotlin/gradle/KotlinWithGradleConfiguratorTest.kt +++ b/idea/tests/org/jetbrains/kotlin/gradle/KotlinWithGradleConfiguratorTest.kt @@ -16,6 +16,8 @@ package org.jetbrains.kotlin.gradle +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator import org.jetbrains.kotlin.idea.configuration.changeCoroutineConfiguration @@ -162,4 +164,148 @@ class KotlinWithGradleConfiguratorTest : LightCodeInsightFixtureTestCase() { |} |""".trimMargin("|")) } + + + fun testAddLibrary() { + val buildGradle = myFixture.configureByText("build.gradle", + """ + |dependencies { + | compile "org.jetbrains.kotlin:stdlib-jre8:1.0.0" + |} + |""".trimMargin("|")) as GroovyFile + myFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModuleGroovyFile( + buildGradle, + DependencyScope.COMPILE, + object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", "1.0.0", "1.0.0") { + override fun getLibraryClassesRoots() = emptyList() + }, + false) + } + myFixture.checkResult( + """ + |dependencies { + | compile "org.jetbrains.kotlin:stdlib-jre8:1.0.0" + | compile "org.jetbrains.kotlin:kotlin-reflect:1.0.0" + |} + |""".trimMargin("|")) + } + + fun testAddLibraryGSK() { + val buildGradle = myFixture.configureByText("build.gradle.kts", + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + |} + |""".trimMargin("|")) as KtFile + myFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile( + buildGradle, + DependencyScope.COMPILE, + object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", "1.0.0", "1.0.0") { + override fun getLibraryClassesRoots() = emptyList() + }, + false) + } + myFixture.checkResult( + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + | compile(kotlinModule("reflect", "1.0.0")) + |} + |""".trimMargin("|")) + } + + fun testAddTestLibraryGSK() { + val buildGradle = myFixture.configureByText("build.gradle.kts", + """ + |dependencies { + | compile(kotlinModule("stdlib-jre8")) + |} + |""".trimMargin("|")) as KtFile + myFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile( + buildGradle, + DependencyScope.TEST, + object: ExternalLibraryDescriptor("junit", "junit", "4.12", "4.12") { + override fun getLibraryClassesRoots() = emptyList() + }, + false) + + KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile( + buildGradle, + DependencyScope.TEST, + object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-test", "1.1.2", "1.1.2") { + override fun getLibraryClassesRoots() = emptyList() + }, + false) + } + myFixture.checkResult( + """ + |dependencies { + | compile(kotlinModule("stdlib-jre8")) + | testCompile("junit:junit:4.12") + | testCompile(kotlinModule("test", "1.1.2")) + |} + |""".trimMargin("|")) + } + + fun testAddLibraryGSK_WithKotlinVersion() { + val buildGradle = myFixture.configureByText("build.gradle.kts", + """ + |val kotlin_version: String by extra + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8", kotlin_version)) + |} + |""".trimMargin("|")) as KtFile + myFixture.project.executeWriteCommand("") { + val stdLibVersion = KotlinWithGradleConfigurator.getKotlinStdlibVersion(myFixture.module) + KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile( + buildGradle, + DependencyScope.COMPILE, + object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", stdLibVersion, stdLibVersion) { + override fun getLibraryClassesRoots() = emptyList() + }, + false) + } + myFixture.checkResult( + """ + |val kotlin_version: String by extra + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8", kotlin_version)) + | compile(kotlinModule("reflect", kotlin_version)) + |} + |""".trimMargin("|")) + } + + fun testAddNonKotlinLibraryGSK() { + val buildGradle = myFixture.configureByText("build.gradle.kts", + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + |} + |""".trimMargin("|")) as KtFile + myFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModuleGSKFile( + buildGradle, + DependencyScope.COMPILE, + object: ExternalLibraryDescriptor("org.a.b", "lib", "1.0.0", "1.0.0") { + override fun getLibraryClassesRoots() = emptyList() + }, + false) + } + myFixture.checkResult( + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + | compile("org.a.b:lib:1.0.0") + |} + |""".trimMargin("|")) + } }