Configuration: Update settings.gradle without loading its PSI

Otherwise dumb-mode problems may happen in latest builds of 182

 #KT-24667 Fixed
This commit is contained in:
Alexey Sedunov
2018-06-06 15:44:00 +03:00
parent c71357dd76
commit 8a09dcf821
5 changed files with 350 additions and 32 deletions
@@ -24,7 +24,6 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModifiableModelsProvider
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.versions.*
import org.jetbrains.plugins.gradle.frameworkSupport.BuildScriptDataBuilder
@@ -78,7 +77,7 @@ abstract class GradleKotlinFrameworkSupportProvider(
if (additionalRepository != null) {
val oneLineRepository = additionalRepository.toGroovyRepositorySnippet().replace('\n', ' ')
updateSettingsScript(module) {
with(KotlinWithGradleConfigurator.getManipulator(it)) {
with(it) {
addPluginRepository(additionalRepository)
addMavenCentralPluginRepository()
}
@@ -126,7 +125,7 @@ abstract class GradleKotlinFrameworkSupportProvider(
}
}
protected open fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) { }
protected open fun updateSettingsScript(settingsBuilder: SettingsScriptBuilder, specifyPluginVersionIfNeeded: Boolean) { }
protected abstract fun getDependencies(sdk: Sdk?): List<String>
protected open fun getTestDependencies(): List<String> = listOf()
@@ -172,9 +171,9 @@ open class GradleKotlinJSFrameworkSupportProvider(
override fun getTestDependencies() = listOf(MAVEN_JS_TEST_ID)
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
override fun updateSettingsScript(settingsBuilder: SettingsScriptBuilder, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin2js")
settingsBuilder.addResolutionStrategy("kotlin2js")
}
}
@@ -189,9 +188,9 @@ open class GradleKotlinMPPCommonFrameworkSupportProvider :
override fun getDependencies(sdk: Sdk?) = listOf(MAVEN_COMMON_STDLIB_ID)
override fun getTestDependencies() = listOf(MAVEN_COMMON_TEST_ID, MAVEN_COMMON_TEST_ANNOTATIONS_ID)
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
override fun updateSettingsScript(settingsBuilder: SettingsScriptBuilder, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin-platform-common")
settingsBuilder.addResolutionStrategy("kotlin-platform-common")
}
}
@@ -216,9 +215,9 @@ class GradleKotlinMPPJavaFrameworkSupportProvider
}
}
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
override fun updateSettingsScript(settingsBuilder: SettingsScriptBuilder, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin-platform-jvm")
settingsBuilder.addResolutionStrategy("kotlin-platform-jvm")
}
}
}
@@ -229,9 +228,9 @@ class GradleKotlinMPPJSFrameworkSupportProvider
override fun getPluginId() = "kotlin-platform-js"
override fun getPluginExpression() = "id 'kotlin-platform-js'"
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
override fun updateSettingsScript(settingsBuilder: SettingsScriptBuilder, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin-platform-js")
settingsBuilder.addResolutionStrategy("kotlin-platform-js")
}
}
@@ -0,0 +1,239 @@
/*
* 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.framework.FrameworkTypeEx
import com.intellij.framework.addSupport.FrameworkSupportInModuleConfigurable
import com.intellij.framework.addSupport.FrameworkSupportInModuleProvider
import com.intellij.ide.util.frameworkSupport.FrameworkSupportModel
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModifiableModelsProvider
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.versions.*
import org.jetbrains.plugins.gradle.frameworkSupport.BuildScriptDataBuilder
import org.jetbrains.plugins.gradle.frameworkSupport.GradleFrameworkSupportProvider
import javax.swing.Icon
import javax.swing.JComponent
import javax.swing.JLabel
abstract class GradleKotlinFrameworkSupportProvider(
val frameworkTypeId: String,
val displayName: String,
val frameworkIcon: Icon
) : GradleFrameworkSupportProvider() {
override fun getFrameworkType(): FrameworkTypeEx = object : FrameworkTypeEx(frameworkTypeId) {
override fun getIcon(): Icon = frameworkIcon
override fun getPresentableName(): String = displayName
override fun createProvider(): FrameworkSupportInModuleProvider = this@GradleKotlinFrameworkSupportProvider
}
override fun createConfigurable(model: FrameworkSupportModel): FrameworkSupportInModuleConfigurable {
val configurable = KotlinGradleFrameworkSupportInModuleConfigurable(model, this)
return object : FrameworkSupportInModuleConfigurable() {
override fun addSupport(module: Module, rootModel: ModifiableRootModel, modifiableModelsProvider: ModifiableModelsProvider) {
configurable.addSupport(module, rootModel, modifiableModelsProvider)
}
override fun createComponent(): JComponent = JLabel(getDescription())
}
}
override fun addSupport(
module: Module,
rootModel: ModifiableRootModel,
modifiableModelsProvider: ModifiableModelsProvider,
buildScriptData: BuildScriptDataBuilder
) {
addSupport(buildScriptData, module, rootModel.sdk, true)
}
open fun addSupport(buildScriptData: BuildScriptDataBuilder, module: Module, sdk: Sdk?, specifyPluginVersionIfNeeded: Boolean) {
var kotlinVersion = bundledRuntimeVersion()
val additionalRepository = getRepositoryForVersion(kotlinVersion)
if (isSnapshot(bundledRuntimeVersion())) {
kotlinVersion = LAST_SNAPSHOT_VERSION
}
val useNewSyntax = buildScriptData.gradleVersion >= MIN_GRADLE_VERSION_FOR_NEW_PLUGIN_SYNTAX
if (useNewSyntax) {
if (additionalRepository != null) {
val oneLineRepository = additionalRepository.toGroovyRepositorySnippet().replace('\n', ' ')
updateSettingsScript(module) {
with(KotlinWithGradleConfigurator.getManipulator(it)) {
addPluginRepository(additionalRepository)
addMavenCentralPluginRepository()
}
}
buildScriptData.addRepositoriesDefinition("mavenCentral()")
buildScriptData.addRepositoriesDefinition(oneLineRepository)
}
buildScriptData.addPluginDefinitionInPluginsGroup(
getPluginExpression() + if (specifyPluginVersionIfNeeded) " version '$kotlinVersion'" else ""
)
} else {
if (additionalRepository != null) {
val oneLineRepository = additionalRepository.toGroovyRepositorySnippet().replace('\n', ' ')
buildScriptData.addBuildscriptRepositoriesDefinition(oneLineRepository)
buildScriptData.addRepositoriesDefinition("mavenCentral()")
buildScriptData.addRepositoriesDefinition(oneLineRepository)
}
buildScriptData
.addPluginDefinition(KotlinWithGradleConfigurator.getGroovyApplyPluginDirective(getPluginId()))
.addBuildscriptRepositoriesDefinition("mavenCentral()")
.addBuildscriptPropertyDefinition("ext.kotlin_version = '$kotlinVersion'")
}
buildScriptData.addRepositoriesDefinition("mavenCentral()")
for (dependency in getDependencies(sdk)) {
buildScriptData.addDependencyNotation(KotlinWithGradleConfigurator.getGroovyDependencySnippet(dependency, "compile", !useNewSyntax))
}
for (dependency in getTestDependencies()) {
buildScriptData.addDependencyNotation(
if (":" in dependency)
"testCompile \"$dependency\""
else
KotlinWithGradleConfigurator.getGroovyDependencySnippet(dependency, "testCompile", !useNewSyntax)
)
}
if (useNewSyntax) {
updateSettingsScript(module) { updateSettingsScript(it, specifyPluginVersionIfNeeded) }
} else {
buildScriptData.addBuildscriptDependencyNotation(KotlinWithGradleConfigurator.CLASSPATH)
}
}
protected open fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) { }
protected abstract fun getDependencies(sdk: Sdk?): List<String>
protected open fun getTestDependencies(): List<String> = listOf()
protected abstract fun getPluginId(): String
protected abstract fun getPluginExpression(): String
protected abstract fun getDescription(): String
}
open class GradleKotlinJavaFrameworkSupportProvider(
frameworkTypeId: String = "KOTLIN",
displayName: String = "Kotlin (Java)"
) : GradleKotlinFrameworkSupportProvider(frameworkTypeId, displayName, KotlinIcons.SMALL_LOGO) {
override fun getPluginId() = KotlinGradleModuleConfigurator.KOTLIN
override fun getPluginExpression() = "id 'org.jetbrains.kotlin.jvm'"
override fun getDependencies(sdk: Sdk?) = listOf(getStdlibArtifactId(sdk, bundledRuntimeVersion()))
override fun addSupport(buildScriptData: BuildScriptDataBuilder, module: Module, sdk: Sdk?, specifyPluginVersionIfNeeded: Boolean) {
super.addSupport(buildScriptData, module, sdk, specifyPluginVersionIfNeeded)
val jvmTarget = getDefaultJvmTarget(sdk, bundledRuntimeVersion())
if (jvmTarget != null) {
val description = jvmTarget.description
buildScriptData.addOther("compileKotlin {\n kotlinOptions.jvmTarget = \"$description\"\n}\n\n")
buildScriptData.addOther("compileTestKotlin {\n kotlinOptions.jvmTarget = \"$description\"\n}\n")
}
}
override fun getDescription() = "A Kotlin library or application targeting the JVM"
}
open class GradleKotlinJSFrameworkSupportProvider(
frameworkTypeId: String = "KOTLIN_JS",
displayName: String = "Kotlin (JavaScript)"
) : GradleKotlinFrameworkSupportProvider(frameworkTypeId, displayName, KotlinIcons.JS) {
override fun getPluginId() = KotlinJsGradleModuleConfigurator.KOTLIN_JS
override fun getPluginExpression() = "id 'kotlin2js'"
override fun getDependencies(sdk: Sdk?) = listOf(MAVEN_JS_STDLIB_ID)
override fun getTestDependencies() = listOf(MAVEN_JS_TEST_ID)
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin2js")
}
}
override fun getDescription() = "A Kotlin library or application targeting JavaScript"
}
open class GradleKotlinMPPCommonFrameworkSupportProvider :
GradleKotlinFrameworkSupportProvider("KOTLIN_MPP_COMMON", "Kotlin (Multiplatform Common - Experimental)", KotlinIcons.MPP) {
override fun getPluginId() = "kotlin-platform-common"
override fun getPluginExpression() = "id 'kotlin-platform-common'"
override fun getDependencies(sdk: Sdk?) = listOf(MAVEN_COMMON_STDLIB_ID)
override fun getTestDependencies() = listOf(MAVEN_COMMON_TEST_ID, MAVEN_COMMON_TEST_ANNOTATIONS_ID)
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin-platform-common")
}
}
override fun getDescription() = "Shared code for a Kotlin multiplatform project (targeting JVM and JS)"
}
class GradleKotlinMPPJavaFrameworkSupportProvider
: GradleKotlinJavaFrameworkSupportProvider("KOTLIN_MPP_JVM", "Kotlin (Multiplatform JVM - Experimental)") {
override fun getPluginId() = "kotlin-platform-jvm"
override fun getPluginExpression() = "id 'kotlin-platform-jvm'"
override fun getDescription() = "JVM-specific code for a Kotlin multiplatform project"
override fun getTestDependencies() = listOf(MAVEN_TEST_ID, MAVEN_TEST_JUNIT_ID, "junit:junit:4.12")
override fun addSupport(buildScriptData: BuildScriptDataBuilder, module: Module, sdk: Sdk?, specifyPluginVersionIfNeeded: Boolean) {
super.addSupport(buildScriptData, module, sdk, specifyPluginVersionIfNeeded)
val jvmTarget = getDefaultJvmTarget(sdk, bundledRuntimeVersion())
if (jvmTarget != null) {
val description = jvmTarget.description
buildScriptData.addOther("sourceCompatibility = \"$description\"\n\n")
}
}
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin-platform-jvm")
}
}
}
class GradleKotlinMPPJSFrameworkSupportProvider
: GradleKotlinJSFrameworkSupportProvider("KOTLIN_MPP_JS", "Kotlin (Multiplatform JS - Experimental)") {
override fun getPluginId() = "kotlin-platform-js"
override fun getPluginExpression() = "id 'kotlin-platform-js'"
override fun updateSettingsScript(settingsScript: PsiFile, specifyPluginVersionIfNeeded: Boolean) {
if (specifyPluginVersionIfNeeded) {
KotlinWithGradleConfigurator.getManipulator(settingsScript).addResolutionStrategy("kotlin-platform-js")
}
}
override fun getDescription() = "JavaScript-specific code for a Kotlin multiplatform project"
}
@@ -63,7 +63,7 @@ abstract class KotlinDslGradleKotlinFrameworkSupportProvider(
if (additionalRepository != null) {
val repository = additionalRepository.toKotlinRepositorySnippet()
updateSettingsScript(module) {
with(KotlinWithGradleConfigurator.getManipulator(it)) {
with(it) {
addPluginRepository(additionalRepository)
addMavenCentralPluginRepository()
}
@@ -148,7 +148,7 @@ class KotlinDslGradleKotlinJSFrameworkSupportProvider :
) {
super.addSupport(projectId, module, rootModel, modifiableModelsProvider, buildScriptData)
updateSettingsScript(module) {
KotlinWithGradleConfigurator.getManipulator(it).addResolutionStrategy("kotlin2js")
it.addResolutionStrategy("kotlin2js")
}
}
}
@@ -30,7 +30,6 @@ import org.jetbrains.kotlin.idea.util.rootManager
import org.jetbrains.plugins.gradle.frameworkSupport.BuildScriptDataBuilder
import org.jetbrains.plugins.gradle.service.project.wizard.GradleModuleBuilder
import org.jetbrains.plugins.gradle.service.settings.GradleProjectSettingsControl
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory
import javax.swing.Icon
class KotlinGradleMultiplatformModuleBuilder : GradleModuleBuilder() {
@@ -82,9 +81,7 @@ class KotlinGradleMultiplatformModuleBuilder : GradleModuleBuilder() {
updateSettingsScript(module) {
val includedModules = listOfNotNull(commonModuleName, jvmModuleName, jsModuleName).filter { it.isNotEmpty() }
if (includedModules.isNotEmpty()) {
val includeCall = GroovyPsiElementFactory.getInstance(module.project)
.createExpressionFromText(includedModules.joinToString(prefix = "include ") { "'$it'" })
it.add(includeCall)
it.addIncludedModules(includedModules)
}
}
} finally {
@@ -9,25 +9,24 @@ import com.intellij.framework.addSupport.FrameworkSupportInModuleConfigurable
import com.intellij.ide.util.frameworkSupport.FrameworkSupportModel
import com.intellij.openapi.externalSystem.model.project.ProjectId
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.roots.ModifiableModelsProvider
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
import org.jetbrains.kotlin.psi.UserDataProperty
import org.jetbrains.plugins.gradle.frameworkSupport.GradleFrameworkSupportProvider
import org.jetbrains.plugins.gradle.service.project.wizard.GradleModuleBuilder
import org.jetbrains.plugins.gradle.util.GradleConstants
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory
import java.io.File
internal var Module.gradleModuleBuilder: GradleModuleBuilder? by UserDataProperty(Key.create("GRADLE_MODULE_BUILDER"))
private var Module.editableSettingsPsiFileCopy: PsiFile? by UserDataProperty(Key.create("EDITABLE_SETTINGS_PSI_FILE_COPY"))
private var Module.settingsScriptBuilder: SettingsScriptBuilder? by UserDataProperty(Key.create("SETTINGS_SCRIPT_BUILDER"))
internal fun findSettingsGradleFile(module: Module): VirtualFile? {
val contentEntryPath = module.gradleModuleBuilder?.contentEntryPath ?: return null
@@ -38,28 +37,112 @@ internal fun findSettingsGradleFile(module: Module): VirtualFile? {
?: module.project.baseDir.findChild(GradleConstants.SETTINGS_FILE_NAME)
}
class SettingsScriptBuilder(scriptFile: GroovyFile) {
private val builder = StringBuilder(scriptFile.text)
private fun findBlockBody(blockName: String, startFrom: Int = 0): Int {
val blockOffset = builder.indexOf(blockName, startFrom)
if (blockOffset < 0) return -1
return builder.indexOf('{', blockOffset + 1) + 1
}
private fun getOrPrependTopLevelBlockBody(blockName: String): Int {
val blockBody = findBlockBody(blockName)
if (blockBody >= 0) return blockBody
builder.insert(0, "$blockName {}\n")
return findBlockBody(blockName)
}
private fun getOrAppendInnerBlockBody(blockName: String, offset: Int): Int {
val repositoriesBody = findBlockBody(blockName, offset)
if (repositoriesBody >= 0) return repositoriesBody
builder.insert(offset, "\n$blockName {}\n")
return findBlockBody(blockName, offset)
}
private fun appendExpressionToBlockIfAbsent(expression: String, offset: Int) {
var braceCount = 1
var blockEnd = offset
for (i in offset..builder.lastIndex) {
when (builder[i]) {
'{' -> braceCount++
'}' -> braceCount--
}
if (braceCount == 0) {
blockEnd = i
break
}
}
if (!builder.substring(offset, blockEnd).contains(expression.trim())) {
builder.insert(blockEnd, "\n$expression\n")
}
}
private fun getOrCreatePluginManagementBody() = getOrPrependTopLevelBlockBody("pluginManagement")
private fun addPluginRepositoryExpression(expression: String) {
val repositoriesBody = getOrAppendInnerBlockBody("repositories", getOrCreatePluginManagementBody())
appendExpressionToBlockIfAbsent(expression, repositoriesBody)
}
fun addMavenCentralPluginRepository() {
addPluginRepositoryExpression("mavenCentral()")
}
fun addPluginRepository(repository: RepositoryDescription) {
addPluginRepositoryExpression(repository.toGroovyRepositorySnippet())
}
fun addResolutionStrategy(pluginId: String) {
val resolutionStrategyBody = getOrAppendInnerBlockBody("resolutionStrategy", getOrCreatePluginManagementBody())
val eachPluginBody = getOrAppendInnerBlockBody("eachPlugin", resolutionStrategyBody)
appendExpressionToBlockIfAbsent(
"""
if (requested.id.id == "$pluginId") {
useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}{requested.version}")
}
""".trimIndent(),
eachPluginBody
)
}
fun addIncludedModules(modules: List<String>) {
builder.append(modules.joinToString(prefix = "include ", postfix = "\n") { "'$it'" })
}
fun build() = builder.toString()
}
// Circumvent write actions and modify the file directly
// TODO: Get rid of this hack when IDEA API allows manipulation of settings script similarly to the main script itself
internal fun updateSettingsScript(module: Module, updater: (PsiFile) -> Unit) {
val storedCopy = module.editableSettingsPsiFileCopy
val settingsPsiCopy = storedCopy ?: findSettingsGradleFile(module)?.toPsiFile(module.project)?.copied() ?: return
if (storedCopy == null) {
module.editableSettingsPsiFileCopy = settingsPsiCopy
internal fun updateSettingsScript(module: Module, updater: (SettingsScriptBuilder) -> Unit) {
val storedSettingsBuilder = module.settingsScriptBuilder
val settingsBuilder =
storedSettingsBuilder
?: (findSettingsGradleFile(module)?.toPsiFile(module.project) as? GroovyFile)?.let { SettingsScriptBuilder(it) }
?: return
if (storedSettingsBuilder == null) {
module.settingsScriptBuilder = settingsBuilder
}
DumbService.getInstance(module.project).runWithAlternativeResolveEnabled<Throwable> { updater(settingsPsiCopy) }
updater(settingsBuilder)
}
internal fun flushSettingsGradleCopy(module: Module) {
try {
val settingsFile = findSettingsGradleFile(module)
val settingsPsiCopy = module.editableSettingsPsiFileCopy
if (settingsPsiCopy != null && settingsFile != null) {
CodeStyleManager.getInstance(module.project).reformat(settingsPsiCopy)
VfsUtil.saveText(settingsFile, settingsPsiCopy.text)
val settingsScriptBuilder = module.settingsScriptBuilder
if (settingsScriptBuilder != null && settingsFile != null) {
val project = module.project
val tmpFile =
GroovyPsiElementFactory
.getInstance(project)
.createGroovyFile(settingsScriptBuilder.build(), false, null)
CodeStyleManager.getInstance(project).reformat(tmpFile)
VfsUtil.saveText(settingsFile, tmpFile.text)
}
} finally {
module.gradleModuleBuilder = null
module.editableSettingsPsiFileCopy = null
module.settingsScriptBuilder = null
}
}