Add dependency from annotation extraction mechanisms to the prototype, with tests
This commit is contained in:
committed by
Pavel V. Talanov
parent
3f5a2c2781
commit
402e8c1e3e
+5
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
* 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.
|
||||
@@ -20,6 +20,9 @@ import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinderFactory
|
||||
|
||||
class JvmCliVirtualFileFinderFactory(private val index: JvmDependenciesIndex) : JvmVirtualFileFinderFactory {
|
||||
class JvmLazyCliVirtualFileFinderFactory(private val makeIndex: () -> JvmDependenciesIndex) : JvmVirtualFileFinderFactory {
|
||||
|
||||
private val index by lazy { makeIndex() }
|
||||
|
||||
override fun create(scope: GlobalSearchScope): JvmVirtualFileFinder = JvmCliVirtualFileFinder(index)
|
||||
}
|
||||
@@ -163,9 +163,7 @@ class KotlinCoreEnvironment private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fillClasspath(configuration)
|
||||
|
||||
project.registerService(JvmVirtualFileFinderFactory::class.java, JvmCliVirtualFileFinderFactory(index))
|
||||
project.registerService(JvmVirtualFileFinderFactory::class.java, JvmLazyCliVirtualFileFinderFactory({ fillClasspath(configuration); index } ))
|
||||
|
||||
ExternalDeclarationsProvider.registerExtensionPoint(project)
|
||||
ExpressionCodegenExtension.registerExtensionPoint(project)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.text.StringUtil
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
object SimpleUntypedAst {
|
||||
|
||||
sealed class Node<out T>(val name: String) {
|
||||
class empty(name: String) : Node<Unit>(name)
|
||||
class str(name: String, val value: String) : Node<String>(name)
|
||||
class int(name: String, val value: Int) : Node<Int>(name)
|
||||
class list<out T>(name: String, val value: List<Node<T>>) : Node<List<Node<T>>>(name)
|
||||
class ann(name: String, val value: List<Node<Any>>) : Node<List<Node<Any>>>(name)
|
||||
}
|
||||
|
||||
class KtAnnotationWrapper(val psi: KtAnnotationEntry) {
|
||||
val name: String
|
||||
get() = (psi.typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous()
|
||||
|
||||
val valueArguments by lazy {
|
||||
psi.valueArguments.map {
|
||||
val name = it.getArgumentName()?.asName?.identifier.orAnonymous()
|
||||
convert(it.getArgumentExpression()!!)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun String?.orAnonymous(kind: String = ""): String {
|
||||
return this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
|
||||
}
|
||||
|
||||
internal fun convert(expression: KtExpression): Node<Any> = when (expression) {
|
||||
is KtStringTemplateExpression -> {
|
||||
if (expression.entries.isEmpty())
|
||||
SimpleUntypedAst.Node.str(name, "")
|
||||
else if (expression.entries.size == 1)
|
||||
convert(expression.entries[0])
|
||||
else
|
||||
SimpleUntypedAst.Node.str(name, "")
|
||||
// convertStringTemplateExpression(expression, parent, expression.entries.size - 1)
|
||||
}
|
||||
else -> Node.empty(name)
|
||||
|
||||
}
|
||||
|
||||
internal fun convert(entry: KtStringTemplateEntry): Node<Any> = when (entry) {
|
||||
is KtStringTemplateEntryWithExpression -> convertOrEmpty(entry.expression)
|
||||
is KtEscapeStringTemplateEntry -> Node.str(name, entry.unescapedValue)
|
||||
else -> {
|
||||
Node.str(name, StringUtil.unescapeStringCharacters(entry.text))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun convertOrEmpty(expression: KtExpression?): Node<Any> {
|
||||
return if (expression != null) convert(expression) else Node.empty(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ 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.KtAnnotation
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import kotlin.reflect.KClass
|
||||
@@ -34,7 +36,8 @@ interface ScriptDependencies {
|
||||
}
|
||||
|
||||
interface GetScriptDependencies {
|
||||
operator fun invoke(annotations: Iterable<Annotation>, context: Any?): ScriptDependencies?
|
||||
operator fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): ScriptDependencies? = null
|
||||
operator fun invoke(context: Any?): ScriptDependencies? = null
|
||||
}
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@@ -64,7 +67,7 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
|
||||
}
|
||||
|
||||
private val dependencies by lazy {
|
||||
dependenciesExtractors.mapNotNull { it(template.annotations, context) }
|
||||
dependenciesExtractors.mapNotNull { it(context) }
|
||||
}
|
||||
|
||||
override fun getScriptDependenciesClasspath(): List<String> = dependencies.flatMap { it.classpath }
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
|
||||
// this script expected parameter num : Int
|
||||
|
||||
@file:org.jetbrains.kotlin.scripts.depends("@{runtime}")
|
||||
|
||||
fun fib(n: Int): Int {
|
||||
val v = if(n < 2) 1 else fib(n-1) + fib(n-2)
|
||||
System.out.println("fib($n)=$v")
|
||||
return v
|
||||
}
|
||||
|
||||
val hdr = "Num".decapitalize()
|
||||
|
||||
System.out.println("$hdr: $num")
|
||||
val result = fib(num)
|
||||
|
||||
@@ -85,7 +85,7 @@ fun classpathFromProperty(): List<String> =
|
||||
.map { File(it).canonicalPath }
|
||||
} ?: emptyList()
|
||||
|
||||
private fun URL.toFile() =
|
||||
fun URL.toFile() =
|
||||
try {
|
||||
File(toURI().schemeSpecificPart)
|
||||
}
|
||||
|
||||
@@ -22,10 +22,10 @@ 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.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.script.*
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
@@ -33,7 +33,6 @@ 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
|
||||
|
||||
@@ -59,6 +58,13 @@ class ScriptTest2 {
|
||||
aClass!!.getConstructor(Integer.TYPE, Integer.TYPE).newInstance(4, 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScriptWithDependsAnn() {
|
||||
val aClass = compileScript("fib_ext_ann.kts", ScriptWithIntParam::class, null)
|
||||
Assert.assertNotNull(aClass)
|
||||
aClass!!.getConstructor(Integer.TYPE).newInstance(4)
|
||||
}
|
||||
|
||||
private fun compileScript(
|
||||
scriptPath: String,
|
||||
scriptBase: KClass<out Any>,
|
||||
@@ -84,7 +90,6 @@ class ScriptTest2 {
|
||||
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)
|
||||
|
||||
@@ -111,14 +116,44 @@ class ScriptTest2 {
|
||||
|
||||
class GetTestKotlinScriptDependencies : GetScriptDependencies {
|
||||
|
||||
override fun invoke(annotations: Iterable<Annotation>, context: Any?): ScriptDependencies? =
|
||||
object : ScriptDependencies {
|
||||
override val classpath =
|
||||
(GetTestKotlinScriptDependencies::class.java.classLoader as? URLClassLoader)?.urLs?.map { it.file } ?: emptyList()
|
||||
override val implicitImports = emptyList<String>()
|
||||
private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() }
|
||||
|
||||
override fun invoke(annotations: Iterable<KtAnnotationEntry>, context: Any?): ScriptDependencies? {
|
||||
if (annotations.none()) return null
|
||||
val anns = annotations.map { parseAnnotation(it) }.filter { it.name == depends::class.simpleName }
|
||||
val cp = anns.flatMap {
|
||||
it.value.mapNotNull {
|
||||
when (it) {
|
||||
is SimpleUntypedAst.Node.str -> if (it.value == "@{runtime}") kotlinPaths.runtimePath.canonicalPath else it.value
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
return object : ScriptDependencies {
|
||||
override val classpath = cp
|
||||
override val implicitImports = emptyList<String>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun classpathFromClassloader(): List<String> =
|
||||
(GetTestKotlinScriptDependencies::class.java.classLoader as? URLClassLoader)?.urLs
|
||||
?.mapNotNull { it.toFile()?.canonicalPath }
|
||||
?.filter { it.contains("out/test") }
|
||||
?: emptyList()
|
||||
|
||||
override fun invoke(context: Any?): ScriptDependencies? {
|
||||
return object : ScriptDependencies {
|
||||
override val classpath = classpathFromClassloader()
|
||||
override val implicitImports = emptyList<String>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun parseAnnotation(ann: KtAnnotationEntry): SimpleUntypedAst.Node.list<Any> {
|
||||
val wann = SimpleUntypedAst.KtAnnotationWrapper(ann)
|
||||
val vals = wann.valueArguments
|
||||
return SimpleUntypedAst.Node.list(wann.name, vals)
|
||||
}
|
||||
|
||||
@ScriptFilePattern(".*\\.kts")
|
||||
@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class)
|
||||
@@ -131,3 +166,7 @@ abstract class ScriptWithClassParam(param: TestParamClass)
|
||||
@ScriptFilePattern(".*\\.kts")
|
||||
@ScriptDependencyExtractor(GetTestKotlinScriptDependencies::class)
|
||||
abstract class ScriptWithBaseClass(num: Int, passthrough: Int) : TestDSLClassWithParam(passthrough)
|
||||
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class depends(val path: String)
|
||||
|
||||
Reference in New Issue
Block a user