diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt index b6d1511e883..b31caa5f9a3 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt @@ -10,14 +10,19 @@ package org.jetbrains.kotlin.idea.scripting.gradle import com.intellij.notification.* import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemUtil import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.vfs.VirtualFile +import org.gradle.util.GradleVersion import org.jetbrains.kotlin.idea.KotlinIcons import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle +import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager +import org.jetbrains.kotlin.idea.util.application.getServiceSafe import org.jetbrains.kotlin.psi.UserDataProperty import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.util.GradleConstants @@ -37,26 +42,24 @@ fun runPartialGradleImport(project: Project) { fun getMissingConfigurationActionText() = KotlinIdeaGradleBundle.message("action.label.import.project") -fun autoReloadScriptConfigurations(project: Project): Boolean { - val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) - val projectSettings = gradleSettings.getLinkedProjectsSettings() - .filterIsInstance() - .firstOrNull() - if (projectSettings != null) { - return projectSettings.isUseAutoImport - } +fun autoReloadScriptConfigurations(project: Project, file: VirtualFile): Boolean { + val path = GradleBuildRootsManager.getInstance(project) + .getScriptInfo(file)?.buildRoot?.pathPrefix ?: return false - return false + return ExternalSystemApiUtil + .getSettings(project, GradleConstants.SYSTEM_ID) + .getLinkedProjectSettings(path) + ?.isUseAutoImport ?: false } private const val kotlinDslNotificationGroupId = "Gradle Kotlin DSL Scripts" private var Project.notificationPanel: ScriptConfigurationChangedNotification? by UserDataProperty(Key.create("load.script.configuration.panel")) -fun scriptConfigurationsNeedToBeUpdated(project: Project) { +fun scriptConfigurationsNeedToBeUpdated(project: Project, file: VirtualFile) { if (!scriptConfigurationsNeedToBeUpdatedBalloon) return - if (autoReloadScriptConfigurations(project)) { + if (autoReloadScriptConfigurations(project, file)) { // import should be run automatically by Gradle plugin return } @@ -98,12 +101,8 @@ private class ScriptConfigurationChangedNotification(val project: Project) : init { addAction(LoadConfigurationAction()) addAction(NotificationAction.createSimple(KotlinIdeaGradleBundle.message("action.label.enable.auto.import")) { - val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) - val projectSettings = gradleSettings.getLinkedProjectsSettings() - .filterIsInstance() - .firstOrNull() - if (projectSettings != null) { - projectSettings.isUseAutoImport = true + getGradleProjectSettings(project).forEach { + it.isUseAutoImport = true } runPartialGradleImport(project) }) diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 index 79cdd03a248..48ee9f21ae6 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleImportHelper.kt.201 @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModelResolver import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager import org.jetbrains.plugins.gradle.service.project.GradlePartialResolverPolicy +import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.util.GradleConstants fun runPartialGradleImport(project: Project) { @@ -39,17 +40,17 @@ fun runPartialGradleImport(project: Project) { } } -fun getMissingConfigurationNotificationText() = KotlinIdeaGradleBundle.message("script.configurations.will.be.available.after.load.changes") fun getMissingConfigurationActionText() = KotlinIdeaGradleBundle.message("action.text.load.script.configurations") -fun autoReloadScriptConfigurations(project: Project): Boolean { - return GradleScriptDefinitionsContributor.getDefinitions(project).any { +fun autoReloadScriptConfigurations(project: Project, file: VirtualFile): Boolean { + val buildRoot = GradleBuildRootsManager.getInstance(project).getScriptInfo(file)?.buildRoot ?: return false + return GradleScriptDefinitionsContributor.getDefinitions(project, buildRoot.pathPrefix, buildRoot.data.gradleHome)?.any { KotlinScriptingSettings.getInstance(project).autoReloadConfigurations(it) - } + } ?: return false } -fun scriptConfigurationsNeedToBeUpdated(project: Project) { - if (autoReloadScriptConfigurations(project)) { +fun scriptConfigurationsNeedToBeUpdated(project: Project, file: VirtualFile) { + if (autoReloadScriptConfigurations(project, file)) { runPartialGradleImport(project) } else { // notification is shown in LoadConfigurationAction @@ -86,7 +87,7 @@ class LoadConfigurationAction : AnAction( val project = editor.project ?: return false val file = getKotlinScriptFile(editor) ?: return false - if (autoReloadScriptConfigurations(project)) { + if (autoReloadScriptConfigurations(project, file)) { return false } @@ -94,13 +95,13 @@ class LoadConfigurationAction : AnAction( } private fun getKotlinScriptFile(editor: Editor): VirtualFile? { - return FileDocumentManager.getInstance() - .getFile(editor.document) - ?.takeIf { - it !is LightVirtualFileBase - && it.isValid - && it.fileType != KotlinFileType.INSTANCE - && isGradleKotlinScript(it) - } - } + return FileDocumentManager.getInstance() + .getFile(editor.document) + ?.takeIf { + it !is LightVirtualFileBase + && it.isValid + && it.fileType == KotlinFileType.INSTANCE + && isGradleKotlinScript(it) + } + } } \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt index a2fe2e6a5c2..e3a0ac4ed18 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/GradleScriptDefinitionsProvider.kt @@ -6,29 +6,33 @@ package org.jetbrains.kotlin.idea.scripting.gradle import com.intellij.execution.configurations.CommandLineTokenizer -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.project.Project import com.intellij.util.EnvironmentUtil +import com.intellij.util.PathUtil +import org.gradle.util.GradleVersion import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionSourceAsContributor import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager import org.jetbrains.kotlin.idea.core.script.loadDefinitionsFromTemplates import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslSyncListener +import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager +import org.jetbrains.kotlin.idea.scripting.gradle.roots.Imported +import org.jetbrains.kotlin.idea.scripting.gradle.roots.WithoutScriptModels import org.jetbrains.kotlin.scripting.definitions.KotlinScriptDefinitionAdapterFromNewAPIBase import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.getEnvironment import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter +import org.jetbrains.plugins.gradle.service.GradleInstallationManager import org.jetbrains.plugins.gradle.settings.DistributionType import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings -import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.settings.GradleSettingsListener import org.jetbrains.plugins.gradle.util.GradleConstants import java.io.File import java.util.* -import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.ConcurrentHashMap import kotlin.reflect.KClass import kotlin.script.dependencies.Environment import kotlin.script.dependencies.ScriptContents @@ -42,23 +46,236 @@ import kotlin.script.templates.standard.ScriptTemplateWithArgs class GradleScriptDefinitionsContributor(private val project: Project) : ScriptDefinitionSourceAsContributor { companion object { - fun getDefinitions(project: Project) = - ScriptDefinitionContributor.EP_NAME.getExtensions(project) + fun getDefinitions(project: Project, workingDir: String, gradleHome: String?): List? { + val contributor = ScriptDefinitionContributor.EP_NAME.getExtensions(project) .filterIsInstance() - .single().definitions.toList() + .singleOrNull() - fun getDefinitionsTemplateClasspath(project: Project): MutableSet = - mutableSetOf().also { result -> - getDefinitions(project).forEach { definition -> - definition.asLegacyOrNull() - ?.templateClasspath - ?.mapNotNullTo(result) { it.path } - } + if (contributor == null) { + scriptingInfoLog( + "cannot find gradle script definitions contributor in ScriptDefinitionContributor.EP_NAME list: " + + "workingDir=$workingDir gradleHome=$gradleHome" + ) + return null } + if (gradleHome == null) { + scriptingInfoLog(KotlinIdeaGradleBundle.message("error.text.unable.to.get.gradle.home.directory") + ": workingDir=$workingDir gradleHome=$gradleHome") + return null + } + + val root = LightGradleBuildRoot(workingDir, gradleHome) + val definitions = contributor.definitionsByRoots[root] + if (definitions == null) { + scriptingInfoLog( + "script definitions aren't loaded yet. " + + "They should be loaded by invoking GradleScriptDefinitionsContributor.reloadIfNeeded from KotlinDslSyncListener: " + + "workingDir=$workingDir gradleHome=$gradleHome" + ) + return null + } + return definitions + } + + fun getDefinitionsTemplateClasspath(gradleHome: String?): List { + return try { + getFullDefinitionsClasspath(gradleHome).first.map { + PathUtil.toSystemIndependentName(it.path) + } + } catch (e: Throwable) { + scriptingInfoLog("cannot get gradle classpath for Gradle Kotlin DSL scripts: ${e.message}") + + emptyList() + } + } + + private val kotlinDslDependencySelector = Regex("^gradle-(?:kotlin-dsl|core).*\\.jar\$") + + private fun getFullDefinitionsClasspath(gradleHome: String?): Pair, List> { + if (gradleHome == null) { + error(KotlinIdeaGradleBundle.message("error.text.unable.to.get.gradle.home.directory")) + } + + val gradleLibDir = File(gradleHome, "lib") + .let { + it.takeIf { it.exists() && it.isDirectory } + ?: error(KotlinIdeaGradleBundle.message("error.text.invalid.gradle.libraries.directory", it)) + } + + val templateClasspath = gradleLibDir + /* an inference problem without explicit 'it', TODO: remove when fixed */ + .listFiles { it -> kotlinDslDependencySelector.matches(it.name) } + ?.takeIf { it.isNotEmpty() } + ?.asList() + ?: error(KotlinIdeaGradleBundle.message("error.text.missing.jars.in.gradle.directory")) + + scriptingDebugLog { "gradle script templates classpath $templateClasspath" } + + val additionalClassPath = kotlinStdlibAndCompiler(gradleLibDir) + + scriptingDebugLog { "gradle script templates additional classpath $templateClasspath" } + + return templateClasspath to additionalClassPath + } + + // TODO: check this against kotlin-dsl branch that uses daemon + private fun kotlinStdlibAndCompiler(gradleLibDir: File): List { + // additionally need compiler jar to load gradle resolver + return gradleLibDir.listFiles { file -> + file?.name?.startsWith("kotlin-compiler-embeddable") == true || file?.name?.startsWith("kotlin-stdlib") == true + }?.firstOrNull()?.let(::listOf).orEmpty() + } } override val id: String = "Gradle Kotlin DSL" - private val failedToLoad = AtomicBoolean(false) + + private data class LightGradleBuildRoot(val workingDir: String, val gradleHome: String?) + + private val definitionsByRoots = ConcurrentHashMap>() + + private fun LightGradleBuildRoot.markAsError() { + definitionsByRoots[this] = listOf(ErrorGradleScriptDefinition(project)) + } + + private fun LightGradleBuildRoot.isError(): Boolean { + return definitionsByRoots[this]?.any { it is ErrorGradleScriptDefinition } ?: false + } + + private fun forceReload() { + for (key in definitionsByRoots.keys()) { + key.markAsError() + } + ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this) + } + + // TODO: remove old roots + fun reloadIfNeeded(workingDir: String, gradleHome: String?) { + val root = LightGradleBuildRoot(workingDir, gradleHome) + val value = definitionsByRoots[root] + if (value != null) { + if (root.isError()) { + ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this) + } + } else { + val rootWithChangedGradleHome = definitionsByRoots.filter { it.key.workingDir == workingDir } + if (rootWithChangedGradleHome.isNotEmpty()) { + rootWithChangedGradleHome.forEach { + definitionsByRoots.remove(it.key) + } + } + root.markAsError() + ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this) + } + } + + private fun loadGradleDefinitions(root: LightGradleBuildRoot): List { + try { + val (templateClasspath, additionalClassPath) = getFullDefinitionsClasspath(root.gradleHome) + + val kotlinDslTemplates = ArrayList() + + val projectPath = root.workingDir + loadGradleTemplates( + projectPath, + templateClass = "org.gradle.kotlin.dsl.KotlinInitScript", + root.gradleHome, + templateClasspath, + additionalClassPath + ).let { kotlinDslTemplates.addAll(it) } + + loadGradleTemplates( + projectPath, + templateClass = "org.gradle.kotlin.dsl.KotlinSettingsScript", + gradleHome = root.gradleHome, + templateClasspath = templateClasspath, + additionalClassPath = additionalClassPath + ).let { kotlinDslTemplates.addAll(it) } + + // KotlinBuildScript should be last because it has wide scriptFilePattern + loadGradleTemplates( + projectPath, + templateClass = "org.gradle.kotlin.dsl.KotlinBuildScript", + gradleHome = root.gradleHome, + templateClasspath = templateClasspath, + additionalClassPath = additionalClassPath + ).let { kotlinDslTemplates.addAll(it) } + + + if (kotlinDslTemplates.isNotEmpty()) { + return kotlinDslTemplates.distinct() + } + } catch (t: Throwable) { + // TODO: review exception handling + + if (t is IllegalStateException) { + scriptingInfoLog("IllegalStateException loading gradle script templates: ${t.message}") + } else { + scriptingDebugLog { "error loading gradle script templates ${t.message}" } + } + + return listOf(ErrorGradleScriptDefinition(project, t.message)) + } + + return listOf(ErrorGradleScriptDefinition(project)) + + } + + private fun loadGradleTemplates( + projectPath: String, + templateClass: String, + gradleHome: String?, + templateClasspath: List, + additionalClassPath: List + ): List { + val gradleExeSettings = ExternalSystemApiUtil.getExecutionSettings( + project, + projectPath, + GradleConstants.SYSTEM_ID + ) + val hostConfiguration = createHostConfiguration(projectPath, gradleHome, gradleExeSettings) + return loadDefinitionsFromTemplates( + listOf(templateClass), + templateClasspath, + hostConfiguration, + additionalClassPath + ).map { + it.asLegacyOrNull()?.let { legacyDef -> + // Expand scope for old gradle script definition + val version = GradleInstallationManager.getGradleVersion(gradleHome) ?: GradleVersion.current().version + GradleKotlinScriptDefinitionWrapper( + it.hostConfiguration, + legacyDef, + version + ) + } ?: it + } + } + + private fun createHostConfiguration( + projectPath: String, + gradleHome: String?, + gradleExeSettings: GradleExecutionSettings + ): ScriptingHostConfiguration { + val gradleJvmOptions = gradleExeSettings.daemonVmOptions?.let { vmOptions -> + CommandLineTokenizer(vmOptions).toList() + .mapNotNull { it?.let { it as? String } } + .filterNot(String::isBlank) + .distinct() + } ?: emptyList() + + val environment = mapOf( + "gradleHome" to gradleHome?.let(::File), + "gradleJavaHome" to gradleExeSettings.javaHome, // TODO: should be taken from information from gradle sync + + "projectRoot" to projectPath.let(::File), + + "gradleOptions" to emptyList(), // There is no option in UI to set project wide gradleOptions + "gradleJvmOptions" to gradleJvmOptions, + "gradleEnvironmentVariables" to if (gradleExeSettings.isPassParentEnvs) EnvironmentUtil.getEnvironmentMap() else emptyMap() + ) + return ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) { + getEnvironment { environment } + } + } init { subscribeToGradleSettingChanges() @@ -67,19 +284,19 @@ class GradleScriptDefinitionsContributor(private val project: Project) : ScriptD private fun subscribeToGradleSettingChanges() { val listener = object : GradleSettingsListenerAdapter() { override fun onGradleVmOptionsChange(oldOptions: String?, newOptions: String?) { - reload() + forceReload() } override fun onGradleHomeChange(oldPath: String?, newPath: String?, linkedProjectPath: String) { - reload() + forceReload() } override fun onServiceDirectoryPathChange(oldPath: String?, newPath: String?) { - reload() + forceReload() } override fun onGradleDistributionTypeChange(currentValue: DistributionType?, linkedProjectPath: String) { - reload() + forceReload() } } project.messageBus.connect().subscribe(GradleSettingsListener.TOPIC, listener) @@ -89,214 +306,87 @@ class GradleScriptDefinitionsContributor(private val project: Project) : ScriptD // TODO: possibly combine exceptions from every loadGradleTemplates call, be mindful of KT-19276 override val definitions: Sequence get() { - val kotlinDslDependencySelector = Regex("^gradle-(?:kotlin-dsl|core).*\\.jar\$") - val kotlinDslAdditionalResolverCp = ::kotlinStdlibAndCompiler - - failedToLoad.set(false) - - val kotlinDslTemplates = ArrayList() - - loadGradleTemplates( - templateClass = "org.gradle.kotlin.dsl.KotlinInitScript", - dependencySelector = kotlinDslDependencySelector, - additionalResolverClasspath = kotlinDslAdditionalResolverCp - - ).let { kotlinDslTemplates.addAll(it) } - - loadGradleTemplates( - templateClass = "org.gradle.kotlin.dsl.KotlinSettingsScript", - dependencySelector = kotlinDslDependencySelector, - additionalResolverClasspath = kotlinDslAdditionalResolverCp - - ).let { kotlinDslTemplates.addAll(it) } - - // KotlinBuildScript should be last because it has wide scriptFilePattern - loadGradleTemplates( - templateClass = "org.gradle.kotlin.dsl.KotlinBuildScript", - dependencySelector = kotlinDslDependencySelector, - additionalResolverClasspath = kotlinDslAdditionalResolverCp - ).let { kotlinDslTemplates.addAll(it) } - - - if (kotlinDslTemplates.isNotEmpty()) { - return kotlinDslTemplates.distinct().asSequence() + definitionsByRoots.keys().iterator().forEachRemaining { root -> + // reload definitions marked as error + if (root.isError()) { + definitionsByRoots[root] = loadGradleDefinitions(root) + } } - - val default = tryToLoadOldBuildScriptDefinition() - if (default.isNotEmpty()) { - return default.distinct().asSequence() + if (definitionsByRoots.isEmpty()) { + // can be empty in case when import wasn't done from IDE start up, + // otherwise KotlinDslSyncListener should run reloadIfNeeded for valid roots + GradleBuildRootsManager.getInstance(project).getAllRoots().forEach { + val workingDir = it.pathPrefix + val gradleHome = when (it) { + is Imported -> { + it.data.gradleHome + } + is WithoutScriptModels -> { + ExternalSystemApiUtil.getExecutionSettings( + project, + workingDir, + GradleConstants.SYSTEM_ID + ).gradleHome + } + } + val root = LightGradleBuildRoot(workingDir, gradleHome) + definitionsByRoots[root] = loadGradleDefinitions(root) + } + } + if (definitionsByRoots.isNotEmpty()) { + return definitionsByRoots.flatMap { it.value }.asSequence() } - return sequenceOf(ErrorGradleScriptDefinition(project)) } - private fun tryToLoadOldBuildScriptDefinition(): List { - failedToLoad.set(false) - return loadGradleTemplates( - templateClass = "org.gradle.script.lang.kotlin.KotlinBuildScript", - dependencySelector = Regex("^gradle-(?:script-kotlin|core).*\\.jar\$"), - additionalResolverClasspath = { emptyList() } - ) - } +} - // TODO: check this against kotlin-dsl branch that uses daemon - private fun kotlinStdlibAndCompiler(gradleLibDir: File): List { - // additionally need compiler jar to load gradle resolver - return gradleLibDir.listFiles { file -> - file.name.startsWith("kotlin-compiler-embeddable") || file.name.startsWith("kotlin-stdlib") +// TODO: refactor - minimize +private class ErrorGradleScriptDefinition(project: Project, message: String? = null) : + ScriptDefinition.FromLegacy( + ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration), + LegacyDefinition(project, message) + ) { + + private class LegacyDefinition(project: Project, message: String?) : KotlinScriptDefinitionAdapterFromNewAPIBase() { + companion object { + private const val KOTLIN_DSL_SCRIPT_EXTENSION = "gradle.kts" } - .firstOrNull()?.let(::listOf).orEmpty() + + override val name: String get() = KotlinIdeaGradleBundle.message("text.default.kotlin.gradle.script") + override val fileExtension: String = KOTLIN_DSL_SCRIPT_EXTENSION + + override val scriptCompilationConfiguration: ScriptCompilationConfiguration = ScriptCompilationConfiguration.Default + override val hostConfiguration: ScriptingHostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) + override val baseClass: KClass<*> = ScriptTemplateWithArgs::class + + override val dependencyResolver: DependenciesResolver = + ErrorScriptDependenciesResolver(project, message) + + override fun toString(): String = "ErrorGradleScriptDefinition" } - private fun loadGradleTemplates( - templateClass: String, dependencySelector: Regex, - additionalResolverClasspath: (gradleLibDir: File) -> List - ): List = try { - doLoadGradleTemplates(templateClass, dependencySelector, additionalResolverClasspath) - } catch (t: Throwable) { - scriptingDebugLog { "error loading gradle script templates ${t.message}" } - // TODO: review exception handling - failedToLoad.set(true) - if (t is IllegalStateException) { - Logger.getInstance(GradleScriptDefinitionsContributor::class.java) - .info("[kts] error loading gradle script templates: ${t.message}") - } - listOf(ErrorGradleScriptDefinition(project, t.message)) - } + override fun equals(other: Any?): Boolean = other is ErrorGradleScriptDefinition + override fun hashCode(): Int = name.hashCode() +} - - private fun doLoadGradleTemplates( - templateClass: String, dependencySelector: Regex, - additionalResolverClasspath: (gradleLibDir: File) -> List - ): List { - fun createHostConfiguration( - gradleExeSettings: GradleExecutionSettings, - projectSettings: GradleProjectSettings - ): ScriptingHostConfiguration { - val gradleJvmOptions = gradleExeSettings.daemonVmOptions?.let { vmOptions -> - CommandLineTokenizer(vmOptions).toList() - .mapNotNull { it?.let { it as? String } } - .filterNot(String::isBlank) - .distinct() - } ?: emptyList() - - - val environment = mapOf( - "gradleHome" to gradleExeSettings.gradleHome?.let(::File), - "gradleJavaHome" to gradleExeSettings.javaHome, - - "projectRoot" to projectSettings.externalProjectPath.let(::File), - - "gradleOptions" to emptyList(), // There is no option in UI to set project wide gradleOptions - "gradleJvmOptions" to gradleJvmOptions, - "gradleEnvironmentVariables" to if (gradleExeSettings.isPassParentEnvs) EnvironmentUtil.getEnvironmentMap() else emptyMap() +private class ErrorScriptDependenciesResolver( + private val project: Project, + private val message: String? = null +) : DependenciesResolver { + override fun resolve(scriptContents: ScriptContents, environment: Environment): ResolveResult { + val importTasks = KotlinDslSyncListener.instance.tasks + val importInProgress = synchronized(importTasks) { importTasks.values.any { it.project == project } } + val failureMessage = if (importInProgress) { + KotlinIdeaGradleBundle.message("error.text.highlighting.is.impossible.during.gradle.import") + } else { + message ?: KotlinIdeaGradleBundle.message( + "error.text.failed.to.load.script.definitions.by", + GradleScriptDefinitionsContributor::class.java.name ) - return ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) { - getEnvironment { environment } - } - } - - val gradleSettings = ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID) - if (gradleSettings.getLinkedProjectsSettings().isEmpty()) - error(KotlinIdeaGradleBundle.message("error.text.project.isn.t.linked.with.gradle", project.name)) - - val projectSettings = gradleSettings.getLinkedProjectsSettings().filterIsInstance().firstOrNull() - ?: error(KotlinIdeaGradleBundle.message("error.text.project.isn.t.linked.with.gradle", project.name)) - - val gradleExeSettings = ExternalSystemApiUtil.getExecutionSettings( - project, - projectSettings.externalProjectPath, - GradleConstants.SYSTEM_ID - ) - - val gradleHome = gradleExeSettings.gradleHome - ?: error(KotlinIdeaGradleBundle.message("error.text.unable.to.get.gradle.home.directory")) - - val gradleLibDir = File(gradleHome, "lib").let { - it.takeIf { it.exists() && it.isDirectory } - ?: error(KotlinIdeaGradleBundle.message("error.text.invalid.gradle.libraries.directory", it)) - } - val templateClasspath = gradleLibDir.listFiles { it -> - /* an inference problem without explicit 'it', TODO: remove when fixed */ - dependencySelector.matches(it.name) - }.takeIf { it.isNotEmpty() }?.asList() ?: error(KotlinIdeaGradleBundle.message("error.text.missing.jars.in.gradle.directory")) - - scriptingDebugLog { "loading gradle script templates from $templateClasspath" } - - return loadDefinitionsFromTemplates( - listOf(templateClass), - templateClasspath, - createHostConfiguration(gradleExeSettings, projectSettings), - additionalResolverClasspath(gradleLibDir) - ).map { - it.asLegacyOrNull()?.let { legacyDef -> - // Expand scope for old gradle script definition - GradleKotlinScriptDefinitionWrapper( - it.hostConfiguration, - legacyDef, - projectSettings.resolveGradleVersion().version - ) - } ?: it - } - } - - fun reloadIfNecessary() { - if (failedToLoad.compareAndSet(true, false)) { - reload() - } - } - - private fun reload() { - ScriptDefinitionsManager.getInstance(project).reloadDefinitionsBy(this) - } - - // TODO: refactor - minimize - private class ErrorGradleScriptDefinition(project: Project, message: String? = null) : - ScriptDefinition.FromLegacy( - ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration), - LegacyDefinition(project, message) - ) { - - private class LegacyDefinition(project: Project, message: String?) : KotlinScriptDefinitionAdapterFromNewAPIBase() { - companion object { - private const val KOTLIN_DSL_SCRIPT_EXTENSION = "gradle.kts" - } - - override val name: String get() = KotlinIdeaGradleBundle.message("text.default.kotlin.gradle.script") - override val fileExtension: String = KOTLIN_DSL_SCRIPT_EXTENSION - - override val scriptCompilationConfiguration: ScriptCompilationConfiguration = ScriptCompilationConfiguration.Default - override val hostConfiguration: ScriptingHostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) - override val baseClass: KClass<*> = ScriptTemplateWithArgs::class - - override val dependencyResolver: DependenciesResolver = - ErrorScriptDependenciesResolver(project, message) - - override fun toString(): String = "ErrorGradleScriptDefinition" - } - - override fun equals(other: Any?): Boolean = other is ErrorGradleScriptDefinition - override fun hashCode(): Int = name.hashCode() - } - - private class ErrorScriptDependenciesResolver( - private val project: Project, - private val message: String? = null - ) : DependenciesResolver { - override fun resolve(scriptContents: ScriptContents, environment: Environment): ResolveResult { - val importTasks = KotlinDslSyncListener.instance.tasks - val importInProgress = synchronized(importTasks) { importTasks.values.any { it.project == project } } - val failureMessage = if (importInProgress) { - KotlinIdeaGradleBundle.message("error.text.highlighting.is.impossible.during.gradle.import") - } else { - message ?: KotlinIdeaGradleBundle.message( - "error.text.failed.to.load.script.definitions.by", - GradleScriptDefinitionsContributor::class.java.name - ) - } - return ResolveResult.Failure(ScriptReport(failureMessage, ScriptReport.Severity.FATAL)) } + return ResolveResult.Failure(ScriptReport(failureMessage, ScriptReport.Severity.FATAL)) } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelProcessor.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelProcessor.kt index 6a5a81365e5..aeb32267c15 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelProcessor.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelProcessor.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.idea.scripting.gradle.importing import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Key import com.intellij.openapi.util.io.FileUtil.toSystemIndependentName import com.intellij.openapi.vfs.VfsUtil import org.gradle.tooling.model.kotlin.dsl.EditorReportSeverity @@ -16,10 +15,26 @@ import org.jetbrains.kotlin.gradle.BrokenKotlinDslScriptsModel import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.scripting.gradle.getGradleScriptInputsStamp import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager -import org.jetbrains.kotlin.psi.NotNullableUserDataProperty +import org.jetbrains.plugins.gradle.model.BuildScriptClasspathModel import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext import java.io.File -import java.util.concurrent.ConcurrentHashMap + +fun saveGradleBuildEnvironment(resolverCtx: ProjectResolverContext) { + val task = resolverCtx.externalSystemTaskId + val tasks = KotlinDslSyncListener.instance.tasks + val sync = synchronized(tasks) { tasks[task] } + if (sync != null) { + val gradleHome = resolverCtx.getExtraProject(BuildScriptClasspathModel::class.java)?.gradleHomeDir?.canonicalPath + ?: resolverCtx.settings?.gradleHome + synchronized(sync) { + sync.gradleVersion = resolverCtx.projectGradleVersion + + if (gradleHome != null) { + sync.gradleHome = toSystemIndependentName(gradleHome) + } + } + } +} fun processScriptModel( resolverCtx: ProjectResolverContext, @@ -44,6 +59,11 @@ fun processScriptModel( } if (models.containsErrors()) { + if (sync != null) { + synchronized(sync) { + sync.failed = true + } + } throw IllegalStateException(KotlinIdeaGradleBundle.message("title.kotlin.build.script")) } } @@ -103,6 +123,8 @@ private fun KotlinDslScriptsModel.toListOfScriptModels(project: Project): List() val models = mutableListOf() var failed = false diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt index 54e97d270eb..eb3bb6ecc78 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt @@ -31,12 +31,10 @@ class KotlinDslScriptModelResolver : KotlinDslScriptModelResolverCommon() { override fun populateProjectExtraModels(gradleProject: IdeaProject, ideProject: DataNode) { super.populateProjectExtraModels(gradleProject, ideProject) - if (kotlinDslScriptsModelImportSupported(resolverCtx.projectGradleVersion)) { - populateBuildModels(resolverCtx.models.mainBuild, ideProject) + populateBuildModels(resolverCtx.models.mainBuild, ideProject) - resolverCtx.models.includedBuilds.forEach { includedRoot -> - populateBuildModels(includedRoot, ideProject) - } + resolverCtx.models.includedBuilds.forEach { includedRoot -> + populateBuildModels(includedRoot, ideProject) } } @@ -46,9 +44,13 @@ class KotlinDslScriptModelResolver : KotlinDslScriptModelResolverCommon() { ) { root.projects.forEach { if (it.projectIdentifier.projectPath == ":") { - resolverCtx.models.getModel(it, KotlinDslScriptsModel::class.java)?.let { model -> - processScriptModel(resolverCtx, model, it.projectIdentifier.projectPath) + if (kotlinDslScriptsModelImportSupported(resolverCtx.projectGradleVersion)) { + resolverCtx.models.getModel(it, KotlinDslScriptsModel::class.java)?.let { model -> + processScriptModel(resolverCtx, model, it.projectIdentifier.projectPath) + } } + + saveGradleBuildEnvironment(resolverCtx) } } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.192 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.192 index ae4f734ff70..b397e8790b5 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.192 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.192 @@ -29,12 +29,10 @@ class KotlinDslScriptModelResolver : KotlinDslScriptModelResolverCommon() { override fun populateProjectExtraModels(gradleProject: IdeaProject, ideProject: DataNode) { super.populateProjectExtraModels(gradleProject, ideProject) - if (kotlinDslScriptsModelImportSupported(resolverCtx.projectGradleVersion)) { - populateBuildModels(gradleProject, ideProject) + populateBuildModels(gradleProject, ideProject) - resolverCtx.models.includedBuilds.forEach { includedRoot -> - populateBuildModels(includedRoot, ideProject) - } + resolverCtx.models.includedBuilds.forEach { includedRoot -> + populateBuildModels(includedRoot, ideProject) } } @@ -44,9 +42,13 @@ class KotlinDslScriptModelResolver : KotlinDslScriptModelResolverCommon() { ) { root.modules.forEach { if (it.gradleProject.parent == null) { - resolverCtx.getExtraProject(it, KotlinDslScriptsModel::class.java)?.let { model -> - processScriptModel(resolverCtx, model, it.name) + if (kotlinDslScriptsModelImportSupported(resolverCtx.projectGradleVersion)) { + resolverCtx.getExtraProject(it, KotlinDslScriptsModel::class.java)?.let { model -> + processScriptModel(resolverCtx, model, it.name) + } } + + saveGradleBuildEnvironment(resolverCtx) } } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.201 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.201 index b30283ca07e..1dad8d7effa 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.201 +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslScriptModelResolver.kt.201 @@ -5,12 +5,12 @@ package org.jetbrains.kotlin.idea.scripting.gradle.importing -import org.jetbrains.plugins.gradle.model.ClassSetImportModelProvider -import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider +import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel import org.jetbrains.kotlin.gradle.KotlinDslScriptAdditionalTask import org.jetbrains.kotlin.gradle.KotlinDslScriptModelProvider -import org.gradle.tooling.model.kotlin.dsl.KotlinDslScriptsModel import org.jetbrains.kotlin.idea.scripting.gradle.kotlinDslScriptsModelImportSupported +import org.jetbrains.plugins.gradle.model.ClassSetImportModelProvider +import org.jetbrains.plugins.gradle.model.ProjectImportModelProvider import org.jetbrains.plugins.gradle.service.project.ModifiableGradleProjectModel import org.jetbrains.plugins.gradle.service.project.ProjectModelContributor import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext @@ -34,17 +34,19 @@ class KotlinDslScriptModelContributor : ProjectModelContributor { override fun accept( projectModelBuilder: ModifiableGradleProjectModel, toolingModelsProvider: ToolingModelsProvider, - resolverContext: ProjectResolverContext + resolverCtx: ProjectResolverContext ) { - if (!kotlinDslScriptsModelImportSupported(resolverContext.projectGradleVersion)) return - toolingModelsProvider.projects().forEach { val projectIdentifier = it.projectIdentifier.projectPath if (projectIdentifier == ":") { - val model = toolingModelsProvider.getProjectModel(it, KotlinDslScriptsModel::class.java) - if (model != null) { - processScriptModel(resolverContext, model, projectIdentifier) + if (kotlinDslScriptsModelImportSupported(resolverCtx.projectGradleVersion)) { + val model = toolingModelsProvider.getProjectModel(it, KotlinDslScriptsModel::class.java) + if (model != null) { + processScriptModel(resolverCtx, model, projectIdentifier) + } } + + saveGradleBuildEnvironment(resolverCtx) } } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt index 4df38b84d2f..b3e2daef63d 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/importing/KotlinDslSyncListener.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.idea.scripting.gradle.importing +import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter @@ -13,6 +14,7 @@ import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID import org.jetbrains.kotlin.idea.scripting.gradle.GradleScriptDefinitionsContributor import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRootsManager +import org.jetbrains.plugins.gradle.service.GradleInstallationManager import java.util.* class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { @@ -45,8 +47,15 @@ class KotlinDslSyncListener : ExternalSystemTaskNotificationListenerAdapter() { // project may be null in case of new project val project = id.findProject() ?: return + if (sync.gradleHome == null) { + sync.gradleHome = ServiceManager + .getService(GradleInstallationManager::class.java) + .getGradleHome(project, sync.workingDir) + ?.canonicalPath + } + @Suppress("DEPRECATION") - ScriptDefinitionContributor.find(project)?.reloadIfNecessary() + ScriptDefinitionContributor.find(project)?.reloadIfNeeded(sync.workingDir, sync.gradleHome) saveScriptModels(project, sync) } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt index 9ed731d327b..bcce3b1494f 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRoot.kt @@ -96,12 +96,17 @@ class Imported( builder.sdks.addSdk(javaHome) } - val definitions = GradleScriptDefinitionsContributor.getDefinitions(builder.project) + val definitions = GradleScriptDefinitionsContributor.getDefinitions(builder.project, pathPrefix, data.gradleHome) + if (definitions == null) { + // needed to recreate classRoots if correct script definitions weren't loaded at this moment + // in this case classRoots will be recreated after script definitions update + builder.useCustomScriptDefinition() + } - builder.addTemplateClassesRoots(data.templateClasspath) + builder.addTemplateClassesRoots(GradleScriptDefinitionsContributor.getDefinitionsTemplateClasspath(data.gradleHome)) data.models.forEach { script -> - val definition = selectScriptDefinition(script, definitions) + val definition = definitions?.let { selectScriptDefinition(script, it) } builder.addCustom( script.file, diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt index 987f83a6355..6704da8b41d 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootData.kt @@ -10,6 +10,6 @@ import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslScriptModel data class GradleBuildRootData( val importTs: Long, val projectRoots: Collection, - val templateClasspath: Collection, + val gradleHome: String, val models: Collection ) \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootDataSerializer.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootDataSerializer.kt index 7efa0432788..8f5789ab500 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootDataSerializer.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootDataSerializer.kt @@ -19,7 +19,7 @@ import java.io.DataInputStream import java.io.DataOutput internal object GradleBuildRootDataSerializer { - private val attribute = FileAttribute("kotlin-dsl-script-models", 6, false) + private val attribute = FileAttribute("kotlin-dsl-script-models", 7, false) fun read(buildRoot: VirtualFile): GradleBuildRootData? { return attribute.readAttribute(buildRoot)?.use { @@ -46,7 +46,7 @@ internal object GradleBuildRootDataSerializer { internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRootData) { val strings = StringsPool.writer(output) strings.addStrings(data.projectRoots) - strings.addStrings(data.templateClasspath) + strings.addString(data.gradleHome) data.models.forEach { strings.addString(it.file) strings.addStrings(it.classPath) @@ -56,7 +56,7 @@ internal fun writeKotlinDslScriptModels(output: DataOutput, data: GradleBuildRoo strings.writeHeader() output.writeLong(data.importTs) strings.writeStringIds(data.projectRoots) - strings.writeStringIds(data.templateClasspath) + strings.writeStringId(data.gradleHome) output.writeList(data.models) { strings.writeStringId(it.file) output.writeString(it.inputs.sections) @@ -72,7 +72,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String val importTs = input.readLong() val projectRoots = strings.readStrings() - val templateClasspath = strings.readStrings() + val gradleHome = strings.readString() val models = input.readList { KotlinDslScriptModel( strings.readString(), @@ -84,7 +84,7 @@ internal fun readKotlinDslScriptModels(input: DataInputStream, buildRoot: String ) } - return GradleBuildRootData(importTs, projectRoots, templateClasspath, models) + return GradleBuildRootData(importTs, projectRoots, gradleHome, models) } private object StringsPool { diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocator.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocator.kt index a84b43ee58f..34ec8c107d8 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocator.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsLocator.kt @@ -19,6 +19,8 @@ abstract class GradleBuildRootsLocator { abstract fun getScriptInfo(localPath: String): GradleScriptInfo? + fun getAllRoots(): Collection = roots.list + fun getBuildRootByWorkingDir(gradleWorkingDir: String) = roots.getBuildByRootDir(gradleWorkingDir) diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt index 59b5fef7db1..7254dd72d31 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/roots/GradleBuildRootsManager.kt @@ -30,7 +30,9 @@ import org.jetbrains.kotlin.idea.scripting.gradle.roots.GradleBuildRoot.Importin import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter +import org.jetbrains.plugins.gradle.service.GradleInstallationManager import org.jetbrains.plugins.gradle.settings.* import org.jetbrains.plugins.gradle.util.GradleConstants import java.io.File @@ -73,7 +75,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), if (value != field) { field = value roots.list.toList().forEach { - reloadBuildRoot(it.pathPrefix) + reloadBuildRoot(it.pathPrefix, null) } } } @@ -135,25 +137,31 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), } fun markImportingInProgress(workingDir: String, inProgress: Boolean = true) { - actualizeBuildRoot(workingDir)?.importing?.set(if (inProgress) importing else updated) + actualizeBuildRoot(workingDir, null)?.importing?.set(if (inProgress) importing else updated) updateNotifications { it.startsWith(workingDir) } } fun update(sync: KotlinDslGradleBuildSync) { + val oldRoot = actualizeBuildRoot(sync.workingDir, sync.gradleVersion) ?: return + // fast path for linked gradle builds without .gradle.kts support if (sync.models.isEmpty()) { - val root = getBuildRootByWorkingDir(sync.workingDir) ?: return - if (root is Imported && root.data.models.isEmpty()) return + if (oldRoot is Imported && oldRoot.data.models.isEmpty()) return } - val oldRoot = actualizeBuildRoot(sync.workingDir) ?: return + if (oldRoot is Legacy) return + oldRoot.importing.set(updatingCaches) try { - if (oldRoot is Legacy) return + // TODO: can gradleHome be null, what to do in this case + val gradleHome = sync.gradleHome + if (gradleHome == null) { + scriptingInfoLog("Cannot find valid gradle home for ${sync.gradleHome} with version = ${sync.gradleVersion}, script models cannot be saved") + return + } - val templateClasspath = GradleScriptDefinitionsContributor.getDefinitionsTemplateClasspath(project) - val newData = GradleBuildRootData(sync.ts, sync.projectRoots, templateClasspath, sync.models) + val newData = GradleBuildRootData(sync.ts, sync.projectRoots, gradleHome, sync.models) val mergedData = if (sync.failed && oldRoot is Imported) merge(oldRoot.data, newData) else newData val lastModifiedFilesReset = LastModifiedFiles() @@ -175,7 +183,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), val models = old.models.associateByTo(mutableMapOf()) { it.file } new.models.associateByTo(models) { it.file } - return GradleBuildRootData(new.importTs, roots, new.templateClasspath, models.values) + return GradleBuildRootData(new.importTs, roots, new.gradleHome, models.values) } private val modifiedFilesCheckScheduled = AtomicBoolean() @@ -206,7 +214,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), // detect gradle version change val buildDir = findGradleWrapperPropertiesBuildDir(file) if (buildDir != null) { - actualizeBuildRoot(buildDir) + actualizeBuildRoot(buildDir, null) } } } @@ -239,11 +247,12 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), } override fun onGradleHomeChange(oldPath: String?, newPath: String?, linkedProjectPath: String) { - reloadBuildRoot(linkedProjectPath) + val version = GradleInstallationManager.getGradleVersion(newPath) + reloadBuildRoot(linkedProjectPath, version) } override fun onGradleDistributionTypeChange(currentValue: DistributionType?, linkedProjectPath: String) { - reloadBuildRoot(linkedProjectPath) + reloadBuildRoot(linkedProjectPath, null) } } @@ -260,59 +269,69 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), * Actually this should be true, but we may miss some change events. * For that cases we are rechecking this on each Gradle Project sync (importing/reimporting) */ - private fun actualizeBuildRoot(workingDir: String): GradleBuildRoot? { + private fun actualizeBuildRoot(workingDir: String, gradleVersion: String?): GradleBuildRoot? { val actualSettings = getGradleProjectSettings(workingDir) val buildRoot = getBuildRootByWorkingDir(workingDir) + val version = gradleVersion ?: actualSettings?.resolveGradleVersion()?.version return when { - buildRoot != null -> when { - !buildRoot.checkActual(actualSettings) -> reloadBuildRoot(workingDir) - else -> buildRoot + buildRoot != null -> { + when { + !buildRoot.checkActual(version) -> reloadBuildRoot(workingDir, version) + else -> buildRoot + } + } + actualSettings != null && version != null -> { + loadLinkedRoot(actualSettings, version) } - actualSettings != null -> loadLinkedRoot(actualSettings) else -> null } } - private fun GradleBuildRoot.checkActual(actualSettings: GradleProjectSettings?): Boolean { - if (actualSettings == null) return false + private fun GradleBuildRoot.checkActual(version: String?): Boolean { + if (version == null) return false val knownAsSupported = this !is Legacy - val shouldBeSupported = kotlinDslScriptsModelImportSupported(actualSettings.resolveGradleVersion().version) + val shouldBeSupported = kotlinDslScriptsModelImportSupported(version) return knownAsSupported == shouldBeSupported } - private fun reloadBuildRoot(rootPath: String): GradleBuildRoot? { + private fun reloadBuildRoot(rootPath: String, version: String?): GradleBuildRoot? { val settings = getGradleProjectSettings(rootPath) if (settings == null) { remove(rootPath) return null } else { - val newRoot = loadLinkedRoot(settings) + val gradleVersion = version ?: settings.resolveGradleVersion().version + val newRoot = loadLinkedRoot(settings, gradleVersion) add(newRoot) return newRoot } } - private fun loadLinkedRoot(settings: GradleProjectSettings): GradleBuildRoot { + private fun loadLinkedRoot(settings: GradleProjectSettings, version: String = getGradleVersion(project, settings)): GradleBuildRoot { if (!enabled) { return Legacy(settings) } - val supported = kotlinDslScriptsModelImportSupported(settings.resolveGradleVersion().version) + val supported = kotlinDslScriptsModelImportSupported(version) return when { - supported -> tryLoadFromFsCache(settings) ?: New(settings) + supported -> tryLoadFromFsCache(settings, version) ?: New(settings) else -> Legacy(settings) } } - private fun tryLoadFromFsCache(settings: GradleProjectSettings) = - tryCreateImportedRoot(settings.externalProjectPath) { + private fun tryLoadFromFsCache(settings: GradleProjectSettings, version: String): Imported? { + return tryCreateImportedRoot(settings.externalProjectPath) { GradleBuildRootDataSerializer.read(it)?.let { data -> + val gradleHome = data.gradleHome + if (gradleHome.isNotBlank() && GradleInstallationManager.getGradleVersion(gradleHome) != version) return@let null + addFromSettings(data, settings) } } + } private fun addFromSettings( data: GradleBuildRootData, @@ -326,6 +345,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), ): Imported? { val buildRoot = VfsUtil.findFile(Paths.get(externalProjectPath), true) ?: return null val data = dataProvider(buildRoot) ?: return null + // TODO: can be outdated, should be taken from sync val javaHome = ExternalSystemApiUtil .getExecutionSettings(project, externalProjectPath, GradleConstants.SYSTEM_ID) .javaHome?.let { File(it) } @@ -406,7 +426,7 @@ class GradleBuildRootsManager(val project: Project) : GradleBuildRootsLocator(), private fun updateFloatingAction(file: VirtualFile) { if (isConfigurationOutOfDate(file)) { - scriptConfigurationsNeedToBeUpdated(project) + scriptConfigurationsNeedToBeUpdated(project, file) } else { scriptConfigurationsAreUpToDate(project) } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt index 20c610674f6..da20a4cc6cc 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/scripting/gradle/utils.kt @@ -84,4 +84,8 @@ fun scriptingDebugLog(message: () -> String) { if (logger.isDebugEnabled) { logger.debug("[KOTLIN_GRADLE_DSL] ${message()}") } +} + +fun scriptingInfoLog(message: String) { + logger.info("[KOTLIN_GRADLE_DSL] $message") } \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRootDataSerializerTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRootDataSerializerTest.kt index 53bd554c4fd..a59a5f2ef2b 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRootDataSerializerTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/GradleBuildRootDataSerializerTest.kt @@ -22,7 +22,7 @@ class GradleBuildRootDataSerializerTest { val data = GradleBuildRootData( 123, listOf("a", "b", "c"), - listOf("a"), + "a", listOf( KotlinDslScriptModel( "a", diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/AbstractGradleBuildRootsLocatorTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/AbstractGradleBuildRootsLocatorTest.kt index 736ccccf38c..4654809daac 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/AbstractGradleBuildRootsLocatorTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/scripting/gradle/roots/AbstractGradleBuildRootsLocatorTest.kt @@ -39,7 +39,7 @@ open class AbstractGradleBuildRootsLocatorTest { GradleBuildRootData( ts, relativeProjectRoots.map { (pathPrefix + it).removeSuffix("/") }, - listOf(), + "", listOf() ), LastModifiedFiles() diff --git a/idea/idea-new-project-wizard/build.gradle.kts b/idea/idea-new-project-wizard/build.gradle.kts index d2b01f8b074..cd7ec1bf86b 100644 --- a/idea/idea-new-project-wizard/build.gradle.kts +++ b/idea/idea-new-project-wizard/build.gradle.kts @@ -13,6 +13,9 @@ dependencies { compileOnly(intellijCoreDep()) compileOnly(intellijDep()) compileOnly(intellijPluginDep("gradle")) + Platform[193].orHigher { + compileOnly(intellijPluginDep("gradle-java")) + } testImplementation(projectTests(":idea")) testImplementation(project(":libraries:tools:new-project-wizard:new-project-wizard-cli")) @@ -23,7 +26,7 @@ dependencies { testImplementation(intellijDep()) testImplementation(intellijPluginDep("gradle")) - + testImplementation(projectTests(":idea:idea-gradle")) testRuntimeOnly(toolsJar()) testRuntimeOnly(project(":plugins:kapt3-idea")) @@ -44,9 +47,9 @@ dependencies { } Platform[192].orHigher { - compileOnly(intellijPluginDep("java")) { includeJars("java-api", "java-impl") } - testCompileOnly(intellijPluginDep("java")) { includeJars("java-api", "java-impl") } - testRuntimeOnly(intellijPluginDep("java")) { includeJars("java-api") } + compileOnly(intellijPluginDep("java")) + testCompileOnly(intellijPluginDep("java")) + testRuntimeOnly(intellijPluginDep("java")) } } diff --git a/idea/idea-new-project-wizard/tests/org/jetbrains/kotlin/tools/projectWizard/wizard/AbstractNewWizardProjectImportTest.kt b/idea/idea-new-project-wizard/tests/org/jetbrains/kotlin/tools/projectWizard/wizard/AbstractNewWizardProjectImportTest.kt index 965bd30d5fd..7ca813d29df 100644 --- a/idea/idea-new-project-wizard/tests/org/jetbrains/kotlin/tools/projectWizard/wizard/AbstractNewWizardProjectImportTest.kt +++ b/idea/idea-new-project-wizard/tests/org/jetbrains/kotlin/tools/projectWizard/wizard/AbstractNewWizardProjectImportTest.kt @@ -6,11 +6,23 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard import com.intellij.openapi.application.impl.ApplicationInfoImpl +import com.intellij.openapi.components.service import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.projectRoots.JavaSdk +import com.intellij.openapi.projectRoots.JavaSdkType import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.projectRoots.SimpleJavaSdkType +import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil +import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.testFramework.IdeaTestUtil import com.intellij.testFramework.PlatformTestCase +import org.jetbrains.kotlin.diagnostics.Severity +import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages +import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent +import org.jetbrains.kotlin.idea.codeInsight.gradle.ExternalSystemImportingTestCase +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager +import org.jetbrains.kotlin.idea.core.script.configuration.utils.getKtFile +import org.jetbrains.kotlin.idea.scripting.gradle.getGradleProjectSettings import org.jetbrains.kotlin.idea.test.KotlinSdkCreationChecker import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.test.testFramework.runWriteAction @@ -27,6 +39,7 @@ import org.jetbrains.plugins.gradle.settings.GradleProjectSettings import org.jetbrains.plugins.gradle.settings.GradleSettings import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleEnvironment +import java.io.File import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -40,8 +53,9 @@ abstract class AbstractNewWizardProjectImportTest : PlatformTestCase() { override fun setUp() { super.setUp() runWriteAction { - val sdk = SimpleJavaSdkType().createJdk(SDK_NAME, IdeaTestUtil.requireRealJdkHome()) - PluginTestCaseBase.addJdk(testRootDisposable, { sdk }) + PluginTestCaseBase.addJdk(testRootDisposable) { + JavaSdk.getInstance().createJdk(SDK_NAME, IdeaTestUtil.requireRealJdkHome(), false) + } } sdkCreationChecker = KotlinSdkCreationChecker() } @@ -56,6 +70,7 @@ abstract class AbstractNewWizardProjectImportTest : PlatformTestCase() { fun doTestGradleKts(directoryPath: String) { doTest(directoryPath, BuildSystem.GRADLE_KOTLIN_DSL) + checkScriptConfigurationsIfAny() } fun doTestGradleGroovy(directoryPath: String) { @@ -77,6 +92,14 @@ abstract class AbstractNewWizardProjectImportTest : PlatformTestCase() { prepareGradleBuildSystem(tempDirectory) } + runWizard(directory, buildSystem, tempDirectory) + } + + protected fun runWizard( + directory: Path, + buildSystem: BuildSystem, + tempDirectory: Path + ) { val wizard = createWizard(directory, buildSystem, tempDirectory) val projectDependentServices = @@ -86,7 +109,10 @@ abstract class AbstractNewWizardProjectImportTest : PlatformTestCase() { wizard.apply(projectDependentServices, GenerationPhase.ALL).assertSuccess() } - protected fun prepareGradleBuildSystem(directory: Path) { + protected fun prepareGradleBuildSystem( + directory: Path, + distributionTypeSettings: DistributionType = DistributionType.WRAPPED + ) { com.intellij.openapi.components.ServiceManager.getService(project, GradleSettings::class.java)?.apply { isOfflineWork = GradleEnvironment.Headless.GRADLE_OFFLINE?.toBoolean() ?: isOfflineWork serviceDirectoryPath = GradleEnvironment.Headless.GRADLE_SERVICE_DIRECTORY ?: serviceDirectoryPath @@ -99,12 +125,36 @@ abstract class AbstractNewWizardProjectImportTest : PlatformTestCase() { isUseAutoImport = false isUseQualifiedModuleNames = true gradleJvm = SDK_NAME - distributionType = DistributionType.WRAPPED + distributionType = distributionTypeSettings } ExternalSystemApiUtil.getSettings(project, GradleConstants.SYSTEM_ID).linkProject(settings) } } + protected fun checkScriptConfigurationsIfAny() { + if (is192()) return + + val settings = getGradleProjectSettings(project).firstOrNull() ?: error("Cannot find linked gradle project: ${project.basePath}") + val scripts = File(settings.externalProjectPath).walkTopDown().filter { + it.name.endsWith("gradle.kts") + } + scripts.forEach { + val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(it)!! + val psiFile = project.getKtFile(virtualFile) ?: error("Cannot find KtFile for $it") + assertTrue( + "Configuration for ${it.path} is missing", + project.service().hasConfiguration(psiFile) + ) + + val bindingContext = psiFile.analyzeWithContent() + + val diagnostics = bindingContext.diagnostics.filter { it.severity == Severity.ERROR } + assert(diagnostics.isEmpty()) { + "Diagnostics list should be empty:\n ${diagnostics.joinToString("\n") { DefaultErrorMessages.render(it) }}" + } + } + } + private fun is192() = ApplicationInfoImpl.getShadowInstance().minorVersionMainPart == "2" && ApplicationInfoImpl.getShadowInstance().majorVersion == "2019" diff --git a/idea/idea-new-project-wizard/tests/org/jetbrains/kotlin/tools/projectWizard/wizard/ScriptHighlightingGradleDistributionTypeTest.kt b/idea/idea-new-project-wizard/tests/org/jetbrains/kotlin/tools/projectWizard/wizard/ScriptHighlightingGradleDistributionTypeTest.kt new file mode 100644 index 00000000000..27c0c609b6b --- /dev/null +++ b/idea/idea-new-project-wizard/tests/org/jetbrains/kotlin/tools/projectWizard/wizard/ScriptHighlightingGradleDistributionTypeTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.tools.projectWizard.wizard + +import org.jetbrains.kotlin.tools.projectWizard.cli.BuildSystem +import org.jetbrains.plugins.gradle.settings.DistributionType +import org.junit.Test +import java.nio.file.Files +import java.nio.file.Paths + +class ScriptHighlightingGradleDistributionTypeTest : AbstractProjectTemplateNewWizardProjectImportTest() { + + @Test + fun testScriptHighlightingGradleWrapped() { + doTest(DistributionType.WRAPPED) + } + + @Test + fun testScriptHighlightingGradleDefaultWrapped() { + doTest(DistributionType.DEFAULT_WRAPPED) + } + + @Test + fun testScriptHighlightingGradleBundled() { + doTest(DistributionType.BUNDLED) + } + + private fun doTest(distributionType: DistributionType) { + val directory = Paths.get("backendApplication") + val tempDirectory = Files.createTempDirectory(null) + + prepareGradleBuildSystem(tempDirectory, distributionType) + + runWizard(directory, BuildSystem.GRADLE_KOTLIN_DSL, tempDirectory) + + checkScriptConfigurationsIfAny() + } +} \ No newline at end of file