From 2ec2a08753c1fadb42c5ca9f08c7e5dc5419c985 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Thu, 22 Jun 2017 14:55:43 +0200 Subject: [PATCH] Implement advanced gradle script template selection in the provider, improve diagnostics --- .../scriptTemplateProviderExtensionPoint.kt | 30 ++++---- .../script/GradleScriptTemplateProvider.kt | 69 +++++++++++++------ 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt b/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt index e28d1e066e4..329874c9748 100644 --- a/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt +++ b/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptTemplateProviderExtensionPoint.kt @@ -46,6 +46,9 @@ interface ScriptTemplatesProvider { val environment: Map? + // for caching already loaded definitions, when needed + val scriptDefinitions: List? get() = null + companion object { val EP_NAME: ExtensionPointName = ExtensionPointName.create("org.jetbrains.kotlin.scriptTemplatesProvider") @@ -54,26 +57,27 @@ interface ScriptTemplatesProvider { fun makeScriptDefsFromTemplatesProviderExtensions(project: Project, errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { _, ex -> throw ex } -): List = +): List = makeScriptDefsFromTemplatesProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplatesProvider.EP_NAME).extensions.asIterable(), errorsHandler) fun makeScriptDefsFromTemplatesProviders(providers: Iterable, errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { _, ex -> throw ex } -): List { - return providers.filter { it.isValid }.flatMap { provider -> - try { - Logger.getInstance("makeScriptDefsFromTemplatesProviders") - .info("[kts] loading script definitions ${provider.templateClassNames} using cp: ${provider.dependenciesClasspath.joinToString(File.pathSeparator)}") +): List = providers.flatMap { provider -> + try { + LOG.info("[kts] loading script definitions ${provider.templateClassNames} using cp: ${provider.dependenciesClasspath.joinToString(File.pathSeparator)}") + provider.scriptDefinitions ?: run { val loader = URLClassLoader(provider.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplatesProvider::class.java.classLoader) provider.templateClassNames.map { - val cl = loader.loadClass(it) - KotlinScriptDefinitionFromAnnotatedTemplate(cl.kotlin, provider.resolver, provider.filePattern, provider.environment) + KotlinScriptDefinitionFromAnnotatedTemplate(loader.loadClass(it).kotlin, provider.resolver, provider.filePattern, provider.environment) } } - catch (ex: Exception) { - errorsHandler(provider, ex) - emptyList() - } } -} \ No newline at end of file + catch (ex: Exception) { + LOG.info("Templates provider ${provider.id} is invalid: ${ex.message}") + errorsHandler(provider, ex) + emptyList() + } +} + +private val LOG = Logger.getInstance("ScriptTemplatesProviders") diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt index 63b32b2b27c..67cc8527d5f 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/script/GradleScriptTemplateProvider.kt @@ -23,6 +23,8 @@ import com.intellij.openapi.project.Project import org.gradle.tooling.ProjectConnection import org.jetbrains.kotlin.lexer.KotlinLexer import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate import org.jetbrains.kotlin.script.ScriptTemplatesProvider import org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings @@ -56,31 +58,51 @@ class GradleScriptTemplatesProvider(project: Project): ScriptTemplatesProvider { } override val id: String = "Gradle" - override val isValid: Boolean get() = gradleExeSettings?.gradleHome != null + override val isValid: Boolean get() = templatesData is TemplateDataOrError.Data - override val templateClassNames: Iterable by lazy { - val loader = URLClassLoader(dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplatesProvider::class.java.classLoader) - val tpl = listOf("org.gradle.kotlin.dsl.KotlinBuildScript", "org.gradle.script.lang.kotlin.KotlinBuildScript").firstOrNull { - try { - // TODO: consider introducing caching mechanism (e.g. for ScriptDefinition) in ScriptTemplatesProvider to avoid double loading - loader.loadClass(it) - true - } - catch (e: ClassNotFoundException) { false } - catch (e: NoClassDefFoundError) { false } + private sealed class TemplateDataOrError { + class Data(val templateClassNames: Iterable, + val dependenciesClasspath: Iterable, + val scriptDefinitions: List) : TemplateDataOrError() + class Error(val message: String) : TemplateDataOrError() + } + + private val templatesData: TemplateDataOrError by lazy { + + if (gradleExeSettings?.gradleHome == null) return@lazy TemplateDataOrError.Error("Unable to get Gradle home directory") + + val gradleLibDir = File(gradleExeSettings!!.gradleHome, "lib").let { + it.takeIf { it.exists() && it.isDirectory } + ?: return@lazy TemplateDataOrError.Error("Invalid Gradle libraries directory $it") } - if (tpl != null) listOf(tpl) else emptyList() + + for ((template, selector) in templates2DependenciesSelectors) { + val cp = gradleLibDir.listFiles { it /* an inference problem without explicit 'it', TODO: remove when fixed */ -> selector.matches(it.name) }.takeIf { it.isNotEmpty() } ?: continue + + val loader = URLClassLoader(cp.map { it.toURI().toURL() }.toTypedArray(), ScriptTemplatesProvider::class.java.classLoader) + try { + val cl = loader.loadClass(template) + val def = KotlinScriptDefinitionFromAnnotatedTemplate(cl.kotlin, resolver, filePattern, environment) + return@lazy TemplateDataOrError.Data(listOf(template), cp.asIterable(), listOf(def)) + } + catch (e: ClassNotFoundException) {} + catch (e: NoClassDefFoundError) {} + } + + return@lazy TemplateDataOrError.Error("Unable to find a suitable template in the Gradle libraries directory $gradleLibDir") } - override val dependenciesClasspath: Iterable by lazy { - gradleExeSettings?.gradleHome?.let { File(it, "lib") } - ?.let { if (it.exists()) it else null } - ?.listFiles { file -> file.extension == "jar" && depLibsPrefixes.any { file.name.startsWith(it) } } - ?.map { it.canonicalPath } - ?: emptyList() + override val templateClassNames: Iterable get() = when(templatesData) { + is GradleScriptTemplatesProvider.TemplateDataOrError.Data -> (templatesData as TemplateDataOrError.Data).templateClassNames + is GradleScriptTemplatesProvider.TemplateDataOrError.Error -> throw IllegalStateException((templatesData as TemplateDataOrError.Error).message) } + + override val dependenciesClasspath: Iterable get() = when(templatesData) { + is GradleScriptTemplatesProvider.TemplateDataOrError.Data -> (templatesData as TemplateDataOrError.Data).dependenciesClasspath.map { it.canonicalPath } + is GradleScriptTemplatesProvider.TemplateDataOrError.Error -> throw IllegalStateException((templatesData as TemplateDataOrError.Error).message) + } + override val environment: Map? by lazy { - mapOf( "gradleHome" to gradleExeSettings?.gradleHome?.let(::File), "projectRoot" to (project.basePath ?: project.baseDir.canonicalPath)?.let(::File), @@ -91,8 +113,15 @@ class GradleScriptTemplatesProvider(project: Project): ScriptTemplatesProvider { "getScriptSectionTokens" to ::topLevelSectionCodeTextTokens) } + override val scriptDefinitions: List? get() = when(templatesData) { + is GradleScriptTemplatesProvider.TemplateDataOrError.Data -> (templatesData as TemplateDataOrError.Data).scriptDefinitions + is GradleScriptTemplatesProvider.TemplateDataOrError.Error -> throw IllegalStateException((templatesData as TemplateDataOrError.Error).message) + } + companion object { - private val depLibsPrefixes = listOf("gradle-kotlin-dsl", "gradle-script-kotlin", "gradle-core") + private val templates2DependenciesSelectors = listOf( + "org.gradle.kotlin.dsl.KotlinBuildScript" to Regex("^gradle-(?:kotlin-dsl|core).*\\.jar\$"), + "org.gradle.script.lang.kotlin.KotlinBuildScript" to Regex("^gradle-(?:script-kotlin|core).*\\.jar\$")) } }