fixes after review
This commit is contained in:
committed by
Pavel V. Talanov
parent
5a77d2e92b
commit
b0bff64cff
@@ -69,13 +69,6 @@ class KotlinScriptExternalDependenciesUnion(val dependencies: Iterable<KotlinScr
|
||||
override val sources: Iterable<File> get() = dependencies.flatMap { it.sources }
|
||||
}
|
||||
|
||||
fun Iterable<File>.isSameClasspathAs(other: Iterable<File>): Boolean {
|
||||
val c1 = map { it.canonicalPath }.toHashSet()
|
||||
val c2 = other.map { it.canonicalPath }.toHashSet()
|
||||
return c1.size == c2.size &&
|
||||
c1.zip(c2).all { it.first == it.second }
|
||||
}
|
||||
|
||||
data class ScriptParameter(val name: Name, val type: KotlinType)
|
||||
|
||||
fun <TF> getFileName(file: TF): String = when (file) {
|
||||
|
||||
+12
-5
@@ -24,6 +24,7 @@ import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
|
||||
class KotlinScriptExternalImportsProvider(val project: Project, private val scriptDefinitionProvider: KotlinScriptDefinitionProvider) {
|
||||
|
||||
private val cacheLock = ReentrantReadWriteLock()
|
||||
private val cache = hashMapOf<String, KotlinScriptExternalDependencies>()
|
||||
private val cacheOfNulls = hashSetOf<String>()
|
||||
@@ -82,7 +83,7 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
|
||||
val deps = scriptDef.getDependenciesFor(file, project, oldDeps)
|
||||
when {
|
||||
deps != null && (oldDeps == null ||
|
||||
!deps.classpath.isSameClasspathAs(oldDeps.classpath) || !deps.sources.isSameClasspathAs(oldDeps.sources)) -> {
|
||||
!deps.classpath.isSamePathListAs(oldDeps.classpath) || !deps.sources.isSamePathListAs(oldDeps.sources)) -> {
|
||||
// changed or new
|
||||
cache.put(path, deps)
|
||||
cacheOfNulls.remove(path)
|
||||
@@ -104,9 +105,8 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
|
||||
|
||||
fun invalidateCaches() {
|
||||
cacheLock.write {
|
||||
cache.keys.toList().apply {
|
||||
cache.clear()
|
||||
}
|
||||
cache.clear()
|
||||
cacheOfNulls.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,4 +136,11 @@ class KotlinScriptExternalImportsProvider(val project: Project, private val scri
|
||||
fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
|
||||
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Iterable<File>.isSamePathListAs(other: Iterable<File>): Boolean {
|
||||
val c1 = asSequence().map { it.canonicalPath }
|
||||
val c2 = other.asSequence().map { it.canonicalPath }
|
||||
return c1 == c2
|
||||
}
|
||||
|
||||
|
||||
+14
-41
@@ -16,54 +16,27 @@
|
||||
|
||||
package org.jetbrains.kotlin.script
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtUserType
|
||||
import org.jetbrains.kotlin.resolve.BindingTraceContext
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
|
||||
internal class KtAnnotationWrapper(val psi: KtAnnotationEntry) {
|
||||
val name: String
|
||||
get() = (psi.typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous()
|
||||
|
||||
val valueArguments by lazy {
|
||||
val valueArguments: List<Pair<String?, Any?>> by lazy {
|
||||
psi.valueArguments.map {
|
||||
Pair(it.getArgumentName()?.toString(), convert(it.getArgumentExpression()!!))
|
||||
val evaluator = ConstantExpressionEvaluator(DefaultBuiltIns.Instance)
|
||||
val trace = BindingTraceContext()
|
||||
val result = evaluator.evaluateToConstantValue(it.getArgumentExpression()!!, trace, TypeUtils.NO_EXPECTED_TYPE)
|
||||
it.getArgumentName()?.asName.toString() to result?.value
|
||||
// TODO: consider inspecting `trace` to find diagnostics reported during the computation (such as division by zero, integer overflow, invalid annotation parameters etc.)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun String?.orAnonymous(kind: String = ""): String {
|
||||
return this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
|
||||
}
|
||||
|
||||
internal fun convert(expression: KtExpression): Any? = when (expression) {
|
||||
is KtStringTemplateExpression -> {
|
||||
if (expression.entries.isEmpty())
|
||||
""
|
||||
else if (expression.entries.size == 1)
|
||||
convert(expression.entries[0])
|
||||
else
|
||||
""
|
||||
// TODO: parse expressions, etc. e.g.:
|
||||
// convertStringTemplateExpression(expression, parent, expression.entries.size - 1)
|
||||
}
|
||||
else -> null
|
||||
|
||||
}
|
||||
|
||||
internal fun convert(entry: KtStringTemplateEntry): Any? = when (entry) {
|
||||
is KtStringTemplateEntryWithExpression -> convertOrEmpty(entry.expression)
|
||||
is KtEscapeStringTemplateEntry -> entry.unescapedValue
|
||||
else -> {
|
||||
StringUtil.unescapeStringCharacters(entry.text)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun convertOrEmpty(expression: KtExpression?): Any? {
|
||||
return if (expression != null) convert(expression) else null
|
||||
}
|
||||
internal fun String?.orAnonymous(kind: String = ""): String =
|
||||
this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
|
||||
}
|
||||
|
||||
fun parseAnnotation(ann: KtAnnotationEntry): Pair<String, Iterable<Any?>> {
|
||||
val wann = KtAnnotationWrapper(ann)
|
||||
val vals = wann.valueArguments
|
||||
return Pair(wann.name, vals)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,14 +28,12 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import java.lang.reflect.InvocationHandler
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.memberFunctions
|
||||
import kotlin.reflect.memberProperties
|
||||
import kotlin.reflect.*
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@@ -56,12 +54,12 @@ annotation class AcceptedAnnotations(vararg val supportedAnnotationClasses: KCla
|
||||
|
||||
data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val environment: Map<String, Any?>?) : KotlinScriptDefinition {
|
||||
|
||||
private val definitionAnnotation by lazy { template.annotations.mapNotNull { it as? ScriptTemplateDefinition }.firstOrNull() }
|
||||
private val definitionAnnotation by lazy { template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>() }
|
||||
|
||||
override val name = template.simpleName!!
|
||||
|
||||
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
|
||||
template.constructors.first().parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByFqName(scriptDescriptor, it.type.toString())) }
|
||||
template.primaryConstructor!!.parameters.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByFqName(scriptDescriptor, it.type.toString())) }
|
||||
|
||||
override fun getScriptSupertypes(scriptDescriptor: ScriptDescriptor): List<KotlinType> =
|
||||
listOf(getKotlinTypeByFqName(scriptDescriptor, template.qualifiedName!!))
|
||||
@@ -76,14 +74,19 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
|
||||
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, KotlinParserDefinition.STD_SCRIPT_EXT)
|
||||
|
||||
private class ResolverWithAcceptedAnnotations(val resolverClass: KClass<out ScriptDependenciesResolver>) {
|
||||
val resolver by lazy { resolverClass.constructors.first().call() }
|
||||
val resolver by lazy { resolverClass.primaryConstructor!!.call() }
|
||||
val acceptedAnnotations by lazy {
|
||||
val resolveMethodName = ScriptDependenciesResolver::class.memberFunctions.first().name
|
||||
resolverClass.memberFunctions.find { it.name == resolveMethodName }?.annotations
|
||||
?.mapNotNull { it as? AcceptedAnnotations }
|
||||
?.flatMap {
|
||||
val resolveMethod = ScriptDependenciesResolver::resolve
|
||||
val resolverMethodAnnotations =
|
||||
resolverClass.memberFunctions.find {
|
||||
it.name == resolveMethod.name &&
|
||||
sameSignature(it, resolveMethod)
|
||||
}
|
||||
?.annotations
|
||||
?.filterIsInstance<AcceptedAnnotations>()
|
||||
resolverMethodAnnotations?.flatMap {
|
||||
val v = it.supportedAnnotationClasses
|
||||
v.toList() // TODO: find out why if inlined, it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass"
|
||||
v.toList() // TODO: inline after KT-9453 is resolved (now it fails with "java.lang.Class cannot be cast to kotlin.reflect.KClass")
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
@@ -103,23 +106,24 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
|
||||
.map { KtAnnotationWrapper(it) }
|
||||
.mapNotNull { wrappedAnn ->
|
||||
// TODO: consider advanced matching using semantic similar to actual resolving
|
||||
supportedAnnotationClasses.find { wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName }
|
||||
?.let { Pair(it, wrappedAnn) }
|
||||
supportedAnnotationClasses.find {
|
||||
wrappedAnn.name == it.simpleName || wrappedAnn.name == it.qualifiedName
|
||||
}?.let { it to wrappedAnn }
|
||||
}
|
||||
val annotations = fileAnnotations.map {
|
||||
val annotations = fileAnnotations.map { annClassToWrapper ->
|
||||
try {
|
||||
val handler = AnnProxyInvocationHandler(it.first, it.second.valueArguments)
|
||||
val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(it.first.java), handler) as Annotation
|
||||
Pair(it.first, proxy)
|
||||
val handler = AnnProxyInvocationHandler(annClassToWrapper.first, annClassToWrapper.second.valueArguments)
|
||||
val proxy = Proxy.newProxyInstance((template as Any).javaClass.classLoader, arrayOf(annClassToWrapper.first.java), handler) as Annotation
|
||||
annClassToWrapper.first to proxy
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
Pair(it.first, InvalidScriptResolverAnnotation(it.second.name, it.second.valueArguments, ex))
|
||||
annClassToWrapper.first to InvalidScriptResolverAnnotation(annClassToWrapper.second.name, annClassToWrapper.second.valueArguments, ex)
|
||||
}
|
||||
}
|
||||
val fileDeps = dependenciesResolvers.mapNotNull { resolverWrapper ->
|
||||
val supportedAnnotations = annotations.mapNotNull { ann ->
|
||||
val annFQN = ann.first.qualifiedName
|
||||
if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) ann.second else null
|
||||
val supportedAnnotations = annotations.mapNotNull { annClassToWrapper ->
|
||||
val annFQN = annClassToWrapper.first.qualifiedName
|
||||
if (resolverWrapper.acceptedAnnotations.any { it.qualifiedName == annFQN }) annClassToWrapper.second else null
|
||||
}
|
||||
resolverWrapper.resolver.resolve(getFile(file), supportedAnnotations, environment, previousDependencies)
|
||||
}
|
||||
@@ -148,7 +152,6 @@ data class KotlinScriptDefinitionFromTemplate(val template: KClass<out Any>, val
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
class InvalidScriptResolverAnnotation(val name: String, val params: Iterable<Any?>, val error: Exception? = null) : Annotation
|
||||
|
||||
class AnnProxyInvocationHandler<out K: KClass<out Any>>(val targetAnnClass: K, val annParams: List<Pair<String?, Any?>>) : InvocationHandler {
|
||||
@@ -161,4 +164,12 @@ class AnnProxyInvocationHandler<out K: KClass<out Any>>(val targetAnnClass: K, v
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun sameSignature(left: KFunction<*>, right: KFunction<*>): Boolean =
|
||||
left.parameters.size == right.parameters.size &&
|
||||
left.parameters.zip(right.parameters).all {
|
||||
it.first.kind == KParameter.Kind.INSTANCE ||
|
||||
it.first.type == it.second.type
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -42,24 +42,24 @@ interface ScriptTemplateProvider {
|
||||
}
|
||||
|
||||
fun makeScriptDefsFromTemplateProviderExtensions(project: Project,
|
||||
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = fun (ep, ex) = println(ex)): List<KotlinScriptDefinitionFromTemplate> =
|
||||
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = { ep, ex -> throw ex }): List<KotlinScriptDefinitionFromTemplate> =
|
||||
makeScriptDefsFromTemplateProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplateProvider.EP_NAME).extensions.asIterable(),
|
||||
errorsHandler)
|
||||
|
||||
fun makeScriptDefsFromTemplateProviders(providers: Iterable<ScriptTemplateProvider>,
|
||||
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = fun (ep, ex) = println(ex)
|
||||
errorsHandler: ((ScriptTemplateProvider, Exception) -> Unit) = { ep, ex -> throw ex }
|
||||
): List<KotlinScriptDefinitionFromTemplate> {
|
||||
val idToVersion = hashMapOf<String, Int>()
|
||||
return providers.filter { it.isValid }.sortedByDescending { it.version }.mapNotNull {
|
||||
return providers.filter { it.isValid }.sortedByDescending { it.version }.mapNotNull { provider ->
|
||||
try {
|
||||
idToVersion.get(it.id)?.let { ver -> errorsHandler(it, RuntimeException("Conflicting scriptTemplateProvider ${it.id}, using one with version $ver")) }
|
||||
val loader = URLClassLoader(it.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader)
|
||||
val cl = loader.loadClass(it.templateClassName)
|
||||
idToVersion.put(it.id, it.version)
|
||||
KotlinScriptDefinitionFromTemplate(cl.kotlin, it.environment)
|
||||
idToVersion.get(provider.id)?.let { ver -> errorsHandler(provider, RuntimeException("Conflicting scriptTemplateProvider ${provider.id}, using one with version $ver")) }
|
||||
val loader = URLClassLoader(provider.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplateProvider::class.java.classLoader)
|
||||
val cl = loader.loadClass(provider.templateClassName)
|
||||
idToVersion.put(provider.id, provider.version)
|
||||
KotlinScriptDefinitionFromTemplate(cl.kotlin, provider.environment)
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
errorsHandler(it, ex)
|
||||
errorsHandler(provider, ex)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// TODO: the contetnts of this file should go into ScriptTest.kt and replace appropriate xml-based functionality,
|
||||
// as soon as the the latter is removed from the codebase
|
||||
|
||||
class ScriptTest2 {
|
||||
@Test
|
||||
fun testScriptWithParam() {
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
<orderEntry type="module" module-name="light-classes" />
|
||||
<orderEntry type="module" module-name="frontend.java" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
<orderEntry type="library" name="gradle-and-groovy-plugin" level="project" />
|
||||
<orderEntry type="library" scope="PROVIDED" name="gradle-and-groovy-plugin" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
+1
-1
@@ -114,7 +114,7 @@ class KotlinScriptConfigurationManager(
|
||||
}
|
||||
|
||||
private fun reloadScriptDefinitions() {
|
||||
(makeScriptDefsFromTemplateProviderExtensions(project /* TODO: add logging here */) +
|
||||
(makeScriptDefsFromTemplateProviderExtensions(project, { ep, ex -> /* TODO: add logging here */ }) +
|
||||
loadScriptConfigsFromProjectRoot(File(project.basePath ?: "")).map { KotlinConfigurableScriptDefinition(it, kotlinEnvVars) }).let {
|
||||
if (it.isNotEmpty()) {
|
||||
scriptDefinitionProvider.setScriptDefinitions(it + StandardScriptDefinition)
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class GradleScriptTemplateProvider(project: Project, gim: GradleInstallationMana
|
||||
gradleLibsPath?.listFiles { file -> file.extension == "jar" && depLibsPrefixes.any { file.name.startsWith(it) } }
|
||||
?.map { it.canonicalPath }
|
||||
?: emptyList()
|
||||
override val environment: Map<String, Any?>? = kotlin.collections.mapOf("gradleHome" to gradleHome)
|
||||
override val environment: Map<String, Any?>? = mapOf("gradleHome" to gradleHome)
|
||||
|
||||
companion object {
|
||||
private val depLibsPrefixes = listOf("gradle-script-kotlin", "gradle-core")
|
||||
|
||||
Reference in New Issue
Block a user