diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleModuleConfigurator.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleModuleConfigurator.kt index f3cb53131ad..963d276708a 100644 --- a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleModuleConfigurator.kt +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleModuleConfigurator.kt @@ -17,17 +17,18 @@ package org.jetbrains.kotlin.android.configure import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.projectRoots.JavaSdk import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ModuleRootManager import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.KotlinPluginUtil import org.jetbrains.kotlin.idea.configuration.KotlinWithGradleConfigurator -import org.jetbrains.kotlin.idea.versions.* -import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.idea.versions.MAVEN_STDLIB_ID_JRE7 +import org.jetbrains.kotlin.idea.versions.hasJreSpecificRuntime import org.jetbrains.kotlin.resolve.TargetPlatform import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform -import org.jetbrains.plugins.groovy.lang.psi.GroovyFile class KotlinAndroidGradleModuleConfigurator internal constructor() : KotlinWithGradleConfigurator() { @@ -42,25 +43,21 @@ class KotlinAndroidGradleModuleConfigurator internal constructor() : KotlinWithG override val kotlinPluginName: String = KOTLIN_ANDROID override fun addElementsToFile(file: PsiFile, isTopLevelProjectFile: Boolean, version: String): Boolean { - if (file is GroovyFile) { - if (isTopLevelProjectFile) { - return addElementsToProjectGroovyFile(file, version) - } - else { - return addElementsToModuleGroovyFile(file, version) - } - } + val manipulator = getManipulator(file) + val sdk = ModuleUtil.findModuleForPsiElement(file)?.let { ModuleRootManager.getInstance(it).sdk } + val jvmTarget = getJvmTarget(sdk, version) - if (file is KtFile) { - if (isTopLevelProjectFile) { - return addElementsToProjectGSKFile(file, version) - } - else { - return addElementsToModuleGSKFile(file, version) - } + return if (isTopLevelProjectFile) { + manipulator.configureProjectBuildScript(version) + } + else { + manipulator.configureModuleBuildScript( + kotlinPluginName, + getStdlibArtifactName(sdk, version), + version, + jvmTarget + ) } - - return false } override fun getStdlibArtifactName(sdk: Sdk?, version: String): String { diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt index 7156c64ea27..399adcd7e7d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinInProjectUtils.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.DependencyScope import com.intellij.openapi.util.Computable import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.GlobalSearchScope @@ -65,6 +66,24 @@ val EAP_12_REPOSITORY = RepositoryDescription( "https://bintray.com/kotlin/kotlin-eap-1.2/kotlin/", isSnapshot = false) +val MAVEN_CENTRAL = "mavenCentral()" + +val JCENTER = "jcenter()" + +val KOTLIN_GROUP_ID = "org.jetbrains.kotlin" + +fun isRepositoryConfigured(repositoriesBlockText: String): Boolean = + repositoriesBlockText.contains(MAVEN_CENTRAL) || repositoriesBlockText.contains(JCENTER) + +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 RepositoryDescription.toGroovyRepositorySnippet() = "maven {\nurl '$url'\n}" fun RepositoryDescription.toKotlinRepositorySnippet() = "maven {\nsetUrl(\"$url\")\n}" @@ -240,4 +259,4 @@ fun isEap(version: String): Boolean { fun useEapRepository(minorKotlinVersion: Int, version: String): Boolean { return Regex("1\\.$minorKotlinVersion(\\.\\d)?-[A-Za-z][A-Za-z0-9-]*").matches(version) && !version.startsWith("1.$minorKotlinVersion.0-dev") -} +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/GSKUtil.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/GSKUtil.kt deleted file mode 100644 index eff9da5ea00..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/GSKUtil.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.configuration - -import com.intellij.psi.PsiElement -import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType -import org.jetbrains.kotlin.resolve.ImportPath -import org.jetbrains.kotlin.utils.addToStdlib.cast - - -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(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 - -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") - -fun KtFile.getDependenciesBlock(): KtBlockExpression? = - findScriptInitializer("dependencies")?.getBlock() ?: addTopLevelBlock("dependencies") - -fun KtFile.createApplyBlock(): KtBlockExpression? { - val apply = psiFactory.createScriptInitializer("apply {\n}") - val plugins = findScriptInitializer("plugins") - val addedElement = plugins?.addSibling(apply) ?: addToScriptBlock(apply) - addedElement?.addNewLinesIfNeeded() - return (addedElement as? KtScriptInitializer)?.getBlock() -} - -fun KtFile.getApplyBlock(): KtBlockExpression? = findScriptInitializer("apply")?.getBlock() ?: createApplyBlock() - -private fun KtBlockExpression.findPlugin(pluginName: String): KtCallExpression? { - return PsiTreeUtil.getChildrenOfType(this, KtCallExpression::class.java)?.find { - it.calleeExpression?.text == "plugin" && it.valueArguments.firstOrNull()?.text == "\"$pluginName\"" - } -} - -fun KtBlockExpression.createPluginIfMissing(pluginName: String): KtCallExpression? = - findPlugin(pluginName) ?: addExpressionIfMissing("plugin(\"$pluginName\")") as? KtCallExpression - -fun changeCoroutineConfiguration(buildScriptFile: KtFile, coroutineOption: String): PsiElement? { - val snippet = "experimental.coroutines = Coroutines.${coroutineOption.toUpperCase()}" - val kotlinBlock = buildScriptFile.findScriptInitializer("kotlin")?.getBlock() ?: - buildScriptFile.addTopLevelBlock("kotlin") ?: return null - buildScriptFile.addImportIfMissing("org.jetbrains.kotlin.gradle.dsl.Coroutines") - val statement = kotlinBlock.statements.find { it.text.startsWith("experimental.coroutines") } - return if (statement != null) { - statement.replace(buildScriptFile.psiFactory.createExpression(snippet)) - } - else { - kotlinBlock.add(buildScriptFile.psiFactory.createExpression(snippet)).apply { addNewLinesIfNeeded() } - } -} - -fun KtFile.changeKotlinTaskParameter(parameterName: String, parameterValue: String, forTests: Boolean): PsiElement? { - val snippet = "$parameterName = \"$parameterValue\"" - val taskName = if (forTests) "compileTestKotlin" else "compileKotlin" - val optionsBlock = findScriptInitializer("$taskName.kotlinOptions")?.getBlock() - return if (optionsBlock != null) { - val assignment = optionsBlock.statements.find { - (it as? KtBinaryExpression)?.left?.text == parameterName - } - if (assignment != null) { - assignment.replace(psiFactory.createExpression(snippet)) - } - else { - optionsBlock.addExpressionIfMissing(snippet) - } - } - else { - addImportIfMissing("org.jetbrains.kotlin.gradle.tasks.KotlinCompile") - script?.blockExpression?.addDeclarationIfMissing("val $taskName: KotlinCompile by tasks") - addTopLevelBlock("$taskName.kotlinOptions")?.addExpressionIfMissing(snippet) - } -} - -fun KtFile.getBuildScriptBlock(): KtBlockExpression? = - findScriptInitializer("buildscript")?.getBlock() ?: addTopLevelBlock("buildscript", true) - -fun KtBlockExpression.getRepositoriesBlock(): KtBlockExpression? = - findBlock("repositories") ?: addBlock("repositories") - -fun KtBlockExpression.getDependenciesBlock(): KtBlockExpression? = - findBlock("dependencies") ?: addBlock("dependencies") - -fun KtBlockExpression.addRepositoryIfMissing(version: String): KtCallExpression? { - val repository = getRepositoryForVersion(version) - val snippet = when { - repository != null -> repository.toKotlinRepositorySnippet() - !isRepositoryConfigured() -> MAVEN_CENTRAL - else -> return null - } - - return addExpressionIfMissing(snippet) as? KtCallExpression -} - -private fun KtBlockExpression.isRepositoryConfigured(): Boolean { - return text.contains(MAVEN_CENTRAL) || text.contains(JCENTER) -} - -fun KtBlockExpression.addPluginToClassPathIfMissing(): KtCallExpression? = - addExpressionIfMissing("classpath(${getKotlinModuleDependencySnippet("gradle-plugin")})") as? KtCallExpression - -private fun KtFile.findScriptInitializer(startsWith: String): KtScriptInitializer? = - PsiTreeUtil.findChildrenOfType(this, KtScriptInitializer::class.java).find { it.text.startsWith(startsWith) } - -private fun KtBlockExpression.findBlock(name: String): KtBlockExpression? { - return getChildrenOfType().find { - it.calleeExpression?.text == name && - it.valueArguments.singleOrNull()?.getArgumentExpression() is KtLambdaExpression - }?.getBlock() -} - -private fun KtScriptInitializer.getBlock(): KtBlockExpression? = - PsiTreeUtil.findChildOfType(this, KtCallExpression::class.java)?.getBlock() - -private fun KtCallExpression.getBlock(): KtBlockExpression? = - (valueArguments.singleOrNull()?.getArgumentExpression() as? KtLambdaExpression)?.bodyExpression - -private fun KtBlockExpression.addBlock(name: String): KtBlockExpression? { - return add(psiFactory.createExpression("$name {\n}")) - ?.apply { addNewLinesIfNeeded() } - ?.cast() - ?.getBlock() -} - -private fun KtFile.addTopLevelBlock(name: String, first: Boolean = false): KtBlockExpression? { - val scriptInitializer = psiFactory.createScriptInitializer("$name {\n}") - val addedElement = addToScriptBlock(scriptInitializer, first) as? KtScriptInitializer - addedElement?.addNewLinesIfNeeded() - return addedElement?.getBlock() -} - -private fun PsiElement.addSibling(element: PsiElement): PsiElement = parent.addAfter(element, this) - -private fun PsiElement.addNewLineBefore(lineBreaks: Int = 1) { - parent.addBefore(psiFactory.createNewLine(lineBreaks), this) -} - -private fun PsiElement.addNewLineAfter(lineBreaks: Int = 1) { - parent.addAfter(psiFactory.createNewLine(lineBreaks), this) -} - -private fun PsiElement.addNewLinesIfNeeded(lineBreaks: Int = 1) { - if (prevSibling != null && prevSibling.text.isNotBlank()) { - addNewLineBefore(lineBreaks) - } - - if (nextSibling != null && nextSibling.text.isNotBlank()) { - addNewLineAfter(lineBreaks) - } -} - -private fun KtFile.addToScriptBlock(element: PsiElement, first: Boolean = false): PsiElement? = - if (first) script?.blockExpression?.addAfter(element, null) else script?.blockExpression?.add(element) - -private fun KtFile.addImportIfMissing(path: String): KtImportDirective = - importDirectives.find { it.importPath?.pathStr == path } ?: - importList?.add(psiFactory.createImportDirective(ImportPath.fromString(path))) as KtImportDirective - -fun KtBlockExpression.addExpressionAfterIfMissing(text: String, after: PsiElement): KtExpression = addStatementIfMissing(text) { - psiFactory.createExpression(it).let { created -> - addAfter(created, after) - } -} - -fun KtBlockExpression.addExpressionIfMissing(text: String, first: Boolean = false): KtExpression = addStatementIfMissing(text) { - psiFactory.createExpression(it).let { created -> - if(first) addAfter(created, null) else add(created) - } -} - -fun KtBlockExpression.addDeclarationIfMissing(text: String, first: Boolean = false): KtDeclaration = addStatementIfMissing(text) { - psiFactory.createDeclaration(it).let { created -> - if(first) addAfter(created, null) else add(created) - } -} - -private inline fun KtBlockExpression.addStatementIfMissing( - text: String, - crossinline factory: (String) -> PsiElement): T { - statements.find { it.text == text }?.let { - return it as T - } - - return factory(text).apply { addNewLinesIfNeeded() } as T -} - -private fun KtPsiFactory.createScriptInitializer(text: String): KtScriptInitializer = - createFile("dummy.kts", text).script?.blockExpression?.firstChild as KtScriptInitializer - -private val PsiElement.psiFactory: KtPsiFactory - get() = KtPsiFactory(this) - - -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/GradleBuildScriptManipulator.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/GradleBuildScriptManipulator.kt new file mode 100644 index 00000000000..26cd2ee9c6b --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/GradleBuildScriptManipulator.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.configuration + +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.psi.PsiElement + +interface GradleBuildScriptManipulator { + fun isConfigured(kotlinPluginName: String): Boolean + + fun configureModuleBuildScript(kotlinPluginName: String, + stdlibArtifactName: String, + version: String, + jvmTarget: String?): Boolean + + fun configureProjectBuildScript(version: String): Boolean + + fun changeCoroutineConfiguration(coroutineOption: String): PsiElement? + + fun changeLanguageVersion(version: String, forTests: Boolean): PsiElement? + + fun changeApiVersion(version: String, forTests: Boolean): PsiElement? + + fun addKotlinLibraryToModuleBuildScript( + scope: DependencyScope, + libraryDescriptor: ExternalLibraryDescriptor, + isAndroidModule: Boolean) + + fun getKotlinStdlibVersion(): String? +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/GroovyBuildScriptManipulator.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/GroovyBuildScriptManipulator.kt new file mode 100644 index 00000000000..5f8a9d5de9e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/GroovyBuildScriptManipulator.kt @@ -0,0 +1,282 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.configuration + +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.psi.PsiElement +import com.intellij.psi.codeStyle.CodeStyleManager +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression +import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner + +class GroovyBuildScriptManipulator(private val groovyScript: GroovyFile) : GradleBuildScriptManipulator { + override fun isConfigured(kotlinPluginName: String): Boolean { + val fileText = groovyScript.text + return containsDirective(fileText, getApplyPluginDirective(kotlinPluginName)) && + fileText.contains("org.jetbrains.kotlin") && + fileText.contains("kotlin-stdlib") + } + + override fun configureModuleBuildScript( + kotlinPluginName: String, + stdlibArtifactName: String, + version: String, + jvmTarget: String? + ): Boolean { + val oldText = groovyScript.text + val applyPluginDirective = getGroovyApplyPluginDirective(kotlinPluginName) + if (!containsDirective(groovyScript.text, applyPluginDirective)) { + val apply = GroovyPsiElementFactory.getInstance(groovyScript.project).createExpressionFromText(applyPluginDirective) + val applyStatement = getApplyStatement(groovyScript) + if (applyStatement != null) { + groovyScript.addAfter(apply, applyStatement) + } + else { + val buildScriptBlock = groovyScript.getBlockByName("buildscript") + if (buildScriptBlock != null) { + groovyScript.addAfter(apply, buildScriptBlock.parent) + } + else { + groovyScript.addAfter(apply, groovyScript.statements.lastOrNull() ?: groovyScript.firstChild) + } + } + } + + groovyScript.getRepositoriesBlock().apply { + addRepository(version) + } + + groovyScript.getDependenciesBlock().apply { + addExpressionInBlockIfNeeded(getGroovyDependencySnippet(stdlibArtifactName), false) + } + + if (jvmTarget != null) { + changeKotlinTaskParameter(groovyScript, "jvmTarget", jvmTarget, forTests = false) + changeKotlinTaskParameter(groovyScript, "jvmTarget", jvmTarget, forTests = true) + } + + return groovyScript.text != oldText + } + + override fun configureProjectBuildScript(version: String): Boolean { + val oldText = groovyScript.text + groovyScript.apply { + getBuildScriptBlock().apply { + addFirstExpressionInBlockIfNeeded(VERSION.replace(VERSION_TEMPLATE, version)) + } + + getBuildScriptRepositoriesBlock().apply { + addRepository(version) + } + + getBuildScriptDependenciesBlock().apply { + addLastExpressionInBlockIfNeeded(CLASSPATH) + } + } + + return oldText != groovyScript.text + } + + override fun changeCoroutineConfiguration(coroutineOption: String): PsiElement? { + val snippet = "coroutines \"$coroutineOption\"" + val kotlinBlock = groovyScript.getBlockOrCreate("kotlin") + kotlinBlock.getBlockOrCreate("experimental").apply { + addOrReplaceExpression(snippet) { stmt -> + (stmt as? GrMethodCall)?.invokedExpression?.text == "coroutines" + } + } + + return kotlinBlock.parent + } + + override fun changeLanguageVersion(version: String, forTests: Boolean): PsiElement? = + changeKotlinTaskParameter(groovyScript, "languageVersion", version, forTests) + + override fun changeApiVersion(version: String, forTests: Boolean): PsiElement? = + changeKotlinTaskParameter(groovyScript, "apiVersion", version, forTests) + + override fun addKotlinLibraryToModuleBuildScript( + scope: DependencyScope, + libraryDescriptor: ExternalLibraryDescriptor, + isAndroidModule: Boolean + ) { + val dependencyString = String.format( + "%s \"%s:%s:%s\"", + scope.toGradleCompileScope(isAndroidModule), + libraryDescriptor.libraryGroupId, + libraryDescriptor.libraryArtifactId, + libraryDescriptor.maxVersion) + + groovyScript.getDependenciesBlock().apply { + addLastExpressionInBlockIfNeeded(dependencyString) + } + } + + override fun getKotlinStdlibVersion(): String? { + val versionProperty = "\$kotlin_version" + groovyScript.getBlockByName("buildScript")?.let { + if (it.text.contains("ext.kotlin_version = ")) { + return versionProperty + } + } + + val dependencies = groovyScript.getBlockByName("dependencies")?.statements + val stdlibArtifactPrefix = "org.jetbrains.kotlin:kotlin-stdlib:" + dependencies?.forEach { dependency -> + val dependencyText = dependency.text + val startIndex = dependencyText.indexOf(stdlibArtifactPrefix) + stdlibArtifactPrefix.length + val endIndex = dependencyText.length - 1 + if (startIndex != -1 && endIndex != -1) { + return dependencyText.substring(startIndex, endIndex) + } + } + + return null + } + + private fun changeKotlinTaskParameter( + gradleFile: GroovyFile, + parameterName: String, + parameterValue: String, + forTests: Boolean + ): PsiElement? { + val snippet = "$parameterName = \"$parameterValue\"" + val kotlinBlock = gradleFile.getBlockOrCreate(if (forTests) "compileTestKotlin" else "compileKotlin") + + for (stmt in kotlinBlock.statements) { + if ((stmt as? GrAssignmentExpression)?.lValue?.text == "kotlinOptions." + parameterName) { + return stmt.replaceWithStatementFromText("kotlinOptions." + snippet) + } + } + + kotlinBlock.getBlockOrCreate("kotlinOptions").apply { + addOrReplaceExpression(snippet) { stmt -> + (stmt as? GrAssignmentExpression)?.lValue?.text == parameterName + } + } + + return kotlinBlock.parent + } + + private fun getGroovyDependencySnippet(artifactName: String) = "compile \"org.jetbrains.kotlin:$artifactName:\$kotlin_version\"" + + private fun getApplyPluginDirective(pluginName: String) = "apply plugin: '$pluginName'" + + private fun containsDirective(fileText: String, directive: String): Boolean { + return fileText.contains(directive) + || fileText.contains(directive.replace("\"", "'")) + || fileText.contains(directive.replace("'", "\"")) + } + + private fun getApplyStatement(file: GroovyFile): GrApplicationStatement? = + file.getChildrenOfType().find { it.invokedExpression.text == "apply" } + + private fun PsiElement.getBlockByName(name: String): GrClosableBlock? { + return getChildrenOfType() + .filter { it.closureArguments.isNotEmpty() } + .find { it.invokedExpression.text == name } + ?.let { it.closureArguments[0] } + } + + private fun GrClosableBlock.addRepository(version: String): Boolean { + val repository = getRepositoryForVersion(version) + val snippet = when { + repository != null -> repository.toGroovyRepositorySnippet() + !isRepositoryConfigured(text) -> "$MAVEN_CENTRAL\n" + else -> return false + } + return addLastExpressionInBlockIfNeeded(snippet) + } + + private fun GrStatementOwner.getRepositoriesBlock() = getBlockOrCreate("repositories") + + private fun GrStatementOwner.getDependenciesBlock(): GrClosableBlock = getBlockOrCreate("dependencies") + + private fun GrStatementOwner.getBuildScriptBlock() = getBlockOrCreate("buildscript") + + private fun GrStatementOwner.getBuildScriptRepositoriesBlock(): GrClosableBlock = + getBuildScriptBlock().getBlockOrCreate("repositories") + + private fun GrStatementOwner.getBuildScriptDependenciesBlock(): GrClosableBlock = + getBuildScriptBlock().getBlockOrCreate("dependencies") + + private fun GrStatementOwner.getBlockOrCreate(name: String): GrClosableBlock { + var block = getBlockByName(name) + if (block == null) { + val factory = GroovyPsiElementFactory.getInstance(project) + val newBlock = factory.createExpressionFromText("$name{\n}\n") + addAfter(newBlock, statements.lastOrNull() ?: firstChild) + block = getBlockByName(name)!! + } + return block + } + + private fun GrClosableBlock.addOrReplaceExpression(snippet: String, predicate: (GrStatement) -> Boolean) { + statements.firstOrNull(predicate)?.let { stmt -> + stmt.replaceWithStatementFromText(snippet) + return + } + addLastExpressionInBlockIfNeeded(snippet) + } + + private fun GrClosableBlock.addLastExpressionInBlockIfNeeded(expressionText: String): Boolean = + addExpressionInBlockIfNeeded(expressionText, false) + + private fun GrClosableBlock.addFirstExpressionInBlockIfNeeded(expressionText: String): Boolean = + addExpressionInBlockIfNeeded(expressionText, true) + + private fun GrClosableBlock.addExpressionInBlockIfNeeded(expressionText: String, isFirst: Boolean): Boolean { + if (text.contains(expressionText)) return false + val newStatement = GroovyPsiElementFactory.getInstance(project).createExpressionFromText(expressionText) + CodeStyleManager.getInstance(project).reformat(newStatement) + if (!isFirst && statements.isNotEmpty()) { + val lastStatement = statements[statements.size - 1] + if (lastStatement != null) { + addAfter(newStatement, lastStatement) + } + } + else { + if (firstChild != null) { + addAfter(newStatement, firstChild) + } + } + return true + } + + private fun getGroovyApplyPluginDirective(pluginName: String) = "apply plugin: '$pluginName'" + + private fun GrStatement.replaceWithStatementFromText(snippet: String): GrStatement { + val newStatement = GroovyPsiElementFactory.getInstance(project).createExpressionFromText(snippet) + CodeStyleManager.getInstance(project).reformat(newStatement) + return replaceWithStatement(newStatement) + } + + companion object { + private val VERSION_TEMPLATE = "\$VERSION$" + private val VERSION = String.format("ext.kotlin_version = '%s'", VERSION_TEMPLATE) + private val GRADLE_PLUGIN_ID = "kotlin-gradle-plugin" + private val CLASSPATH = "classpath \"$KOTLIN_GROUP_ID:$GRADLE_PLUGIN_ID:\$kotlin_version\"" + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinBuildScriptManipulator.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinBuildScriptManipulator.kt new file mode 100644 index 00000000000..1e2ad6e173c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinBuildScriptManipulator.kt @@ -0,0 +1,315 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.configuration + +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.kotlin.resolve.ImportPath +import org.jetbrains.kotlin.utils.addToStdlib.cast + +class KotlinBuildScriptManipulator(private val kotlinScript: KtFile) : GradleBuildScriptManipulator { + override fun isConfigured(kotlinPluginName: String): Boolean = + kotlinScript.containsApplyKotlinPlugin(kotlinPluginName) && kotlinScript.containsCompileStdLib() + + override fun configureProjectBuildScript(version: String): Boolean { + val originalText = kotlinScript.text + kotlinScript.getBuildScriptBlock()?.apply { + addDeclarationIfMissing("var $GSK_KOTLIN_VERSION_PROPERTY_NAME: String by extra", true).also { + addExpressionAfterIfMissing("$GSK_KOTLIN_VERSION_PROPERTY_NAME = \"$version\"", it) + } + + getRepositoriesBlock()?.addRepositoryIfMissing(version) + getDependenciesBlock()?.addPluginToClassPathIfMissing() + } + + return originalText != kotlinScript.text + } + + override fun configureModuleBuildScript( + kotlinPluginName: String, + stdlibArtifactName: String, + version: String, + jvmTarget: String? + ): Boolean { + val originalText = kotlinScript.text + kotlinScript.apply { + script?.blockExpression?.addDeclarationIfMissing("val $GSK_KOTLIN_VERSION_PROPERTY_NAME: String by extra", true) + getApplyBlock()?.createPluginIfMissing(kotlinPluginName) + getDependenciesBlock()?.addCompileStdlibIfMissing(stdlibArtifactName) + getRepositoriesBlock()?.addRepositoryIfMissing(version) + jvmTarget?.let { + changeKotlinTaskParameter("jvmTarget", it, forTests = false) + changeKotlinTaskParameter("jvmTarget", it, forTests = true) + } + } + + return originalText != kotlinScript.text + } + + override fun changeCoroutineConfiguration(coroutineOption: String): PsiElement? = + kotlinScript.changeCoroutineConfiguration(coroutineOption) + + override fun changeLanguageVersion(version: String, forTests: Boolean): PsiElement? = + kotlinScript.changeKotlinTaskParameter("languageVersion", version, forTests) + + override fun changeApiVersion(version: String, forTests: Boolean): PsiElement? = + kotlinScript.changeKotlinTaskParameter("apiVersion", version, forTests) + + override fun addKotlinLibraryToModuleBuildScript( + scope: DependencyScope, + libraryDescriptor: ExternalLibraryDescriptor, + isAndroidModule: Boolean + ) { + val kotlinLibraryVersion = libraryDescriptor.maxVersion.takeIf { it != GSK_KOTLIN_VERSION_PROPERTY_NAME } + kotlinScript.getDependenciesBlock()?.apply { + addExpressionIfMissing( + getCompileDependencySnippet( + libraryDescriptor.libraryGroupId, + libraryDescriptor.libraryArtifactId, + scope.toGradleCompileScope(isAndroidModule), + kotlinLibraryVersion)) + } + } + + override fun getKotlinStdlibVersion(): String? = kotlinScript.getKotlinStdlibVersion() + + private fun KtBlockExpression.addCompileStdlibIfMissing(stdlibArtifactName: String): KtCallExpression? = + findCompileStdLib() ?: addExpressionIfMissing(getCompileDependencySnippet(KOTLIN_GROUP_ID, stdlibArtifactName)) as? KtCallExpression + + private fun KtFile.containsCompileStdLib(): Boolean = + findScriptInitializer("dependencies")?.getBlock()?.findCompileStdLib() != null + + private fun KtFile.containsApplyKotlinPlugin(pluginName: String): Boolean = + findScriptInitializer("apply")?.getBlock()?.findPlugin(pluginName) != null + + private fun KtBlockExpression.findPlugin(pluginName: String): KtCallExpression? { + return PsiTreeUtil.getChildrenOfType(this, KtCallExpression::class.java)?.find { + it.calleeExpression?.text == "plugin" && it.valueArguments.firstOrNull()?.text == "\"$pluginName\"" + } + } + + private fun KtFile.findScriptInitializer(startsWith: String): KtScriptInitializer? = + PsiTreeUtil.findChildrenOfType(this, KtScriptInitializer::class.java).find { it.text.startsWith(startsWith) } + + private fun KtBlockExpression.findBlock(name: String): KtBlockExpression? { + return getChildrenOfType().find { + it.calleeExpression?.text == name && + it.valueArguments.singleOrNull()?.getArgumentExpression() is KtLambdaExpression + }?.getBlock() + } + + private fun KtScriptInitializer.getBlock(): KtBlockExpression? = + PsiTreeUtil.findChildOfType(this, KtCallExpression::class.java)?.getBlock() + + private fun KtCallExpression.getBlock(): KtBlockExpression? = + (valueArguments.singleOrNull()?.getArgumentExpression() as? KtLambdaExpression)?.bodyExpression + + private fun getCompileDependencySnippet(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\")" + + private fun getKotlinModuleDependencySnippet(artifactId: String, version: String? = null): String = + "kotlinModule(\"${artifactId.removePrefix("kotlin-")}\", ${version?.let { "\"$it\"" } ?: GSK_KOTLIN_VERSION_PROPERTY_NAME})" + + private 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 + } + } + } + + private 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 + } + + private fun KtFile.getRepositoriesBlock(): KtBlockExpression? = + findScriptInitializer("repositories")?.getBlock() ?: addTopLevelBlock("repositories") + + private fun KtFile.getDependenciesBlock(): KtBlockExpression? = + findScriptInitializer("dependencies")?.getBlock() ?: addTopLevelBlock("dependencies") + + private fun KtFile.createApplyBlock(): KtBlockExpression? { + val apply = psiFactory.createScriptInitializer("apply {\n}") + val plugins = findScriptInitializer("plugins") + val addedElement = plugins?.addSibling(apply) ?: addToScriptBlock(apply) + addedElement?.addNewLinesIfNeeded() + return (addedElement as? KtScriptInitializer)?.getBlock() + } + + private fun KtFile.getApplyBlock(): KtBlockExpression? = findScriptInitializer("apply")?.getBlock() ?: createApplyBlock() + + private fun KtBlockExpression.createPluginIfMissing(pluginName: String): KtCallExpression? = + findPlugin(pluginName) ?: addExpressionIfMissing("plugin(\"$pluginName\")") as? KtCallExpression + + private fun KtFile.changeCoroutineConfiguration(coroutineOption: String): PsiElement? { + val snippet = "experimental.coroutines = Coroutines.${coroutineOption.toUpperCase()}" + val kotlinBlock = findScriptInitializer("kotlin")?.getBlock() ?: addTopLevelBlock("kotlin") ?: return null + addImportIfMissing("org.jetbrains.kotlin.gradle.dsl.Coroutines") + val statement = kotlinBlock.statements.find { it.text.startsWith("experimental.coroutines") } + return if (statement != null) { + statement.replace(psiFactory.createExpression(snippet)) + } + else { + kotlinBlock.add(psiFactory.createExpression(snippet)).apply { addNewLinesIfNeeded() } + } + } + + private fun KtFile.changeKotlinTaskParameter(parameterName: String, parameterValue: String, forTests: Boolean): PsiElement? { + val snippet = "$parameterName = \"$parameterValue\"" + val taskName = if (forTests) "compileTestKotlin" else "compileKotlin" + val optionsBlock = findScriptInitializer("$taskName.kotlinOptions")?.getBlock() + return if (optionsBlock != null) { + val assignment = optionsBlock.statements.find { + (it as? KtBinaryExpression)?.left?.text == parameterName + } + if (assignment != null) { + assignment.replace(psiFactory.createExpression(snippet)) + } + else { + optionsBlock.addExpressionIfMissing(snippet) + } + } + else { + addImportIfMissing("org.jetbrains.kotlin.gradle.tasks.KotlinCompile") + script?.blockExpression?.addDeclarationIfMissing("val $taskName: KotlinCompile by tasks") + addTopLevelBlock("$taskName.kotlinOptions")?.addExpressionIfMissing(snippet) + } + } + + private fun KtFile.getBuildScriptBlock(): KtBlockExpression? = + findScriptInitializer("buildscript")?.getBlock() ?: addTopLevelBlock("buildscript", true) + + private fun KtBlockExpression.getRepositoriesBlock(): KtBlockExpression? = + findBlock("repositories") ?: addBlock("repositories") + + private fun KtBlockExpression.getDependenciesBlock(): KtBlockExpression? = + findBlock("dependencies") ?: addBlock("dependencies") + + private fun KtBlockExpression.addRepositoryIfMissing(version: String): KtCallExpression? { + val repository = getRepositoryForVersion(version) + val snippet = when { + repository != null -> repository.toKotlinRepositorySnippet() + !isRepositoryConfigured(text) -> MAVEN_CENTRAL + else -> return null + } + + return addExpressionIfMissing(snippet) as? KtCallExpression + } + + private fun KtBlockExpression.addPluginToClassPathIfMissing(): KtCallExpression? = + addExpressionIfMissing("classpath(${getKotlinModuleDependencySnippet("gradle-plugin")})") as? KtCallExpression + + + private fun KtBlockExpression.addBlock(name: String): KtBlockExpression? { + return add(psiFactory.createExpression("$name {\n}")) + ?.apply { addNewLinesIfNeeded() } + ?.cast() + ?.getBlock() + } + + private fun KtFile.addTopLevelBlock(name: String, first: Boolean = false): KtBlockExpression? { + val scriptInitializer = psiFactory.createScriptInitializer("$name {\n}") + val addedElement = addToScriptBlock(scriptInitializer, first) as? KtScriptInitializer + addedElement?.addNewLinesIfNeeded() + return addedElement?.getBlock() + } + + private fun PsiElement.addSibling(element: PsiElement): PsiElement = parent.addAfter(element, this) + + private fun PsiElement.addNewLineBefore(lineBreaks: Int = 1) { + parent.addBefore(psiFactory.createNewLine(lineBreaks), this) + } + + private fun PsiElement.addNewLineAfter(lineBreaks: Int = 1) { + parent.addAfter(psiFactory.createNewLine(lineBreaks), this) + } + + private fun PsiElement.addNewLinesIfNeeded(lineBreaks: Int = 1) { + if (prevSibling != null && prevSibling.text.isNotBlank()) { + addNewLineBefore(lineBreaks) + } + + if (nextSibling != null && nextSibling.text.isNotBlank()) { + addNewLineAfter(lineBreaks) + } + } + + private fun KtFile.addToScriptBlock(element: PsiElement, first: Boolean = false): PsiElement? = + if (first) script?.blockExpression?.addAfter(element, null) else script?.blockExpression?.add(element) + + private fun KtFile.addImportIfMissing(path: String): KtImportDirective = + importDirectives.find { it.importPath?.pathStr == path } ?: + importList?.add(psiFactory.createImportDirective(ImportPath.fromString(path))) as KtImportDirective + + private fun KtBlockExpression.addExpressionAfterIfMissing(text: String, after: PsiElement): KtExpression = addStatementIfMissing(text) { + psiFactory.createExpression(it).let { created -> + addAfter(created, after) + } + } + + private fun KtBlockExpression.addExpressionIfMissing(text: String, first: Boolean = false): KtExpression = addStatementIfMissing(text) { + psiFactory.createExpression(it).let { created -> + if(first) addAfter(created, null) else add(created) + } + } + + private fun KtBlockExpression.addDeclarationIfMissing(text: String, first: Boolean = false): KtDeclaration = addStatementIfMissing(text) { + psiFactory.createDeclaration(it).let { created -> + if(first) addAfter(created, null) else add(created) + } + } + + private inline fun KtBlockExpression.addStatementIfMissing( + text: String, + crossinline factory: (String) -> PsiElement): T { + statements.find { it.text == text }?.let { + return it as T + } + + return factory(text).apply { addNewLinesIfNeeded() } as T + } + + private fun KtPsiFactory.createScriptInitializer(text: String): KtScriptInitializer = + createFile("dummy.kts", text).script?.blockExpression?.firstChild as KtScriptInitializer + + private val PsiElement.psiFactory: KtPsiFactory + get() = KtPsiFactory(this) + + companion object { + private val STDLIB_ARTIFACT_PREFIX = "org.jetbrains.kotlin:kotlin-stdlib" + private val GSK_KOTLIN_VERSION_PROPERTY_NAME = "kotlin_version" + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt index cfa89f083ec..d40fddeb359 100644 --- a/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt +++ b/idea/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt @@ -33,26 +33,14 @@ import com.intellij.openapi.vfs.WritingAccessProvider import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager -import com.intellij.psi.codeStyle.CodeStyleManager import org.jetbrains.kotlin.idea.KotlinPluginUtil import org.jetbrains.kotlin.idea.framework.ui.ConfigureDialogWithModulesAndVersion import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.versions.getStdlibArtifactId -import org.jetbrains.kotlin.psi.KtBlockExpression -import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.groovy.lang.psi.GroovyFile -import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory -import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement -import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall -import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression -import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner import java.io.File import java.util.* @@ -80,32 +68,18 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { return ConfigureKotlinStatus.BROKEN } - private fun PsiFile.isConfiguredByAnyGradleConfigurator() = - Extensions.getExtensions(KotlinProjectConfigurator.EP_NAME) - .filterIsInstance() - .any { it.isFileConfigured(this) } - - protected open fun isApplicable(module: Module): Boolean { - return KotlinPluginUtil.isGradleModule(module) && !KotlinPluginUtil.isAndroidGradleModule(module) + private fun PsiFile.isConfiguredByAnyGradleConfigurator(): Boolean { + return Extensions.getExtensions(KotlinProjectConfigurator.EP_NAME) + .filterIsInstance() + .any { it.isFileConfigured(this) } } + protected open fun isApplicable(module: Module): Boolean = + KotlinPluginUtil.isGradleModule(module) && !KotlinPluginUtil.isAndroidGradleModule(module) + protected open fun getMinimumSupportedVersion() = "1.0.0" - private fun isFileConfigured(projectGradleFile: PsiFile): Boolean = when(projectGradleFile) { - is GroovyFile -> isGradleFileConfigured(projectGradleFile) - is KtFile -> isKotlinFileConfigured(projectGradleFile) - else -> error("Unknown build script file type") - } - - fun isKotlinFileConfigured(file: KtFile): Boolean = - file.containsApplyKotlinPlugin(kotlinPluginName) && file.containsCompileStdLib() - - private fun isGradleFileConfigured(file: GroovyFile): Boolean { - val fileText = file.text - return containsDirective(fileText, getGroovyApplyPluginDirective(kotlinPluginName)) && - fileText.contains("org.jetbrains.kotlin") && - fileText.contains("kotlin-stdlib") - } + private fun isFileConfigured(buildScript: PsiFile): Boolean = getManipulator(buildScript).isConfigured(kotlinPluginName) @JvmSuppressWildcards override fun configure(project: Project, excludeModules: Collection) { @@ -132,7 +106,7 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { val changedFiles = HashSet() val buildScript = project.getTopLevelBuildScriptPsiFile() if (buildScript != null && canConfigureFile(buildScript)) { - val isModified = changeBuildScript(buildScript, true, kotlinVersion, collector) + val isModified = configureBuildScript(buildScript, true, kotlinVersion, collector) if (isModified) { changedFiles.add(buildScript) } @@ -141,7 +115,7 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { for (module in modulesToConfigure) { val file = module.getBuildScriptPsiFile() if (file != null && canConfigureFile(file)) { - val isModified = changeBuildScript(file, false, kotlinVersion, collector) + val isModified = configureBuildScript(file, false, kotlinVersion, collector) if (isModified) { changedFiles.add(file) } @@ -153,71 +127,18 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { return changedFiles } - protected fun addElementsToModuleFile(file: PsiFile, version: String): Boolean = when (file) { - is GroovyFile -> addElementsToModuleGroovyFile(file, version) - is KtFile -> addElementsToModuleGSKFile(file, version) - else -> error("Unknown build script file type") - } - - protected fun addElementsToModuleGSKFile(file: KtFile, version: String): Boolean { - val originalText = file.text + protected fun configureModuleBuildScript(file: PsiFile, version: String): Boolean { val sdk = ModuleUtil.findModuleForPsiElement(file)?.let { ModuleRootManager.getInstance(it).sdk } - file.script?.blockExpression?.addDeclarationIfMissing("val $GSK_KOTLIN_VERSION_PROPERTY_NAME: String by extra", true) - file.getApplyBlock()?.createPluginIfMissing(kotlinPluginName) - file.getDependenciesBlock()?.addCompileStdlibIfMissing(sdk, version) - file.getRepositoriesBlock()?.addRepositoryIfMissing(version) - getJvmTarget(sdk, version)?.let { - file.changeKotlinTaskParameter("jvmTarget", it, forTests = false) - file.changeKotlinTaskParameter("jvmTarget", it, forTests = true) - } - return originalText != file.text - } - - private fun KtBlockExpression.addCompileStdlibIfMissing(sdk: Sdk?, version: String): KtCallExpression? = - findCompileStdLib() ?: - addExpressionIfMissing(getGSKCompileDependencySnippet(KOTLIN_GROUP_ID, getStdlibArtifactName(sdk, version))) as? KtCallExpression - - protected fun addElementsToModuleGroovyFile(file: GroovyFile, version: String): Boolean { - val oldText = file.text - - val applyPluginDirective = getGroovyApplyPluginDirective(kotlinPluginName) - if (!containsDirective(file.text, applyPluginDirective)) { - val apply = GroovyPsiElementFactory.getInstance(file.project).createExpressionFromText(applyPluginDirective) - val applyStatement = getApplyStatement(file) - if (applyStatement != null) { - file.addAfter(apply, applyStatement) - } - else { - val buildScript = getBlockByName(file, "buildscript") - if (buildScript != null) { - file.addAfter(apply, buildScript.parent) - } - else { - file.addAfter(apply, file.statements.lastOrNull() ?: file.firstChild) - } - } - } - - val repositoriesBlock = getRepositoriesBlock(file) - addRepository(repositoriesBlock, version) - - val dependenciesBlock = getDependenciesBlock(file) - val sdk = ModuleUtil.findModuleForPsiElement(file)?.let { ModuleRootManager.getInstance(it).sdk } - addExpressionInBlockIfNeeded(getStdlibDependencyDirectiveForGroovy(sdk, version), dependenciesBlock, false) val jvmTarget = getJvmTarget(sdk, version) - if (jvmTarget != null) { - changeKotlinTaskParameter(file, "jvmTarget", jvmTarget, forTests = false) - changeKotlinTaskParameter(file, "jvmTarget", jvmTarget, forTests = true) - } - - return file.text != oldText + return getManipulator(file).configureModuleBuildScript( + kotlinPluginName, + getStdlibArtifactName(sdk, version), + version, + jvmTarget) } protected open fun getStdlibArtifactName(sdk: Sdk?, version: String) = getStdlibArtifactId(sdk, version) - private fun getStdlibDependencyDirectiveForGroovy(sdk: Sdk?, version: String) = - getGroovyDependencySnippet(getStdlibArtifactName(sdk, version)) - protected open fun getJvmTarget(sdk: Sdk?, version: String): String? = null protected abstract val kotlinPluginName: String @@ -228,14 +149,14 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { version: String ): Boolean { if (!isTopLevelProjectFile) { - var wasModified = addElementsToProjectFile(file, version) - wasModified = wasModified or addElementsToModuleFile(file, version) + var wasModified = configureProjectFile(file, version) + wasModified = wasModified or configureModuleBuildScript(file, version) return wasModified } return false } - fun changeBuildScript( + fun configureBuildScript( file: PsiFile, isTopLevelProjectFile: Boolean, version: String, @@ -256,26 +177,19 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { } companion object { - private val VERSION_TEMPLATE = "\$VERSION$" + fun getManipulator(file: PsiFile): GradleBuildScriptManipulator = when (file) { + is KtFile -> KotlinBuildScriptManipulator(file) + is GroovyFile -> GroovyBuildScriptManipulator(file) + else -> error("Unknown build script file type!") + } val GROUP_ID = "org.jetbrains.kotlin" val GRADLE_PLUGIN_ID = "kotlin-gradle-plugin" val CLASSPATH = "classpath \"$GROUP_ID:$GRADLE_PLUGIN_ID:\$kotlin_version\"" - private val MAVEN_CENTRAL = "mavenCentral()" - private val JCENTER = "jcenter()" - - private val VERSION = String.format("ext.kotlin_version = '%s'", VERSION_TEMPLATE) - private val KOTLIN_BUILD_SCRIPT_NAME = "build.gradle.kts" - private fun containsDirective(fileText: String, directive: String): Boolean { - return fileText.contains(directive) - || fileText.contains(directive.replace("\"", "'")) - || fileText.contains(directive.replace("'", "\"")) - } - fun getGroovyDependencySnippet(artifactName: String) = "compile \"org.jetbrains.kotlin:$artifactName:\$kotlin_version\"" fun getGroovyApplyPluginDirective(pluginName: String) = "apply plugin: '$pluginName'" @@ -286,11 +200,8 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { return } - val isAndroidModule = KotlinPluginUtil.isAndroidGradleModule(module) - when (buildScript) { - is GroovyFile -> addKotlinLibraryToModuleGroovyFile(buildScript, scope, libraryDescriptor, isAndroidModule) - is KtFile -> addKotlinLibraryToModuleGSKFile(buildScript, scope, libraryDescriptor, isAndroidModule) - } + getManipulator(buildScript) + .addKotlinLibraryToModuleBuildScript(scope, libraryDescriptor, KotlinPluginUtil.isAndroidGradleModule(module)) buildScript.virtualFile?.let { createConfigureKotlinNotificationCollector(buildScript.project) @@ -299,131 +210,26 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { } } - 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? { - return changeBuildGradle(module) { buildScriptFile -> - when (buildScriptFile) { - is GroovyFile -> changeCoroutineConfiguration(buildScriptFile, coroutineOption) - is KtFile -> changeCoroutineConfiguration(buildScriptFile, coroutineOption) - else -> error("Unknown build script file type") - } - } - } - - fun changeCoroutineConfiguration(gradleFile: GroovyFile, coroutineOption: String): PsiElement? { - val snippet = "coroutines \"$coroutineOption\"" - val kotlinBlock = getBlockOrCreate(gradleFile, "kotlin") - val experimentalBlock = getBlockOrCreate(kotlinBlock, "experimental") - addOrReplaceExpression(experimentalBlock, snippet) { stmt -> - (stmt as? GrMethodCall)?.invokedExpression?.text == "coroutines" - } - return kotlinBlock.parent + fun changeCoroutineConfiguration(module: Module, coroutineOption: String): PsiElement? = changeBuildGradle(module) { + getManipulator(it).changeCoroutineConfiguration(coroutineOption) } fun changeLanguageVersion(module: Module, languageVersion: String?, apiVersion: String? = null, forTests: Boolean): PsiElement? { return changeBuildGradle(module) { buildScriptFile -> + val manipulator = getManipulator(buildScriptFile) var result: PsiElement? = null if (languageVersion != null) { - result = when (buildScriptFile) { - is GroovyFile -> changeLanguageVersion(buildScriptFile, languageVersion, forTests) - is KtFile -> changeLanguageVersion(buildScriptFile, languageVersion, forTests) - else -> error("Unknown build script file type") - } + result = manipulator.changeLanguageVersion(languageVersion, forTests) } if (apiVersion != null) { - result = when (buildScriptFile) { - is GroovyFile -> changeApiVersion(buildScriptFile, apiVersion, forTests) - is KtFile -> changeApiVersion(buildScriptFile, apiVersion, forTests) - else -> error("Unknown build script file type") - } + result = manipulator.changeApiVersion(apiVersion, forTests) } result } } - fun changeLanguageVersion(gradleFile: KtFile, languageVersion: String, forTests: Boolean): PsiElement? { - return gradleFile.changeKotlinTaskParameter("languageVersion", languageVersion, forTests) - } - - fun changeApiVersion(gradleFile: KtFile, apiVersion: String, forTests: Boolean): PsiElement? { - return gradleFile.changeKotlinTaskParameter("apiVersion", apiVersion, forTests) - } - - fun changeLanguageVersion(gradleFile: GroovyFile, languageVersion: String, forTests: Boolean): PsiElement? { - return changeKotlinTaskParameter(gradleFile, "languageVersion", languageVersion, forTests) - } - - fun changeApiVersion(gradleFile: GroovyFile, apiVersion: String, forTests: Boolean): PsiElement? { - return changeKotlinTaskParameter(gradleFile, "apiVersion", apiVersion, forTests) - } - - private fun changeKotlinTaskParameter(gradleFile: GroovyFile, parameterName: String, parameterValue: String, forTests: Boolean): PsiElement? { - val snippet = "$parameterName = \"$parameterValue\"" - val kotlinBlock = getBlockOrCreate(gradleFile, if (forTests) "compileTestKotlin" else "compileKotlin") - - for (stmt in kotlinBlock.statements) { - if ((stmt as? GrAssignmentExpression)?.lValue?.text == "kotlinOptions." + parameterName) { - return stmt.replaceWithStatementFromText("kotlinOptions." + snippet) - } - } - - val kotlinOptionsBlock = getBlockOrCreate(kotlinBlock, "kotlinOptions") - addOrReplaceExpression(kotlinOptionsBlock, snippet) { stmt -> - (stmt as? GrAssignmentExpression)?.lValue?.text == parameterName - } - return kotlinBlock.parent - } - private fun changeBuildGradle(module: Module, body: (PsiFile) -> PsiElement?): PsiElement? { val buildScriptFile = module.getBuildScriptPsiFile() if (buildScriptFile != null && canConfigureFile(buildScriptFile)) { @@ -434,93 +240,15 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { return null } - private fun addOrReplaceExpression(block: GrClosableBlock, snippet: String, predicate: (GrStatement) -> Boolean) { - block.statements.firstOrNull(predicate)?.let { stmt -> - stmt.replaceWithStatementFromText(snippet) - return - } - addLastExpressionInBlockIfNeeded(snippet, block) - } - - private fun GrStatement.replaceWithStatementFromText(snippet: String): GrStatement { - val newStatement = GroovyPsiElementFactory.getInstance(project).createExpressionFromText(snippet) - CodeStyleManager.getInstance(project).reformat(newStatement) - return replaceWithStatement(newStatement) - } - fun getKotlinStdlibVersion(module: Module): String? { - val gradleFile = module.getBuildScriptPsiFile() ?: return null - return when (gradleFile) { - is GroovyFile -> getKotlinStdlibVersion(gradleFile) - is KtFile -> gradleFile.getKotlinStdlibVersion() - else -> null + return module.getBuildScriptPsiFile()?.let { + getManipulator(it).getKotlinStdlibVersion() } } - private fun getKotlinStdlibVersion(gradleFile: GroovyFile): String? { - val versionProperty = "\$kotlin_version" - val block = getBuildScriptBlock(gradleFile) - if (block.text.contains("ext.kotlin_version = ")) { - return versionProperty - } + fun configureProjectFile(file: PsiFile, version: String): Boolean = getManipulator(file).configureProjectBuildScript(version) - val dependencies = getDependenciesBlock(gradleFile).statements - val stdlibArtifactPrefix = "org.jetbrains.kotlin:kotlin-stdlib:" - for (dependency in dependencies) { - val dependencyText = dependency.text - val startIndex = dependencyText.indexOf(stdlibArtifactPrefix) + stdlibArtifactPrefix.length - val endIndex = dependencyText.length - 1 - if (startIndex != -1 && endIndex != -1) { - return dependencyText.substring(startIndex, endIndex) - } - } - - return null - } - - fun addElementsToProjectFile(file: PsiFile, version: String): Boolean = when (file) { - is GroovyFile -> addElementsToProjectGroovyFile(file, version) - is KtFile -> addElementsToProjectGSKFile(file, version) - else -> error("Unknown build script file type") - } - - fun addElementsToProjectGSKFile(file: KtFile, version: String): Boolean { - val originalText = file.text - - file.getBuildScriptBlock()?.apply { - addDeclarationIfMissing("var $GSK_KOTLIN_VERSION_PROPERTY_NAME: String by extra", true).also { - addExpressionAfterIfMissing("$GSK_KOTLIN_VERSION_PROPERTY_NAME = \"$version\"", it) - } - - getRepositoriesBlock()?.addRepositoryIfMissing(version) - getDependenciesBlock()?.addPluginToClassPathIfMissing() - } - - return originalText != file.text - } - - fun addElementsToProjectGroovyFile(file: GroovyFile, version: String): Boolean { - var wasModified: Boolean - - val buildScriptBlock = getBuildScriptBlock(file) - wasModified = addFirstExpressionInBlockIfNeeded(VERSION.replace(VERSION_TEMPLATE, version), buildScriptBlock) - - val buildScriptRepositoriesBlock = getBuildScriptRepositoriesBlock(file) - wasModified = wasModified or addRepository(buildScriptRepositoriesBlock, version) - - val buildScriptDependenciesBlock = getBuildScriptDependenciesBlock(file) - wasModified = wasModified or addLastExpressionInBlockIfNeeded(CLASSPATH, buildScriptDependenciesBlock) - - return wasModified - } - - private fun isRepositoryConfigured(repositoriesBlockText: String): Boolean { - return repositoriesBlockText.contains(MAVEN_CENTRAL) || repositoriesBlockText.contains(JCENTER) - } - - private fun canConfigureFile(file: PsiFile): Boolean { - return WritingAccessProvider.isPotentiallyWritable(file.virtualFile, null) - } + private fun canConfigureFile(file: PsiFile): Boolean = WritingAccessProvider.isPotentiallyWritable(file.virtualFile, null) private fun Module.getBuildScriptPsiFile() = getBuildScriptFile()?.getPsiFile(project) @@ -555,74 +283,6 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { PsiManager.getInstance(project).findFile(it) } - private fun getDependenciesBlock(file: GrStatementOwner): GrClosableBlock { - return getBlockOrCreate(file, "dependencies") - } - - private fun getBuildScriptBlock(file: GrStatementOwner) = getBlockOrCreate(file, "buildscript") - - private fun getBuildScriptDependenciesBlock(file: GrStatementOwner): GrClosableBlock { - val buildScript = getBuildScriptBlock(file) - return getBlockOrCreate(buildScript, "dependencies") - } - - private fun getBuildScriptRepositoriesBlock(file: GrStatementOwner): GrClosableBlock { - val buildScript = getBuildScriptBlock(file) - return getBlockOrCreate(buildScript, "repositories") - } - - private fun getRepositoriesBlock(file: GrStatementOwner) = getBlockOrCreate(file, "repositories") - - private fun getBlockOrCreate(parent: GrStatementOwner, name: String): GrClosableBlock { - var block = getBlockByName(parent, name) - if (block == null) { - val factory = GroovyPsiElementFactory.getInstance(parent.project) - val newBlock = factory.createExpressionFromText("$name{\n}\n") - parent.addAfter(newBlock, parent.statements.lastOrNull() ?: parent.firstChild) - block = getBlockByName(parent, name)!! - } - return block - } - - private fun addLastExpressionInBlockIfNeeded(text: String, block: GrClosableBlock): Boolean { - return addExpressionInBlockIfNeeded(text, block, false) - } - - private fun addFirstExpressionInBlockIfNeeded(text: String, block: GrClosableBlock): Boolean { - return addExpressionInBlockIfNeeded(text, block, true) - } - - private fun getBlockByName(parent: PsiElement, name: String): GrClosableBlock? { - return parent.getChildrenOfType() - .filter { it.closureArguments.isNotEmpty() } - .find { it.invokedExpression.text == name } - ?.let { it.closureArguments[0] } - } - - private fun addExpressionInBlockIfNeeded(text: String, block: GrClosableBlock, isFirst: Boolean): Boolean { - if (block.text.contains(text)) return false - val newStatement = GroovyPsiElementFactory.getInstance(block.project).createExpressionFromText(text) - CodeStyleManager.getInstance(block.project).reformat(newStatement) - val statements = block.statements - if (!isFirst && statements.isNotEmpty()) { - val lastStatement = statements[statements.size - 1] - if (lastStatement != null) { - block.addAfter(newStatement, lastStatement) - } - } - else { - val firstChild = block.firstChild - if (firstChild != null) { - block.addAfter(newStatement, firstChild) - } - } - return true - } - - private fun getApplyStatement(file: GroovyFile): GrApplicationStatement? = - file.getChildrenOfType() - .find { it.invokedExpression.text == "apply" } - private fun showErrorMessage(project: Project, message: String?) { Messages.showErrorDialog(project, "Couldn't configure kotlin-gradle plugin automatically.
" + @@ -630,15 +290,5 @@ abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { "
See manual installation instructions here.", "Configure Kotlin-Gradle Plugin") } - - private fun addRepository(repositoriesBlock: GrClosableBlock, version: String): Boolean { - val repository = getRepositoryForVersion(version) - val snippet = when { - repository != null -> repository.toGroovyRepositorySnippet() - !isRepositoryConfigured(repositoriesBlock.text) -> "$MAVEN_CENTRAL\n" - else -> return false - } - return addLastExpressionInBlockIfNeeded(snippet, repositoriesBlock) - } } } diff --git a/idea/tests/org/jetbrains/kotlin/gradle/KotlinWithGradleConfiguratorTest.kt b/idea/tests/org/jetbrains/kotlin/gradle/KotlinWithGradleConfiguratorTest.kt deleted file mode 100644 index 8e085abae43..00000000000 --- a/idea/tests/org/jetbrains/kotlin/gradle/KotlinWithGradleConfiguratorTest.kt +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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 -import org.jetbrains.kotlin.idea.util.application.executeWriteCommand -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.plugins.groovy.lang.psi.GroovyFile - -class KotlinWithGradleConfiguratorTest : LightCodeInsightFixtureTestCase() { - fun testAddCoroutinesSupport() { - val buildGradle = myFixture.configureByText("build.gradle", "apply plugin: 'kotlin'\n") as GroovyFile - myFixture.project.executeWriteCommand("") { - KotlinWithGradleConfigurator.changeCoroutineConfiguration(buildGradle, "enable") - } - myFixture.checkResult( - """apply plugin: 'kotlin' - |kotlin { - | experimental { - | coroutines "enable" - | } - |} - |""".trimMargin("|")) - } - - fun testAddCoroutinesSupportGSK() { - val buildGradle = myFixture.configureByText("build.gradle.kts", "") as KtFile - myFixture.project.executeWriteCommand("") { - changeCoroutineConfiguration(buildGradle, "enable") - } - myFixture.checkResult( - """import org.jetbrains.kotlin.gradle.dsl.Coroutines - | - |kotlin { - | experimental.coroutines = Coroutines.ENABLE - |}""".trimMargin("|")) - } - - fun testChangeCoroutinesSupport() { - val buildGradle = myFixture.configureByText("build.gradle", - """apply plugin: 'kotlin' - |kotlin { - | experimental { - | coroutines "disable" - | } - |} - |""".trimMargin("|")) as GroovyFile - myFixture.project.executeWriteCommand("") { - KotlinWithGradleConfigurator.changeCoroutineConfiguration(buildGradle, "enable") - } - myFixture.checkResult( - """apply plugin: 'kotlin' - |kotlin { - | experimental { - | coroutines "enable" - | } - |} - |""".trimMargin("|")) - } - - fun testChangeCoroutinesSupportGSK() { - val buildGradle = myFixture.configureByText("build.gradle.kts", - """import org.jetbrains.kotlin.gradle.dsl.Coroutines - | - |kotlin { - | experimental.coroutines = Coroutines.DISABLE - |} - |""".trimMargin("|")) as KtFile - myFixture.project.executeWriteCommand("") { - changeCoroutineConfiguration(buildGradle, "enable") - } - myFixture.checkResult( - """import org.jetbrains.kotlin.gradle.dsl.Coroutines - | - |kotlin { - | experimental.coroutines = Coroutines.ENABLE - |} - |""".trimMargin("|")) - } - - fun testAddLanguageVersion() { - val buildGradle = myFixture.configureByText("build.gradle", "apply plugin: 'kotlin'\n") as GroovyFile - myFixture.project.executeWriteCommand("") { - KotlinWithGradleConfigurator.changeLanguageVersion(buildGradle, "1.1", false) - } - myFixture.checkResult( - """apply plugin: 'kotlin' - |compileKotlin { - | kotlinOptions { - | languageVersion = "1.1" - | } - |} - |""".trimMargin("|")) - } - - fun testAddLanguageVersionGSK() { - val buildGradle = myFixture.configureByText("build.gradle.kts", "") as KtFile - myFixture.project.executeWriteCommand("") { - KotlinWithGradleConfigurator.changeLanguageVersion(buildGradle, "1.1", false) - } - myFixture.checkResult( - """import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - | - |val compileKotlin: KotlinCompile by tasks - |compileKotlin.kotlinOptions { - | languageVersion = "1.1" - |}""".trimMargin("|")) - } - - fun testChangeLanguageVersion() { - val buildGradle = myFixture.configureByText("build.gradle", - """apply plugin: 'kotlin' - |compileKotlin { - | kotlinOptions { - | languageVersion = "1.0" - | } - |} - |""".trimMargin("|")) as GroovyFile - myFixture.project.executeWriteCommand("") { - KotlinWithGradleConfigurator.changeLanguageVersion(buildGradle, "1.1", false) - } - myFixture.checkResult( - """apply plugin: 'kotlin' - |compileKotlin { - | kotlinOptions { - | languageVersion = "1.1" - | } - |} - |""".trimMargin("|")) - } - - fun testChangeLanguageVersionGSK() { - val buildGradle = myFixture.configureByText("build.gradle.kts", - """val compileKotlin: KotlinCompile by tasks - |compileKotlin.kotlinOptions { - | languageVersion = "1.0" - |} - |""".trimMargin("|")) as KtFile - myFixture.project.executeWriteCommand("") { - KotlinWithGradleConfigurator.changeLanguageVersion(buildGradle, "1.1", false) - } - myFixture.checkResult( - """val compileKotlin: KotlinCompile by tasks - |compileKotlin.kotlinOptions { - | languageVersion = "1.1" - |} - |""".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("|")) - } -} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt index 0732fc3fca4..3952b525b7a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleConfiguratorTest.kt @@ -20,7 +20,10 @@ import com.intellij.openapi.extensions.Extensions import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.impl.LoadTextUtil import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor import org.jetbrains.kotlin.idea.configuration.* +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.test.testFramework.runInEdtAndWait import org.jetbrains.kotlin.test.testFramework.runWriteAction @@ -208,4 +211,545 @@ class GradleConfiguratorTest : GradleImportingTestCase() { assertSameElements(moduleNamesWithKotlinFiles, "app") } } + + @Test + fun testAddNonKotlinLibraryGSK() { + val buildScript = createProjectSubFile("build.gradle.kts", + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + |} + |""".trimMargin("|")) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModule( + myTestFixture.module, + DependencyScope.COMPILE, + object: ExternalLibraryDescriptor("org.a.b", "lib", "1.0.0", "1.0.0") { + override fun getLibraryClassesRoots() = emptyList() + }) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + | compile("org.a.b:lib:1.0.0") + |} + |""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddLibraryGSK_WithKotlinVersion() { + val buildScript = createProjectSubFile("build.gradle.kts", + """ + |val kotlin_version: String by extra + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8", kotlin_version)) + |} + |""".trimMargin("|")) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + val stdLibVersion = KotlinWithGradleConfigurator.getKotlinStdlibVersion(myTestFixture.module) + KotlinWithGradleConfigurator.addKotlinLibraryToModule( + myTestFixture.module, + DependencyScope.COMPILE, + object : ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", stdLibVersion, stdLibVersion) { + override fun getLibraryClassesRoots() = emptyList() + }) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """ + |val kotlin_version: String by extra + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8", kotlin_version)) + | compile(kotlinModule("reflect", kotlin_version)) + |} + |""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddTestLibraryGSK() { + val buildScript = createProjectSubFile("build.gradle.kts", + """ + |dependencies { + | compile(kotlinModule("stdlib-jre8")) + |} + |""".trimMargin("|")) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModule( + myTestFixture.module, + DependencyScope.TEST, + object : ExternalLibraryDescriptor("junit", "junit", "4.12", "4.12") { + override fun getLibraryClassesRoots() = emptyList() + }) + + KotlinWithGradleConfigurator.addKotlinLibraryToModule( + myTestFixture.module, + DependencyScope.TEST, + object : ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-test", "1.1.2", "1.1.2") { + override fun getLibraryClassesRoots() = emptyList() + }) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """ + |dependencies { + | compile(kotlinModule("stdlib-jre8")) + | testCompile("junit:junit:4.12") + | testCompile(kotlinModule("test", "1.1.2")) + |} + |""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddLibraryGSK() { + val buildScript = createProjectSubFile("build.gradle.kts", + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + |} + |""".trimMargin("|")) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModule( + myTestFixture.module, + DependencyScope.COMPILE, + object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", "1.0.0", "1.0.0") { + override fun getLibraryClassesRoots() = emptyList() + }) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """ + |dependencies { + | testCompile("junit:junit:4.12") + | compile(kotlinModule("stdlib-jre8")) + | compile(kotlinModule("reflect", "1.0.0")) + |} + |""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddCoroutinesSupport() { + val buildScript = createProjectSubFile("build.gradle", """ + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + """.trimIndent()) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeCoroutineConfiguration(myTestFixture.module, "enable") + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals(""" + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + kotlin { + experimental { + coroutines "enable" + } + } + """.trimIndent(), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddCoroutinesSupportGSK() { + val buildScript = createProjectSubFile("build.gradle.kts", "") + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeCoroutineConfiguration(myTestFixture.module, "enable") + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """import org.jetbrains.kotlin.gradle.dsl.Coroutines + | + |kotlin { + | experimental.coroutines = Coroutines.ENABLE + |}""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testChangeCoroutinesSupport() { + val buildScript = createProjectSubFile("build.gradle", """ + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + kotlin { + experimental { + coroutines "error" + } + } + """.trimIndent()) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeCoroutineConfiguration(myTestFixture.module, "enable") + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals(""" + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + kotlin { + experimental { + coroutines "enable" + } + } + """.trimIndent(), LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testChangeCoroutinesSupportGSK() { + val buildScript = createProjectSubFile("build.gradle.kts", + """import org.jetbrains.kotlin.gradle.dsl.Coroutines + | + |kotlin { + | experimental.coroutines = Coroutines.DISABLE + |} + |""".trimMargin("|")) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeCoroutineConfiguration(myTestFixture.module, "enable") + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """import org.jetbrains.kotlin.gradle.dsl.Coroutines + | + |kotlin { + | experimental.coroutines = Coroutines.ENABLE + |} + |""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddLanguageVersion() { + val buildScript = createProjectSubFile("build.gradle", """ + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + """.trimIndent()) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeLanguageVersion(myTestFixture.module, "1.1", null, false) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals(""" + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + compileKotlin { + kotlinOptions { + languageVersion = "1.1" + } + } + """.trimIndent(), LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddLanguageVersionGSK() { + val buildScript = createProjectSubFile("build.gradle.kts", "") + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeLanguageVersion(myTestFixture.module, "1.1", null, false) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + | + |val compileKotlin: KotlinCompile by tasks + |compileKotlin.kotlinOptions { + | languageVersion = "1.1" + |}""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testChangeLanguageVersion() { + val buildScript = createProjectSubFile("build.gradle", """ + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + compileKotlin { + kotlinOptions { + languageVersion = "1.0" + } + } + """.trimIndent()) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeLanguageVersion(myTestFixture.module, "1.1", null, false) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals(""" + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + compileKotlin { + kotlinOptions { + languageVersion = "1.1" + } + } + """.trimIndent(), LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testChangeLanguageVersionGSK() { + val buildScript = createProjectSubFile("build.gradle.kts", + """val compileKotlin: KotlinCompile by tasks + |compileKotlin.kotlinOptions { + | languageVersion = "1.0" + |} + |""".trimMargin("|")) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.changeLanguageVersion(myTestFixture.module, "1.1", null, false) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals( + """val compileKotlin: KotlinCompile by tasks + |compileKotlin.kotlinOptions { + | languageVersion = "1.1" + |} + |""".trimMargin("|"), + LoadTextUtil.loadText(buildScript).toString()) + } + + @Test + fun testAddLibrary() { + val buildScript = createProjectSubFile("build.gradle", """ + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + } + """.trimIndent()) + + importProject() + + runInEdtAndWait { + myTestFixture.project.executeWriteCommand("") { + KotlinWithGradleConfigurator.addKotlinLibraryToModule( + myTestFixture.module, + DependencyScope.COMPILE, + object: ExternalLibraryDescriptor("org.jetbrains.kotlin", "kotlin-reflect", "1.0.0", "1.0.0") { + override fun getLibraryClassesRoots() = emptyList() + }) + } + + FileDocumentManager.getInstance().saveAllDocuments() + } + + assertEquals(""" + buildscript { + repositories { + jcenter() + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0" + } + } + + apply plugin: 'kotlin' + + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-1.1.0" + compile "org.jetbrains.kotlin:kotlin-reflect:1.0.0" + } + """.trimIndent(), LoadTextUtil.loadText(buildScript).toString()) + } + } diff --git a/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractGradleConfigureProjectByChangingFileTest.kt b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractGradleConfigureProjectByChangingFileTest.kt index c2251306916..d67525267b7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractGradleConfigureProjectByChangingFileTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractGradleConfigureProjectByChangingFileTest.kt @@ -34,7 +34,7 @@ abstract class AbstractGradleConfigureProjectByChangingFileTest : AbstractConfig return } - configurator.changeBuildScript(file, true, version, collector) - configurator.changeBuildScript(file, false, version, collector) + configurator.configureBuildScript(file, true, version, collector) + configurator.configureBuildScript(file, false, version, collector) } }