Refactor script definitions loading

Make ScriptDefinitionProvider an interface
    and provide different implementation for cli and IDEA
Rework ide extension point to a simpler interface (ScriptDefinitionContributor)
Move template loading logic into a top level function
Allow script definitions to be reloaded in IDE
This commit is contained in:
Pavel V. Talanov
2017-10-19 20:02:30 +03:00
parent f063254cc1
commit cdfdc1d19b
32 changed files with 481 additions and 419 deletions
@@ -0,0 +1,44 @@
/*
* 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.cli.common.script
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import org.jetbrains.kotlin.script.StandardScriptDefinition
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
class CliScriptDefinitionProvider : ScriptDefinitionProvider {
private val definitions: MutableList<KotlinScriptDefinition> = arrayListOf(StandardScriptDefinition)
private val lock = ReentrantReadWriteLock()
override fun findScriptDefinition(fileName: String) = lock.read {
definitions.firstOrNull { it.isScript(fileName) }
}
override fun isScript(fileName: String) = lock.read {
definitions.any { it.isScript(fileName) }
}
fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>) {
lock.write {
definitions.clear()
definitions.addAll(newDefinitions)
}
}
}
@@ -19,9 +19,9 @@ package org.jetbrains.kotlin.cli.common.script
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import org.jetbrains.kotlin.script.ScriptContentLoader
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import java.io.File
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
@@ -30,7 +30,7 @@ import kotlin.script.experimental.dependencies.ScriptDependencies
class CliScriptDependenciesProvider(
project: Project,
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider
private val scriptDefinitionProvider: ScriptDefinitionProvider
) : ScriptDependenciesProvider {
private val cacheLock = ReentrantReadWriteLock()
@@ -47,7 +47,7 @@ import org.jetbrains.kotlin.javac.JavacWrapper
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import org.jetbrains.kotlin.script.StandardScriptDefinition
import org.jetbrains.kotlin.util.PerformanceCounter
import org.jetbrains.kotlin.utils.KotlinPaths
@@ -166,7 +166,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
val environment = createEnvironmentWithScriptingSupport(rootDisposable, configuration, arguments, messageCollector)
?: return COMPILATION_ERROR
val scriptDefinitionProvider = KotlinScriptDefinitionProvider.getInstance(environment.project)!!
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(environment.project)!!
val scriptFile = File(sourcePath)
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile.name)) {
val extensionHint =
@@ -460,7 +460,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
for (template in scriptTemplates) {
try {
val cls = classloader.loadClass(template)
val def = KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, null, null, scriptResolverEnv)
val def = KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv)
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, def)
messageCollector.report(
INFO,
@@ -29,7 +29,6 @@ import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.lang.MetaLanguage
import com.intellij.lang.java.JavaParserDefinition
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.TransactionGuard
import com.intellij.openapi.application.TransactionGuardImpl
import com.intellij.openapi.components.ServiceManager
@@ -39,7 +38,6 @@ import com.intellij.openapi.fileTypes.FileTypeExtensionPoint
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.*
@@ -71,6 +69,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.script.CliScriptDefinitionProvider
import org.jetbrains.kotlin.cli.common.script.CliScriptDependenciesProvider
import org.jetbrains.kotlin.cli.common.script.CliScriptReportSink
import org.jetbrains.kotlin.cli.common.toBooleanLenient
@@ -104,7 +103,7 @@ import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtens
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFactoryService
import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import org.jetbrains.kotlin.script.ScriptDependenciesProvider
import org.jetbrains.kotlin.script.ScriptReportSink
import org.jetbrains.kotlin.utils.PathUtil
@@ -191,7 +190,8 @@ class KotlinCoreEnvironment private constructor(
})
sourceFiles.sortBy { it.virtualFile.path }
KotlinScriptDefinitionProvider.getInstance(project)?.let { scriptDefinitionProvider ->
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(project) as? CliScriptDefinitionProvider
if (scriptDefinitionProvider != null) {
scriptDefinitionProvider.setScriptDefinitions(
configuration.getList(JVMConfigurationKeys.SCRIPT_DEFINITIONS))
@@ -554,9 +554,9 @@ class KotlinCoreEnvironment private constructor(
@JvmStatic
fun registerProjectServices(projectEnvironment: JavaCoreProjectEnvironment, messageCollector: MessageCollector?) {
with (projectEnvironment.project) {
val kotlinScriptDefinitionProvider = KotlinScriptDefinitionProvider()
registerService(KotlinScriptDefinitionProvider::class.java, kotlinScriptDefinitionProvider)
registerService(ScriptDependenciesProvider::class.java, CliScriptDependenciesProvider(projectEnvironment.project, kotlinScriptDefinitionProvider))
val scriptDefinitionProvider = CliScriptDefinitionProvider()
registerService(ScriptDefinitionProvider::class.java, scriptDefinitionProvider)
registerService(ScriptDependenciesProvider::class.java, CliScriptDependenciesProvider(projectEnvironment.project, scriptDefinitionProvider))
registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this))
registerService(KtLightClassForFacade.FacadeStubCache::class.java, KtLightClassForFacade.FacadeStubCache(this))
if (messageCollector != null) {
@@ -63,7 +63,7 @@ open class KotlinJvmReplService(
try {
val cls = classloader.loadClass(templateClassName)
val def = KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, null, null, emptyMap())
val def = KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, emptyMap())
messageCollector.report(INFO, "New script definition $templateClassName: files pattern = \"${def.scriptFilePattern}\", " +
"resolver = ${def.dependencyResolver.javaClass.name}")
return def
@@ -35,21 +35,17 @@ import kotlin.script.templates.AcceptedAnnotations
open class KotlinScriptDefinitionFromAnnotatedTemplate(
template: KClass<out Any>,
providedResolver: DependenciesResolver? = null,
providedScriptFilePattern: String? = null,
val environment: Map<String, Any?>? = null,
val templateClasspath: List<File> = emptyList()
) : KotlinScriptDefinition(template) {
val scriptFilePattern by lazy {
providedScriptFilePattern
?: takeUnlessError { template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>()?.scriptFilePattern }
takeUnlessError { template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>()?.scriptFilePattern }
?: takeUnlessError { template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.ScriptTemplateDefinition>()?.scriptFilePattern }
?: DEFAULT_SCRIPT_FILE_PATTERN
}
override val dependencyResolver: DependenciesResolver by lazy {
providedResolver ?:
resolverFromAnnotation(template) ?:
resolverFromLegacyAnnotation(template) ?:
DependenciesResolver.NoDependencies
@@ -1,95 +0,0 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.script
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project
import java.io.File
import java.net.URLClassLoader
import kotlin.script.experimental.dependencies.DependenciesResolver
interface ScriptTemplatesProvider {
// for resolving ambiguities
val id: String
@Deprecated("Parameter isn't used for resolving priorities anymore. " +
"com.intellij.openapi.extensions.LoadingOrder constants can be used to order providers when registered from Intellij plugin.",
ReplaceWith("0"))
val version: Int get() = 0
val isValid: Boolean
val templateClassNames: Iterable<String>
val resolver: DependenciesResolver? get() = null
val filePattern: String? get() = null
val templateClasspath: List<File>
// TODO: need to provide a way to specify this in compiler/repl .. etc
/*
* Allows to specify additional jars needed for DependenciesResolver (and not script template).
* Script template dependencies naturally become (part of) dependencies of the script which is not always desired for resolver dependencies.
* i.e. gradle resolver may depend on some jars that 'built.gradle.kts' files should not depend on.
*/
val additionalResolverClasspath: List<File> get() = emptyList()
val environment: Map<String, Any?>?
companion object {
val EP_NAME: ExtensionPointName<ScriptTemplatesProvider> =
ExtensionPointName.create<ScriptTemplatesProvider>("org.jetbrains.kotlin.scriptTemplatesProvider")
}
}
fun makeScriptDefsFromTemplatesProviderExtensions(project: Project,
errorsHandler: ((ScriptTemplatesProvider, Throwable) -> Unit)
): List<KotlinScriptDefinition> =
makeScriptDefsFromTemplatesProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplatesProvider.EP_NAME).extensions.asIterable(),
errorsHandler)
fun makeScriptDefsFromTemplatesProviders(providers: Iterable<ScriptTemplatesProvider>,
errorsHandler: ((ScriptTemplatesProvider, Throwable) -> Unit) = { _, ex -> throw ex }
): List<KotlinScriptDefinition> = providers.flatMap { provider ->
try {
val loader = createClassLoader(provider)
provider.templateClassNames.map {
KotlinScriptDefinitionFromAnnotatedTemplate(
loader.loadClass(it).kotlin, provider.resolver,
provider.filePattern, provider.environment, provider.templateClasspath
)
}
}
catch (ex: Throwable) {
LOG.info("Templates provider ${provider.id} is invalid: ${ex.message}")
errorsHandler(provider, ex)
emptyList<KotlinScriptDefinition>()
}
}
private fun createClassLoader(provider: ScriptTemplatesProvider): ClassLoader {
val classpath = provider.templateClasspath + provider.additionalResolverClasspath
LOG.info("[kts] loading script definitions ${provider.templateClassNames} using cp: ${classpath.joinToString(File.pathSeparator)}")
val baseLoader = ScriptTemplatesProvider::class.java.classLoader
return if (classpath.isEmpty()) baseLoader else URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), baseLoader)
}
private val LOG = Logger.getInstance("ScriptTemplatesProviders")
@@ -23,14 +23,14 @@ import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider;
import org.jetbrains.kotlin.script.ScriptDefinitionProvider;
public class KotlinParser implements PsiParser {
private final KotlinScriptDefinitionProvider scriptDefinitionProvider;
private final ScriptDefinitionProvider scriptDefinitionProvider;
public KotlinParser(Project project) {
scriptDefinitionProvider = KotlinScriptDefinitionProvider.getInstance(project);
scriptDefinitionProvider = ScriptDefinitionProvider.Companion.getInstance(project);
}
@Override
@@ -17,67 +17,23 @@
package org.jetbrains.kotlin.script
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import kotlin.concurrent.read
import kotlin.concurrent.write
class KotlinScriptDefinitionProvider {
private val definitions: MutableList<KotlinScriptDefinition> = arrayListOf(StandardScriptDefinition)
private val definitionsLock = java.util.concurrent.locks.ReentrantReadWriteLock()
fun setScriptDefinitions(newDefinitions: List<KotlinScriptDefinition>): Boolean {
var changed = false
definitionsLock.read {
if (newDefinitions != definitions) {
definitionsLock.write {
definitions.clear()
definitions.addAll(newDefinitions)
}
changed = true
}
}
return changed
}
interface ScriptDefinitionProvider {
fun findScriptDefinition(fileName: String): KotlinScriptDefinition?
fun isScript(fileName: String): Boolean
fun findScriptDefinition(file: VirtualFile): KotlinScriptDefinition? = findScriptDefinition(file.name)
fun findScriptDefinition(fileName: String): KotlinScriptDefinition? = definitionsLock.read {
definitions.firstOrNull { it.isScript(fileName) }
}
fun isScript(fileName: String): Boolean = definitionsLock.read {
definitions.any { it.isScript(fileName) }
}
fun addScriptDefinition(scriptDefinition: KotlinScriptDefinition) {
definitionsLock.write {
definitions.add(0, scriptDefinition)
}
}
fun removeScriptDefinition(scriptDefinition: KotlinScriptDefinition) {
definitionsLock.write {
definitions.remove(scriptDefinition)
}
}
fun getAllKnownFileTypes(): Iterable<LanguageFileType> = definitionsLock.read {
definitions.map { it.fileType }.distinct()
}
companion object {
@JvmStatic
fun getInstance(project: Project): KotlinScriptDefinitionProvider? =
ServiceManager.getService(project, KotlinScriptDefinitionProvider::class.java)
fun getInstance(project: Project): ScriptDefinitionProvider =
ServiceManager.getService(project, ScriptDefinitionProvider::class.java)
}
}
fun getScriptDefinition(file: VirtualFile, project: Project): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(project)?.findScriptDefinition(file)
ScriptDefinitionProvider.getInstance(project).findScriptDefinition(file)
fun getScriptDefinition(psiFile: PsiFile): KotlinScriptDefinition? =
KotlinScriptDefinitionProvider.getInstance(psiFile.project)?.findScriptDefinition(psiFile.name)
ScriptDefinitionProvider.getInstance(psiFile.project).findScriptDefinition(psiFile.name)
@@ -25,8 +25,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.checkers.CheckerTestUtil;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.AnalyzingUtils;
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider;
import org.jetbrains.kotlin.script.StandardScriptDefinition;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import java.io.IOException;
@@ -133,10 +131,6 @@ public class CodegenTestFiles {
}
}
KotlinScriptDefinitionProvider scriptDefinitionProvider = KotlinScriptDefinitionProvider.getInstance(project);
assert scriptDefinitionProvider != null;
scriptDefinitionProvider.addScriptDefinition(StandardScriptDefinition.INSTANCE);
return new CodegenTestFiles(Collections.singletonList(file), expectedValues, scriptParameterValues);
}
}
@@ -184,7 +184,7 @@ internal class TestRepl(
private fun makeScriptDefinition(templateClasspath: List<File>, templateClassName: String): KotlinScriptDefinition {
val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this::class.java.classLoader)
val cls = classloader.loadClass(templateClassName)
return KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, null, null, emptyMap())
return KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, emptyMap())
}
private val scriptDef = makeScriptDefinition(templateClasspath, templateClassName)
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.codegen
import java.io.File
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
@@ -26,16 +25,18 @@ import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.addKotlinSourceRoots
import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate
import org.jetbrains.kotlin.scripts.ScriptWithIntParam
import org.jetbrains.kotlin.scripts.TestKotlinScriptDependenciesResolver
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.org.objectweb.asm.Opcodes
import java.io.File
import kotlin.script.templates.ScriptTemplateDefinition
class ScriptGenTest : CodegenTestCase() {
companion object {
private val FIB_SCRIPT_DEFINITION = KotlinScriptDefinitionFromAnnotatedTemplate(ScriptWithIntParam::class, providedScriptFilePattern = ".*\\.lang\\.kt")
private val NO_PARAM_SCRIPT_DEFINITION = KotlinScriptDefinitionFromAnnotatedTemplate(Any::class, providedScriptFilePattern = ".*\\.kts")
private val FIB_SCRIPT_DEFINITION = KotlinScriptDefinitionFromAnnotatedTemplate(ScriptWithIntParam::class)
private val NO_PARAM_SCRIPT_DEFINITION = KotlinScriptDefinitionFromAnnotatedTemplate(Any::class)
}
override fun setUp() {
@@ -124,3 +125,8 @@ class ScriptGenTest : CodegenTestCase() {
loadFiles(*sourcePaths.toTypedArray())
}
}
@ScriptTemplateDefinition(
scriptFilePattern =".*\\.lang\\.kt",
resolver = TestKotlinScriptDependenciesResolver::class)
abstract class ScriptWithIntParam(val num: Int)
@@ -28,8 +28,9 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.PathUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.KtNodeTypes;
import org.jetbrains.kotlin.cli.common.script.CliScriptDefinitionProvider;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider;
import org.jetbrains.kotlin.script.ScriptDefinitionProvider;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.testFramework.KtParsingTestCase;
@@ -41,7 +42,7 @@ public abstract class AbstractParsingTest extends KtParsingTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
getProject().registerService(KotlinScriptDefinitionProvider.class);
getProject().registerService(ScriptDefinitionProvider.class, CliScriptDefinitionProvider.class);
}
@Override
@@ -333,7 +333,7 @@ class ScriptTemplateTest : KtUsefulTestCase() {
includeKotlinRuntime: Boolean = true
): Class<*>? =
compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromAnnotatedTemplate(
scriptTemplate, null, null, environment
scriptTemplate, environment
), runIsolated, messageCollector, includeKotlinRuntime)
private fun compileScriptImpl(