diff --git a/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt b/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt index c5498f43628..5dfcff41247 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/script/KotlinScriptDefinition.kt @@ -29,7 +29,7 @@ import kotlin.script.experimental.dependencies.DependenciesResolver import kotlin.script.experimental.location.ScriptExpectedLocation import kotlin.script.templates.standard.ScriptTemplateWithArgs -open class KotlinScriptDefinition(val template: KClass) : UserDataHolderBase() { +open class KotlinScriptDefinition(open val template: KClass) : UserDataHolderBase() { open val name: String = "Kotlin Script" diff --git a/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt b/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt index 3f6d81f2c0e..d02e5499ef0 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt @@ -21,6 +21,12 @@ import kotlin.script.experimental.definitions.ScriptDefinitionFromAnnotatedBaseC @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class KotlinScript( + val name: String = "Kotlin script" +) + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class KotlinScriptDefinition( val definition: KClass = ScriptDefinitionFromAnnotatedBaseClass::class ) diff --git a/libraries/scripting/common/src/kotlin/script/experimental/definitions/definitionFromAnnotation.kt b/libraries/scripting/common/src/kotlin/script/experimental/definitions/definitionFromAnnotation.kt index 33225de11e7..2e9d9fad633 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/definitions/definitionFromAnnotation.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/definitions/definitionFromAnnotation.kt @@ -8,10 +8,7 @@ package kotlin.script.experimental.definitions import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.primaryConstructor -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.annotations.KotlinScriptCompilationConfigurator -import kotlin.script.experimental.annotations.KotlinScriptEvaluator -import kotlin.script.experimental.annotations.KotlinScriptFileExtension +import kotlin.script.experimental.annotations.* import kotlin.script.experimental.api.* import kotlin.script.experimental.basic.AnnotationsBasedCompilationConfigurator import kotlin.script.experimental.basic.DummyEvaluator @@ -28,13 +25,13 @@ open class ScriptDefinitionFromAnnotatedBaseClass(val environment: ScriptingEnvi ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting KotlinScript on the $baseClass") private val explicitDefinition: ScriptDefinition? = - mainAnnotation.definition.takeIf { it != this::class }?.let { it.instantiateScriptHandler() } + baseClass.findAnnotation()?.definition.takeIf { it != this::class }?.let { it.instantiateScriptHandler() } override val properties = (explicitDefinition?.properties ?: ScriptingEnvironment()).also { properties -> val toAdd = arrayListOf, Any>>() baseClass.findAnnotation()?.let { toAdd += ScriptDefinitionProperties.fileExtension to it } if (properties.getOrNull(ScriptDefinitionProperties.name) == null) { - toAdd += ScriptDefinitionProperties.name to baseClass.simpleName!! + toAdd += ScriptDefinitionProperties.name to mainAnnotation.name } ScriptingEnvironment(properties, toAdd) } diff --git a/plugins/scripting/scripting-cli/build.gradle.kts b/plugins/scripting/scripting-cli/build.gradle.kts index 99c03c82a20..d89069a9d31 100644 --- a/plugins/scripting/scripting-cli/build.gradle.kts +++ b/plugins/scripting/scripting-cli/build.gradle.kts @@ -9,11 +9,13 @@ plugins { dependencies { compileOnly(project(":compiler:frontend")) compileOnly(project(":compiler:frontend.java")) + compileOnly(project(":compiler:psi")) compileOnly(project(":compiler:plugin-api")) compileOnly(project(":compiler:cli")) compile(project(":kotlin-scripting-common")) compile(project(":kotlin-scripting-jvm")) compileOnly(intellijCoreDep()) { includeJars("intellij-core") } + compileOnly(intellijDep()) { includeJars("asm-all") } testCompile(project(":compiler:frontend")) testCompile(project(":compiler:frontend.script")) diff --git a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt index 58f921eb113..b68fb051ca3 100644 --- a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt +++ b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/KotlinScriptDefinitionAdapterFromNewAPI.kt @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.NameUtils import org.jetbrains.kotlin.psi.KtScript -import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.* import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.script.experimental.api.ScriptCompileConfigurationProperties @@ -19,21 +19,25 @@ import kotlin.script.experimental.api.ScriptDefinitionProperties import kotlin.script.experimental.api.ScriptingEnvironmentProperties import kotlin.script.experimental.dependencies.DependenciesResolver import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver +import kotlin.script.experimental.location.ScriptExpectedLocation -class KotlinScriptDefinitionAdapterFromNewAPI(val scriptDefinition: ScriptDefinition) : - KotlinScriptDefinition(scriptDefinition.compilationConfigurator.defaultConfiguration[ScriptingEnvironmentProperties.baseClass]) { +// temporary trick with passing Any as a template and overwriting it below, TODO: fix after introducing new script definitions hierarchy +abstract class KotlinScriptDefinitionAdapterFromNewAPIBase : KotlinScriptDefinition(Any::class) { - override val name: String get() = scriptDefinition.properties.getOrNull(ScriptDefinitionProperties.name) ?: super.name + protected abstract val scriptDefinition: ScriptDefinition + + protected abstract val scriptFileExtensionWithDot: String + + open val baseClass: KClass<*> + get() = scriptDefinition.compilationConfigurator.defaultConfiguration[ScriptingEnvironmentProperties.baseClass] + + override val template: KClass<*> get() = baseClass + + override val name: String + get() = scriptDefinition.properties.getOrNull(ScriptDefinitionProperties.name) ?: "Kotlin Script" - // TODO: consider creating separate type (subtype? for kotlin scripts) override val fileType: LanguageFileType = KotlinFileType.INSTANCE - override val annotationsForSamWithReceivers: List - get() = emptyList() - - private val scriptFileExtensionWithDot = - "." + (scriptDefinition.properties.getOrNull(ScriptDefinitionProperties.fileExtension) ?: "kts") - override fun isScript(fileName: String): Boolean = fileName.endsWith(scriptFileExtensionWithDot) @@ -42,12 +46,15 @@ class KotlinScriptDefinitionAdapterFromNewAPI(val scriptDefinition: ScriptDefini return Name.identifier(fileBasedName.identifier.removeSuffix(scriptFileExtensionWithDot)) } + override val annotationsForSamWithReceivers: List + get() = emptyList() + override val dependencyResolver: DependenciesResolver by lazy { BridgeDependenciesResolver(scriptDefinition.compilationConfigurator) } override val acceptedAnnotations: List> by lazy { - scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.refineConfigurationOnAnnotations)?.toList() + scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.refineConfigurationOnAnnotations) ?: emptyList() } @@ -60,6 +67,25 @@ class KotlinScriptDefinitionAdapterFromNewAPI(val scriptDefinition: ScriptDefini scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.contextVariables)?.map { (k, v) -> k to v } ?: emptyList() } + + override val additionalCompilerArguments: List + get() = scriptDefinition.compilationConfigurator.defaultConfiguration.getOrNull(ScriptCompileConfigurationProperties.compilerOptions) + ?: emptyList() + + override val scriptExpectedLocations: List = + listOf( + ScriptExpectedLocation.SourcesOnly, + ScriptExpectedLocation.TestsOnly + ) } +class KotlinScriptDefinitionAdapterFromNewAPI( + override val scriptDefinition: ScriptDefinition +) : KotlinScriptDefinitionAdapterFromNewAPIBase() { + + override val name: String get() = scriptDefinition.properties.getOrNull(ScriptDefinitionProperties.name) ?: super.name + + override val scriptFileExtensionWithDot = + "." + (scriptDefinition.properties.getOrNull(ScriptDefinitionProperties.fileExtension) ?: "kts") +} diff --git a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/LazyScriptDefinitionFromDiscoveredClass.kt b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/LazyScriptDefinitionFromDiscoveredClass.kt new file mode 100644 index 00000000000..eda29849bd2 --- /dev/null +++ b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/LazyScriptDefinitionFromDiscoveredClass.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.scripting.compiler.plugin + +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.org.objectweb.asm.AnnotationVisitor +import org.jetbrains.org.objectweb.asm.ClassReader +import org.jetbrains.org.objectweb.asm.ClassVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +import java.io.File +import java.net.URLClassLoader +import kotlin.script.experimental.annotations.KotlinScript +import kotlin.script.experimental.annotations.KotlinScriptFileExtension +import kotlin.script.experimental.api.* +import kotlin.script.experimental.definitions.ScriptDefinitionFromAnnotatedBaseClass + +class LazyScriptDefinitionFromDiscoveredClass( + classBytes: ByteArray, + private val className: String, + private val classpath: List, + private val parentClassloader: ClassLoader, + private val messageCollector: MessageCollector +) : KotlinScriptDefinitionAdapterFromNewAPIBase() { + private val annotationsFromAsm = loadAnnotationsFromClass(classBytes) + + private val classloader by lazy { + if (classpath.isEmpty()) parentClassloader + else URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), parentClassloader) + } + + override val scriptDefinition: ScriptDefinition by lazy { + messageCollector.report( + CompilerMessageSeverity.LOGGING, + "Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}" + ) + try { + val cls = classloader.loadClass(className).kotlin + ScriptDefinitionFromAnnotatedBaseClass( + ScriptingEnvironment( + ScriptingEnvironmentProperties.baseClass to cls + ) + ) + } catch (ex: ClassNotFoundException) { + messageCollector.report(CompilerMessageSeverity.ERROR, "Cannot find script definition class $className") + InvalidScriptDefinition + } catch (ex: Exception) { + messageCollector.report( + CompilerMessageSeverity.ERROR, + "Error processing script definition class $className: ${ex.message}" + ) + InvalidScriptDefinition + } + } + + override val scriptFileExtensionWithDot: String by lazy { + val ext = annotationsFromAsm.find { it.name == KotlinScriptFileExtension::class.simpleName!! }?.args?.first() + ?: scriptDefinition.properties.let { + it.getOrNull(ScriptDefinitionProperties.fileExtension) ?: "kts" + } + ".$ext" + } + + override val name: String by lazy { + annotationsFromAsm.find { it.name == KotlinScript::class.simpleName!! }?.args?.first() + ?: super.name + } +} + +object InvalidScriptDefinition : ScriptDefinition { + override val properties: ScriptDefinitionPropertiesBag = ScriptDefinitionPropertiesBag() + override val compilationConfigurator: ScriptCompilationConfigurator = object : ScriptCompilationConfigurator { + override val defaultConfiguration: ScriptCompileConfiguration = ScriptDefinitionPropertiesBag() + } + override val evaluator: ScriptEvaluator<*>? = null +} + +private class BinAnnData( + val name: String, + val args: ArrayList = arrayListOf() +) + +private class TemplateAnnotationVisitor(val anns: ArrayList = arrayListOf()) : AnnotationVisitor(Opcodes.ASM5) { + override fun visit(name: String?, value: Any?) { + anns.last().args.add(value.toString()) + } +} + +private class TemplateClassVisitor(val annVisitor: TemplateAnnotationVisitor) : ClassVisitor(Opcodes.ASM5) { + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor { + val shortName = jvmDescToClassId(desc).shortClassName.asString() + if (shortName.startsWith("KotlinScript")) { + annVisitor.anns.add(BinAnnData(shortName)) + } + return annVisitor + } +} + +private fun jvmDescToClassId(desc: String): ClassId { + assert(desc.startsWith("L") && desc.endsWith(";")) { "Not a JVM descriptor: $desc" } + val name = desc.substring(1, desc.length - 1) + val cid = ClassId.topLevel(FqName(name.replace('/', '.'))) + return cid +} + +private fun loadAnnotationsFromClass(fileContents: ByteArray): ArrayList { + + val visitor = + TemplateClassVisitor(TemplateAnnotationVisitor()) + + ClassReader(fileContents).accept(visitor, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) + + return visitor.annVisitor.anns +} + diff --git a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptiDefinitionsFromClasspathDiscoverySource.kt b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptiDefinitionsFromClasspathDiscoverySource.kt new file mode 100644 index 00000000000..f1608e17916 --- /dev/null +++ b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptiDefinitionsFromClasspathDiscoverySource.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.scripting.compiler.plugin + +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate +import org.jetbrains.kotlin.script.ScriptDefinitionsSource +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.io.File +import java.io.IOException +import java.net.URLClassLoader +import java.util.jar.JarFile +import kotlin.coroutines.experimental.buildSequence +import kotlin.script.experimental.annotations.KotlinScript +import kotlin.script.experimental.api.ScriptingEnvironment +import kotlin.script.experimental.api.ScriptingEnvironmentProperties +import kotlin.script.experimental.definitions.ScriptDefinitionFromAnnotatedBaseClass + +internal const val SCRIPT_DEFINITION_MARKERS_PATH = "META-INF/kotlin/script/templates/" + +class ScriptDefinitionsFromClasspathDiscoverySource( + private val configuration: CompilerConfiguration, + private val defaultScriptDefinitionClasspath: List, + private val scriptDefinitionParentClassloader: ClassLoader, + private val messageCollector: MessageCollector +) : ScriptDefinitionsSource { + + override val definitions: Sequence = run { + discoverScriptTemplatesInClasspath( + configuration.jvmClasspathRoots, + defaultScriptDefinitionClasspath, + scriptDefinitionParentClassloader, + messageCollector + ) + } +} + +private fun discoverScriptTemplatesInClasspath( + classpath: Iterable, + defaultScriptDefinitionClasspath: List, + scriptDefinitionParentClassloader: ClassLoader, + messageCollector: MessageCollector +): Sequence = buildSequence { + for (dep in classpath) { + try { + when { + // checking for extension is the compiler current behaviour, so the same logic is implemented here + dep.isFile && dep.extension == "jar" -> { + val jar = JarFile(dep) + if (jar.getJarEntry(SCRIPT_DEFINITION_MARKERS_PATH) != null) { + for (template in jar.entries()) { + if (!template.isDirectory && template.name.startsWith(SCRIPT_DEFINITION_MARKERS_PATH)) { + val templateClassName = template.name.removePrefix(SCRIPT_DEFINITION_MARKERS_PATH) + val templateClass = jar.getJarEntry("${templateClassName.replace('.', '/')}.class") + if (templateClass == null) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "Configure scripting: class not found $templateClassName" + ) + } else { + messageCollector.report( + CompilerMessageSeverity.LOGGING, + "Configure scripting: Added template $templateClassName from $dep" + ) + yield( + LazyScriptDefinitionFromDiscoveredClass( + jar.getInputStream(templateClass).readBytes(), + templateClassName, listOf(dep) + jar.extractClasspath(defaultScriptDefinitionClasspath), + scriptDefinitionParentClassloader, messageCollector + ) + ) + } + } + } + } + } + dep.isDirectory -> { + val dir = File(dep, SCRIPT_DEFINITION_MARKERS_PATH) + if (dir.isDirectory) { + dir.listFiles().forEach { + val templateClass = File(dep, "${it.name.replace('.', '/')}.class") + if (!templateClass.exists() || !templateClass.isFile) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "Configure scripting: class not found ${it.name}" + ) + } else { + messageCollector.report( + CompilerMessageSeverity.LOGGING, + "Configure scripting: Added template ${it.name} from $dep" + ) + yield( + LazyScriptDefinitionFromDiscoveredClass( + templateClass.readBytes(), + it.name, listOf(dep) + defaultScriptDefinitionClasspath, + scriptDefinitionParentClassloader, messageCollector + ) + ) + } + } + } + } + else -> { + // assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here + messageCollector.report( + CompilerMessageSeverity.LOGGING, + "Configure scripting: Unknown classpath entry $dep" + ) + } + } + } catch (e: IOException) { + messageCollector.report( + CompilerMessageSeverity.WARNING, + "Configure scripting: unable to process classpath entry $dep: $e" + ) + } + } +} + +private fun JarFile.extractClasspath(defaultClasspath: List): List = + manifest.mainAttributes.getValue("Class-Path")?.split(" ")?.map(::File) ?: defaultClasspath + +internal fun loadScriptDefinition( + classloader: URLClassLoader, + template: String, + scriptResolverEnv: Map, + messageCollector: MessageCollector +): KotlinScriptDefinition? { + try { + val cls = classloader.loadClass(template) + val def = + if (cls.annotations.firstIsInstanceOrNull() != null) { + KotlinScriptDefinitionAdapterFromNewAPI( + ScriptDefinitionFromAnnotatedBaseClass( + ScriptingEnvironment( + ScriptingEnvironmentProperties.baseClass to cls.kotlin + ) + ) + ) + } else { + KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv) + } + messageCollector.report( + CompilerMessageSeverity.INFO, + "Added script definition $template to configuration: name = ${def.name}, " + + "resolver = ${def.dependencyResolver.javaClass.name}" + ) + return def + } catch (ex: ClassNotFoundException) { + messageCollector.report(CompilerMessageSeverity.ERROR, "Cannot find script definition template class $template") + } catch (ex: Exception) { + messageCollector.report( + CompilerMessageSeverity.ERROR, + "Error processing script definition template $template: ${ex.message}" + ) + } + return null +} \ No newline at end of file diff --git a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptingCompilerConfigurationExtension.kt b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptingCompilerConfigurationExtension.kt index bbf5536f02d..07e725d77ba 100644 --- a/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptingCompilerConfigurationExtension.kt +++ b/plugins/scripting/scripting-cli/src/org/jetbrains/kotlin/scripting/compiler/plugin/ScriptingCompilerConfigurationExtension.kt @@ -14,20 +14,9 @@ import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.JVMConfigurationKeys import org.jetbrains.kotlin.extensions.CompilerConfigurationExtension -import org.jetbrains.kotlin.script.KotlinScriptDefinition -import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate -import org.jetbrains.kotlin.script.ScriptDefinitionsSource import org.jetbrains.kotlin.script.StandardScriptDefinition -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.io.File -import java.io.IOException import java.net.URLClassLoader -import java.util.jar.JarFile -import kotlin.coroutines.experimental.buildSequence -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptingEnvironment -import kotlin.script.experimental.api.ScriptingEnvironmentProperties -import kotlin.script.experimental.definitions.ScriptDefinitionFromAnnotatedBaseClass class ScriptingCompilerConfigurationExtension(val project: MockProject) : CompilerConfigurationExtension { @@ -67,7 +56,8 @@ class ScriptingCompilerConfigurationExtension(val project: MockProject) : Compil JVMConfigurationKeys.SCRIPT_DEFINITIONS_SOURCES, ScriptDefinitionsFromClasspathDiscoverySource( configuration, - scriptResolverEnv, + emptyList(), + Thread.currentThread().contextClassLoader, // TODO: consider isolation here messageCollector ) ) @@ -81,72 +71,6 @@ class ScriptingCompilerConfigurationComponentRegistrar : ComponentRegistrar { } } -class ScriptDefinitionsFromClasspathDiscoverySource( - private val configuration: CompilerConfiguration, - private val scriptResolverEnv: Map, - private val messageCollector: MessageCollector -) : ScriptDefinitionsSource { - - override val definitions: Sequence = run { - val classpath = configuration.jvmClasspathRoots - // TODO: consider using escaping to allow kotlin escaped names in class names - val classloader = - URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader) - discoverScriptTemplatesInClasspath(configuration, messageCollector).mapNotNull { - loadScriptDefinition(classloader, it, scriptResolverEnv, messageCollector) - } - } -} - -private fun discoverScriptTemplatesInClasspath(configuration: CompilerConfiguration, messageCollector: MessageCollector): Sequence { - val templatesPath = "META-INF/kotlin/script/templates/" - return buildSequence { - for (dep in configuration.jvmClasspathRoots) { - when { - dep.isFile -> { - // this is the compiler behaviour, so the same logic implemented here - if (dep.extension == "jar") { - try { - with(JarFile(dep)) { - for (template in entries()) { - if (!template.isDirectory && template.name.startsWith(templatesPath)) { - val templateClassName = template.name.removePrefix(templatesPath) - yield(templateClassName) - messageCollector.report( - CompilerMessageSeverity.LOGGING, - "Configure scripting: Added template $templateClassName from $dep" - ) - } - } - } - } catch (e: IOException) { - messageCollector.report( - CompilerMessageSeverity.WARNING, - "Configure scripting: unable to process classpath entry $dep: $e" - ) - } - } - } - dep.isDirectory -> { - val dir = File(dep, templatesPath) - if (dir.isDirectory) { - dir.listFiles().forEach { - yield(it.name) - messageCollector.report( - CompilerMessageSeverity.LOGGING, - "Configure scripting: Added template ${it.name} from $dep" - ) - } - } - } - else -> { - // assuming that invalid classpath entries will be reported elsewhere anyway, so do not spam user with additional warnings here - messageCollector.report(CompilerMessageSeverity.LOGGING, "Configure scripting: Unknown classpath entry $dep") - } - } - } - } -} fun configureScriptDefinitions( scriptTemplates: List, @@ -161,7 +85,12 @@ fun configureScriptDefinitions( URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), Thread.currentThread().contextClassLoader) var hasErrors = false for (template in scriptTemplates) { - val def = loadScriptDefinition(classloader, template, scriptResolverEnv, messageCollector) + val def = loadScriptDefinition( + classloader, + template, + scriptResolverEnv, + messageCollector + ) if (!hasErrors && def == null) hasErrors = true if (def != null) { configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, def) @@ -174,35 +103,3 @@ fun configureScriptDefinitions( } } -private fun loadScriptDefinition( - classloader: URLClassLoader, - template: String, - scriptResolverEnv: Map, - messageCollector: MessageCollector -): KotlinScriptDefinition? { - try { - val cls = classloader.loadClass(template) - val def = - if (cls.annotations.firstIsInstanceOrNull() != null) { - KotlinScriptDefinitionAdapterFromNewAPI( - ScriptDefinitionFromAnnotatedBaseClass(ScriptingEnvironment(ScriptingEnvironmentProperties.baseClass to cls.kotlin)) - ) - } else { - KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, scriptResolverEnv) - } - messageCollector.report( - CompilerMessageSeverity.INFO, - "Added script definition $template to configuration: name = ${def.name}, " + - "resolver = ${def.dependencyResolver.javaClass.name}" - ) - return def - } catch (ex: ClassNotFoundException) { - messageCollector.report(CompilerMessageSeverity.ERROR, "Cannot find script definition template class $template") - } catch (ex: Exception) { - messageCollector.report( - CompilerMessageSeverity.ERROR, - "Error processing script definition template $template: ${ex.message}" - ) - } - return null -}