diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt new file mode 100644 index 00000000000..5198b7bbbf1 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/scriptTemplate.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2016 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.vfs.VirtualFile +import org.jetbrains.kotlin.descriptors.ScriptDescriptor +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.parsing.KotlinParserDefinition +import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.types.KotlinType +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class ScriptFilePattern(val pattern: String) + +interface ScriptDependencies { + val classpath: List + val implicitImports: List +} + +interface GetScriptDependencies { + operator fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? +} + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class ScriptDependencyExtractor(val extractor: KClass) + +data class KotlinScriptDefinitionFromTemplate(val template: KClass, val context: Any?) : KotlinScriptDefinition { + override val name = template.simpleName!! + + override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List = + template.constructors.first().parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByFqName(scriptDescriptor, it.type.toString())) } + + override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List = + listOf(getKotlinTypeByFqName(scriptDescriptor, template.qualifiedName!!)) + + override fun getScriptParametersToPassToSuperclass(scriptDescriptor: ScriptDescriptor): List = + getScriptParameters(scriptDescriptor).map { it.name } + + override fun isScript(file: TF): Boolean = + template.annotations.any { (it as? ScriptFilePattern)?.let { Regex(it.pattern).matches(getFileName(file)) } ?: false } + + // TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern + override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT) + + private val dependenciesExtractors by lazy { + template.annotations.mapNotNull { it as? ScriptDependencyExtractor }.map { it.extractor.constructors.first().call() } + } + + private val dependencies by lazy { + dependenciesExtractors.mapNotNull { it(template.annotations, context) } + } + + override fun getScriptDependenciesClasspath(): List = dependencies.flatMap { it.classpath } +} + diff --git a/compiler/frontend/src/org/jetbrains/kotlin/script/tmpConfigBasedScriptSupport.kt b/compiler/frontend/src/org/jetbrains/kotlin/script/tmpConfigBasedScriptSupport.kt new file mode 100644 index 00000000000..7a8241e71ae --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/script/tmpConfigBasedScriptSupport.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2016 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.util.JDOMUtil +import com.intellij.util.xmlb.XmlSerializer +import com.intellij.util.xmlb.annotations.AbstractCollection +import com.intellij.util.xmlb.annotations.Tag +import java.io.File +import java.net.URLClassLoader +import java.util.* + +val SCRIPT_CONFIG_DEF_FILE_EXTENSION = ".ktsdef.xml" + +fun isScriptDefConfigFile(file: File) = file.isFile && file.name.endsWith(SCRIPT_CONFIG_DEF_FILE_EXTENSION) + +fun isScriptDefConfigFile(file: com.intellij.openapi.vfs.VirtualFile) = !file.isDirectory && file.name.endsWith(SCRIPT_CONFIG_DEF_FILE_EXTENSION) + +fun loadScriptDefConfigsFromProjectRoot(projectRoot: java.io.File): List = + projectRoot.walk().filter(::isScriptDefinitionConfigFile).toList() + .flatMap { loadScriptDefConfigs(it) } + +fun loadScriptDefConfigs(configFile: File): List = + JDOMUtil.loadDocument(configFile).rootElement.children.mapNotNull { + XmlSerializer.deserialize(it, KotlinScripDeftConfig::class.java) + } + +fun makeScriptDefsFromConfigs(configs: List): List = + configs.map { + val loader = URLClassLoader(it.classpath.map { File(it).toURI().toURL() }.toTypedArray()) + val cl = loader.loadClass(it.def) + KotlinScriptDefinitionFromTemplate(cl.kotlin, null) + } + +@Tag("scriptDef") +data class KotlinScripDeftConfig( + @Tag("def") + var def: String = "", + + @Tag("classpath") + @AbstractCollection(surroundWithTag = false, elementTag = "path", elementValueAttribute = "") + var classpath: MutableList = ArrayList() +) diff --git a/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt new file mode 100644 index 00000000000..6c6470ac498 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTest2.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2016 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.scripts + +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.messages.* +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot +import org.jetbrains.kotlin.codegen.CompilationException +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.config.addKotlinSourceRoot +import org.jetbrains.kotlin.script.* +import org.jetbrains.kotlin.test.ConfigurationKind +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.TestJdkKind +import org.jetbrains.kotlin.utils.PathUtil +import org.junit.Assert +import org.junit.Test +import java.io.File +import java.net.URLClassLoader +import kotlin.reflect.KClass + +class ScriptTest2 { + @Test + fun testScriptWithParam() { + val aClass = compileScript("fib.kts", ScriptWithIntParam::class, null) + Assert.assertNotNull(aClass) + aClass!!.getConstructor(Integer.TYPE).newInstance(4) + } + + @Test + fun testScriptWithClassParameter() { + val aClass = compileScript("fib_cp.kts", ScriptWithClassParam::class, null, runIsolated = false) + Assert.assertNotNull(aClass) + aClass!!.getConstructor(TestParamClass::class.java).newInstance(TestParamClass(4)) + } + + @Test + fun testScriptWithBaseClassWithParam() { + val aClass = compileScript("fib_dsl.kts", ScriptWithBaseClass::class, null, runIsolated = false) + Assert.assertNotNull(aClass) + aClass!!.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(4, 1) + } + + private fun compileScript( + scriptPath: String, + scriptBase: KClass, + context: Any? = null, + runIsolated: Boolean = true, + suppressOutput: Boolean = false): Class<*>? = + compileScriptImpl("compiler/testData/script/" + scriptPath, KotlinScriptDefinitionFromTemplate(scriptBase, context), runIsolated, suppressOutput) + + private fun compileScriptImpl( + scriptPath: String, + scriptDefinition: KotlinScriptDefinition, + runIsolated: Boolean, + suppressOutput: Boolean): Class<*>? + { + val paths = PathUtil.getKotlinPathsForDistDirectory() + val messageCollector = + if (suppressOutput) MessageCollector.NONE + else PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, false) + + val rootDisposable = Disposer.newDisposable() + try { + val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.JDK_ONLY, TestJdkKind.FULL_JDK) + configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) + configuration.addKotlinSourceRoot(scriptPath) + configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, scriptDefinition) + scriptDefinition.getScriptDependenciesClasspath().forEach { configuration.addJvmClasspathRoot(File(it)) } + + val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) + + try { + return if (runIsolated) KotlinToJVMBytecodeCompiler.compileScript(environment, paths) + else KotlinToJVMBytecodeCompiler.compileScript(environment, this.javaClass.classLoader) + } + catch (e: CompilationException) { + messageCollector.report(CompilerMessageSeverity.EXCEPTION, OutputMessageUtil.renderException(e), + MessageUtil.psiElementToMessageLocation(e.element)) + return null + } + catch (t: Throwable) { + MessageCollectorUtil.reportException(messageCollector, t) + throw t + } + + } + finally { + Disposer.dispose(rootDisposable) + } + } +} + +class GetTestKotlinScriptDependencies : GetScriptDependencies { + + override fun invoke(annotations: Iterable, context: Any?): ScriptDependencies? = + object : ScriptDependencies { + override val classpath = + (GetTestKotlinScriptDependencies::class.java.classLoader as? URLClassLoader)?.urLs?.map { it.file } ?: emptyList() + override val implicitImports = emptyList() + } +} + + +@ScriptFilePattern(".*\\.kts") +@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) +abstract class ScriptWithIntParam(num: Int) + +@ScriptFilePattern(".*\\.kts") +@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) +abstract class ScriptWithClassParam(param: TestParamClass) + +@ScriptFilePattern(".*\\.kts") +@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class) +abstract class ScriptWithBaseClass(num: Int, passthrough: Int) : TestDSLClassWithParam(passthrough)