Move some script related part of frontend to separate module to avoid using kotlin-reflect.jar in frontend module
Main goal is get rid of kotlin-reflect.jar from modules what required for minimal compiler.jar which can compile Kotlin only to JS to make it smaller.
This commit is contained in:
+211
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.project.Project
|
||||
import com.intellij.openapi.vfs.StandardFileSystems
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.NameUtils
|
||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.File
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.memberFunctions
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import kotlin.script.dependencies.BasicScriptDependenciesResolver
|
||||
import kotlin.script.dependencies.KotlinScriptExternalDependencies
|
||||
import kotlin.script.dependencies.ScriptContents
|
||||
import kotlin.script.dependencies.ScriptDependenciesResolver
|
||||
import kotlin.script.templates.AcceptedAnnotations
|
||||
|
||||
open class KotlinScriptDefinitionFromAnnotatedTemplate(
|
||||
template: KClass<out Any>,
|
||||
providedResolver: ScriptDependenciesResolver? = null,
|
||||
providedScriptFilePattern: String? = null,
|
||||
val environment: Map<String, Any?>? = null
|
||||
) : KotlinScriptDefinition(template) {
|
||||
|
||||
val scriptFilePattern by lazy {
|
||||
providedScriptFilePattern
|
||||
?: template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>()?.scriptFilePattern
|
||||
?: template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.ScriptTemplateDefinition>()?.scriptFilePattern
|
||||
?: DEFAULT_SCRIPT_FILE_PATTERN
|
||||
}
|
||||
|
||||
val resolver: ScriptDependenciesResolver? by lazy {
|
||||
val defAnn by lazy { template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>() }
|
||||
val legacyDefAnn by lazy { template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.ScriptTemplateDefinition>() }
|
||||
when {
|
||||
providedResolver != null -> providedResolver
|
||||
// TODO: logScriptDefMessage missing or invalid constructor
|
||||
defAnn != null ->
|
||||
try {
|
||||
defAnn.resolver.primaryConstructor?.call() ?: null.apply {
|
||||
log.warn("[kts] No default constructor found for ${defAnn.resolver.qualifiedName}")
|
||||
}
|
||||
}
|
||||
catch (ex: ClassCastException) {
|
||||
log.warn("[kts] Script def error ${ex.message}")
|
||||
null
|
||||
}
|
||||
legacyDefAnn != null ->
|
||||
try {
|
||||
log.warn("[kts] Deprecated annotations on the script template are used, please update the provider")
|
||||
legacyDefAnn.resolver.primaryConstructor?.call()?.let {
|
||||
LegacyScriptDependenciesResolverWrapper(it)
|
||||
}
|
||||
?: null.apply {
|
||||
log.warn("[kts] No default constructor found for ${legacyDefAnn.resolver.qualifiedName}")
|
||||
}
|
||||
}
|
||||
catch (ex: ClassCastException) {
|
||||
log.warn("[kts] Script def error ${ex.message}")
|
||||
null
|
||||
}
|
||||
else -> BasicScriptDependenciesResolver()
|
||||
}
|
||||
}
|
||||
|
||||
val samWithReceiverAnnotations: List<String>? by lazy {
|
||||
template.annotations.firstIsInstanceOrNull<kotlin.script.extensions.SamWithReceiverAnnotations>()?.annotations?.toList()
|
||||
?: template.annotations.firstIsInstanceOrNull<org.jetbrains.kotlin.script.SamWithReceiverAnnotations>()?.annotations?.toList()
|
||||
}
|
||||
|
||||
private val acceptedAnnotations: List<KClass<out Annotation>> by lazy {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
val resolveMethod = ScriptDependenciesResolver::resolve
|
||||
val resolverMethodAnnotations =
|
||||
resolver?.let { it::class }?.memberFunctions?.find { function ->
|
||||
function.name == resolveMethod.name &&
|
||||
sameSignature(function, resolveMethod)
|
||||
}?.annotations?.filterIsInstance<AcceptedAnnotations>()
|
||||
resolverMethodAnnotations?.flatMap {
|
||||
it.supportedAnnotationClasses.toList()
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
override val name = template.simpleName!!
|
||||
|
||||
override fun <TF: Any> isScript(file: TF): Boolean =
|
||||
scriptFilePattern.let { Regex(it).matches(getFileName(file)) }
|
||||
|
||||
// TODO: implement other strategy - e.g. try to extract something from match with ScriptFilePattern
|
||||
override fun getScriptName(script: KtScript): Name = NameUtils.getScriptNameForFile(script.containingKtFile.name)
|
||||
|
||||
override fun <TF: Any> getDependenciesFor(file: TF, project: Project, previousDependencies: KotlinScriptExternalDependencies?): KotlinScriptExternalDependencies? {
|
||||
|
||||
fun logClassloadingError(ex: Throwable) {
|
||||
logScriptDefMessage(ScriptDependenciesResolver.ReportSeverity.WARNING, ex.message ?: "Invalid script template: ${template.qualifiedName}", null)
|
||||
}
|
||||
|
||||
fun makeScriptContents() = BasicScriptContents(file, getAnnotations = {
|
||||
val classLoader = template.java.classLoader
|
||||
try {
|
||||
getAnnotationEntries(file, project)
|
||||
.mapNotNull { psiAnn ->
|
||||
// TODO: consider advanced matching using semantic similar to actual resolving
|
||||
acceptedAnnotations.find { ann ->
|
||||
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
|
||||
}?.let { constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
|
||||
}
|
||||
}
|
||||
catch (ex: Throwable) {
|
||||
logClassloadingError(ex)
|
||||
emptyList()
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
val fileDeps = resolver?.resolve(makeScriptContents(), environment, ::logScriptDefMessage, previousDependencies)
|
||||
// TODO: use it as a Future
|
||||
return fileDeps?.get()
|
||||
}
|
||||
catch (ex: Throwable) {
|
||||
logClassloadingError(ex)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun <TF: Any> getAnnotationEntries(file: TF, project: Project): Iterable<KtAnnotationEntry> = when (file) {
|
||||
is PsiFile -> getAnnotationEntriesFromPsiFile(file)
|
||||
is VirtualFile -> getAnnotationEntriesFromVirtualFile(file, project)
|
||||
is File -> {
|
||||
val virtualFile = (StandardFileSystems.local().findFileByPath(file.canonicalPath)
|
||||
?: throw IllegalArgumentException("Unable to find file ${file.canonicalPath}"))
|
||||
getAnnotationEntriesFromVirtualFile(virtualFile, project)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unsupported file type $file")
|
||||
}
|
||||
|
||||
private fun getAnnotationEntriesFromPsiFile(file: PsiFile) =
|
||||
(file as? KtFile)?.annotationEntries
|
||||
?: throw IllegalArgumentException("Unable to extract kotlin annotations from ${file.name} (${file.fileType})")
|
||||
|
||||
private fun getAnnotationEntriesFromVirtualFile(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
|
||||
val psiFile: PsiFile = PsiManager.getInstance(project).findFile(file)
|
||||
?: throw IllegalArgumentException("Unable to load PSI from ${file.canonicalPath}")
|
||||
return getAnnotationEntriesFromPsiFile(psiFile)
|
||||
}
|
||||
|
||||
class BasicScriptContents<out TF: Any>(myFile: TF, getAnnotations: () -> Iterable<Annotation>) : ScriptContents {
|
||||
override val file: File? by lazy { getFile(myFile) }
|
||||
override val annotations: Iterable<Annotation> by lazy { getAnnotations() }
|
||||
override val text: CharSequence? by lazy { getFileContents(myFile) }
|
||||
}
|
||||
|
||||
override fun toString(): String = "KotlinScriptDefinitionFromAnnotatedTemplate - ${template.simpleName}"
|
||||
|
||||
override val annotationsForSamWithReceivers: List<String>
|
||||
get() = samWithReceiverAnnotations ?: super.annotationsForSamWithReceivers
|
||||
|
||||
companion object {
|
||||
internal val log = Logger.getInstance(KotlinScriptDefinitionFromAnnotatedTemplate::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun logScriptDefMessage(reportSeverity: ScriptDependenciesResolver.ReportSeverity, s: String, position: ScriptContents.Position?): Unit {
|
||||
val msg = (position?.run { "[at $line:$col]" } ?: "") + s
|
||||
when (reportSeverity) {
|
||||
ScriptDependenciesResolver.ReportSeverity.ERROR -> KotlinScriptDefinitionFromAnnotatedTemplate.log.error(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.WARNING -> KotlinScriptDefinitionFromAnnotatedTemplate.log.warn(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.INFO -> KotlinScriptDefinitionFromAnnotatedTemplate.log.info(msg)
|
||||
ScriptDependenciesResolver.ReportSeverity.DEBUG -> KotlinScriptDefinitionFromAnnotatedTemplate.log.debug(msg)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.components.ServiceManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import java.io.File
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.read
|
||||
import kotlin.concurrent.write
|
||||
import kotlin.script.dependencies.KotlinScriptExternalDependencies
|
||||
|
||||
class KotlinScriptExternalImportsProviderImpl(
|
||||
val project: Project,
|
||||
private val scriptDefinitionProvider: KotlinScriptDefinitionProvider
|
||||
) : KotlinScriptExternalImportsProvider {
|
||||
|
||||
private val cacheLock = ReentrantReadWriteLock()
|
||||
private val cache = hashMapOf<String, KotlinScriptExternalDependencies?>()
|
||||
|
||||
override fun <TF: Any> getExternalImports(file: TF): KotlinScriptExternalDependencies? = cacheLock.read {
|
||||
calculateExternalDependencies(file)
|
||||
}
|
||||
|
||||
fun <TF: Any> getExternalImports(files: Iterable<TF>): List<KotlinScriptExternalDependencies> = cacheLock.read {
|
||||
files.mapNotNull { calculateExternalDependencies(it) }
|
||||
}
|
||||
|
||||
private fun <TF: Any> calculateExternalDependencies(file: TF): KotlinScriptExternalDependencies? {
|
||||
val path = getFilePath(file)
|
||||
val cached = cache[path]
|
||||
return if (cached != null) cached else {
|
||||
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
|
||||
if (scriptDef != null) {
|
||||
val deps = scriptDef.getDependenciesFor(file, project, null)
|
||||
if (deps != null) {
|
||||
log.info("[kts] new cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
|
||||
}
|
||||
cacheLock.write {
|
||||
cache.put(path, deps)
|
||||
}
|
||||
deps
|
||||
}
|
||||
else null
|
||||
}
|
||||
}
|
||||
|
||||
// optimized for initial caching, additional handling of possible duplicates to save a call to distinct
|
||||
// returns list of cached files
|
||||
override fun <TF: Any> cacheExternalImports(files: Iterable<TF>): Iterable<TF> = cacheLock.write {
|
||||
val uncached = hashSetOf<String>()
|
||||
files.mapNotNull { file ->
|
||||
val path = getFilePath(file)
|
||||
if (isValidFile(file) && !cache.containsKey(path) && !uncached.contains(path)) {
|
||||
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
|
||||
if (scriptDef != null) {
|
||||
val deps = scriptDef.getDependenciesFor(file, project, null)
|
||||
if (deps != null) {
|
||||
log.info("[kts] cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
|
||||
}
|
||||
cache.put(path, deps)
|
||||
file
|
||||
}
|
||||
else {
|
||||
uncached.add(path)
|
||||
null
|
||||
}
|
||||
}
|
||||
else null
|
||||
}
|
||||
}
|
||||
|
||||
// optimized for update, no special duplicates handling
|
||||
// returns files with valid script definition (or deleted from cache - which in fact should have script def too)
|
||||
// TODO: this is the badly designed contract, since it mixes the entities, but these files are needed on the calling site now. Find out other solution
|
||||
override fun <TF: Any> updateExternalImportsCache(files: Iterable<TF>): Iterable<TF> = cacheLock.write {
|
||||
files.mapNotNull { file ->
|
||||
val path = getFilePath(file)
|
||||
if (!isValidFile(file)) {
|
||||
if (cache.remove(path) != null) {
|
||||
log.debug("[kts] removed deps for file $path")
|
||||
file
|
||||
} // cleared
|
||||
else {
|
||||
null // unknown
|
||||
}
|
||||
}
|
||||
else {
|
||||
val scriptDef = scriptDefinitionProvider.findScriptDefinition(file)
|
||||
if (scriptDef != null) {
|
||||
val oldDeps = cache[path]
|
||||
val deps = scriptDef.getDependenciesFor(file, project, oldDeps)
|
||||
when {
|
||||
deps != null && (oldDeps == null ||
|
||||
!deps.classpath.isSamePathListAs(oldDeps.classpath) || !deps.sources.isSamePathListAs(oldDeps.sources)) -> {
|
||||
// changed or new
|
||||
log.info("[kts] updated/new cached deps for $path: ${deps.classpath.joinToString(File.pathSeparator)}")
|
||||
cache.put(path, deps)
|
||||
}
|
||||
deps != null -> {
|
||||
// same as before
|
||||
}
|
||||
else -> {
|
||||
if (cache.remove(path) != null) {
|
||||
log.debug("[kts] removed deps for $path")
|
||||
} // cleared
|
||||
}
|
||||
}
|
||||
file
|
||||
}
|
||||
else null // not a script
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidateCaches() {
|
||||
cacheLock.write(cache::clear)
|
||||
}
|
||||
|
||||
override fun getKnownCombinedClasspath(): List<File> = cacheLock.read {
|
||||
cache.values.flatMap { it?.classpath ?: emptyList() }
|
||||
}.distinct()
|
||||
|
||||
override fun getKnownSourceRoots(): List<File> = cacheLock.read {
|
||||
cache.values.flatMap { it?.sources ?: emptyList() }
|
||||
}.distinct()
|
||||
|
||||
override fun <TF: Any> getCombinedClasspathFor(files: Iterable<TF>): List<File> =
|
||||
getExternalImports(files)
|
||||
.flatMap { it.classpath }
|
||||
.distinct()
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): KotlinScriptExternalImportsProvider? =
|
||||
ServiceManager.getService(project, KotlinScriptExternalImportsProvider::class.java)
|
||||
internal val log = Logger.getInstance(KotlinScriptExternalImportsProvider::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Iterable<File>.isSamePathListAs(other: Iterable<File>): Boolean =
|
||||
with (Pair(iterator(), other.iterator())) {
|
||||
while (first.hasNext() && second.hasNext()) {
|
||||
if (first.next().canonicalPath != second.next().canonicalPath) return false
|
||||
}
|
||||
!(first.hasNext() || second.hasNext())
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class ScriptHelperImpl : ScriptHelper {
|
||||
override fun getScriptParameters(kotlinScriptDefinition: KotlinScriptDefinition, scriptDefinition: ScriptDescriptor) =
|
||||
kotlinScriptDefinition.getScriptParameters(scriptDefinition)
|
||||
|
||||
override fun getKotlinType(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>) =
|
||||
getKotlinTypeByKClass(scriptDescriptor, kClass)
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
||||
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
|
||||
import org.jetbrains.kotlin.utils.tryCreateCallableMappingFromNamedArgs
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
|
||||
internal val KtAnnotationEntry.typeName: String get() = (typeReference?.typeElement as? KtUserType)?.referencedName.orAnonymous()
|
||||
|
||||
internal fun String?.orAnonymous(kind: String = ""): String =
|
||||
this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
|
||||
|
||||
internal fun constructAnnotation(psi: KtAnnotationEntry, targetClass: KClass<out Annotation>): Annotation {
|
||||
|
||||
val valueArguments = psi.valueArguments.map { arg ->
|
||||
val evaluator = ConstantExpressionEvaluator(DefaultBuiltIns.Instance, LanguageVersionSettingsImpl.DEFAULT)
|
||||
val trace = BindingTraceContext()
|
||||
val result = evaluator.evaluateToConstantValue(arg.getArgumentExpression()!!, trace, TypeUtils.NO_EXPECTED_TYPE)
|
||||
// TODO: consider inspecting `trace` to find diagnostics reported during the computation (such as division by zero, integer overflow, invalid annotation parameters etc.)
|
||||
val argName = arg.getArgumentName()?.asName?.toString()
|
||||
argName to result?.value
|
||||
}
|
||||
val mappedArguments: Map<KParameter, Any?> =
|
||||
tryCreateCallableMappingFromNamedArgs(targetClass.constructors.first(), valueArguments)
|
||||
?: return InvalidScriptResolverAnnotation(psi.typeName, valueArguments)
|
||||
|
||||
try {
|
||||
return targetClass.primaryConstructor!!.callBy(mappedArguments)
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
return InvalidScriptResolverAnnotation(psi.typeName, valueArguments, ex)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: this class is used for error reporting. But in order to pass plugin verification, it should derive directly from java's Annotation
|
||||
// and implement annotationType method (see #KT-16621 for details).
|
||||
// TODO: instead of the workaround described above, consider using a sum-type for returning errors from constructAnnotation
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
class InvalidScriptResolverAnnotation(val name: String, val annParams: List<Pair<String?, Any?>>?, val error: Exception? = null) : Annotation, java.lang.annotation.Annotation {
|
||||
override fun annotationType(): Class<out Annotation> = InvalidScriptResolverAnnotation::class.java
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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 java.io.File
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
const val DEFAULT_SCRIPT_FILE_PATTERN = ".*\\.kts"
|
||||
|
||||
// TODO: remove this file and all the usages after releasing GSK 1.0
|
||||
|
||||
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.templatesScriptTemplateDefinition instead",
|
||||
replaceWith = ReplaceWith("kotlin.script.templates.ScriptTemplateDefinition"))
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ScriptTemplateDefinition(val resolver: KClass<out ScriptDependenciesResolver>,
|
||||
val scriptFilePattern: String = DEFAULT_SCRIPT_FILE_PATTERN)
|
||||
|
||||
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.extensions.SamWithReceiverAnnotations instead",
|
||||
replaceWith = ReplaceWith("kotlin.script.extensions.SamWithReceiverAnnotations"))
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SamWithReceiverAnnotations(vararg val annotations: String)
|
||||
|
||||
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.ScriptContents instead",
|
||||
replaceWith = ReplaceWith("kotlin.script.dependencies.ScriptContents"))
|
||||
interface ScriptContents {
|
||||
|
||||
data class Position(val line: Int, val col: Int)
|
||||
|
||||
val file: File?
|
||||
val annotations: Iterable<Annotation>
|
||||
val text: CharSequence?
|
||||
}
|
||||
|
||||
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.PseudoFuture instead",
|
||||
replaceWith = ReplaceWith("kotlin.script.dependencies.PseudoFuture"))
|
||||
class PseudoFuture<T>(private val value: T): Future<T> {
|
||||
override fun get(): T = value
|
||||
override fun get(p0: Long, p1: TimeUnit): T = value
|
||||
override fun cancel(p0: Boolean): Boolean = false
|
||||
override fun isDone(): Boolean = true
|
||||
override fun isCancelled(): Boolean = false
|
||||
}
|
||||
|
||||
fun KotlinScriptExternalDependencies?.asFuture(): PseudoFuture<KotlinScriptExternalDependencies?> = PseudoFuture(this)
|
||||
|
||||
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.ScriptDependenciesResolver instead",
|
||||
replaceWith = ReplaceWith("kotlin.script.dependencies.ScriptDependenciesResolver"))
|
||||
interface ScriptDependenciesResolver {
|
||||
|
||||
enum class ReportSeverity { ERROR, WARNING, INFO, DEBUG }
|
||||
|
||||
fun resolve(script: ScriptContents,
|
||||
environment: Map<String, Any?>?,
|
||||
report: (ReportSeverity, String, ScriptContents.Position?) -> Unit,
|
||||
previousDependencies: KotlinScriptExternalDependencies?
|
||||
): Future<KotlinScriptExternalDependencies?> = PseudoFuture(null)
|
||||
}
|
||||
|
||||
@Deprecated("Used only for compatibility with legacy code, use kotlin.script.dependencies.KotlinScriptExternalDependencies instead",
|
||||
replaceWith = ReplaceWith("kotlin.script.dependencies.KotlinScriptExternalDependencies"))
|
||||
interface KotlinScriptExternalDependencies : Comparable<KotlinScriptExternalDependencies> {
|
||||
val javaHome: String? get() = null
|
||||
val classpath: Iterable<File> get() = emptyList()
|
||||
val imports: Iterable<String> get() = emptyList()
|
||||
val sources: Iterable<File> get() = emptyList()
|
||||
val scripts: Iterable<File> get() = emptyList()
|
||||
|
||||
override fun compareTo(other: KotlinScriptExternalDependencies): Int =
|
||||
compareValues(javaHome, other.javaHome)
|
||||
.chainCompare { compareIterables(classpath, other.classpath) }
|
||||
.chainCompare { compareIterables(imports, other.imports) }
|
||||
.chainCompare { compareIterables(sources, other.sources) }
|
||||
.chainCompare { compareIterables(scripts, other.scripts) }
|
||||
}
|
||||
|
||||
private fun<T: Comparable<T>> compareIterables(a: Iterable<T>, b: Iterable<T>): Int {
|
||||
val ia = a.iterator()
|
||||
val ib = b.iterator()
|
||||
while (true) {
|
||||
if (ia.hasNext() && !ib.hasNext()) return 1
|
||||
if (!ia.hasNext() && !ib.hasNext()) return 0
|
||||
if (!ia.hasNext()) return -1
|
||||
val compRes = compareValues(ia.next(), ib.next())
|
||||
if (compRes != 0) return compRes
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun Int.chainCompare(compFn: () -> Int ): Int = if (this != 0) this else compFn()
|
||||
|
||||
class LegacyScriptDependenciesResolverWrapper(val legacyResolver: ScriptDependenciesResolver) : kotlin.script.dependencies.ScriptDependenciesResolver {
|
||||
|
||||
override fun resolve(script: kotlin.script.dependencies.ScriptContents,
|
||||
environment: Map<String, Any?>?,
|
||||
report: (kotlin.script.dependencies.ScriptDependenciesResolver.ReportSeverity, String, kotlin.script.dependencies.ScriptContents.Position?) -> Unit,
|
||||
previousDependencies: kotlin.script.dependencies.KotlinScriptExternalDependencies?
|
||||
): Future<kotlin.script.dependencies.KotlinScriptExternalDependencies?> {
|
||||
val legacyDeps = legacyResolver.resolve(
|
||||
object : ScriptContents {
|
||||
override val file: File? get() = script.file
|
||||
override val annotations: Iterable<Annotation> get() = script.annotations
|
||||
override val text: CharSequence? get() = script.text
|
||||
},
|
||||
environment,
|
||||
{ sev, msg, pos -> report(kotlin.script.dependencies.ScriptDependenciesResolver.ReportSeverity.values()[sev.ordinal],
|
||||
msg,
|
||||
pos?.let { kotlin.script.dependencies.ScriptContents.Position(it.line, it.col) }) },
|
||||
previousDependencies?.let {
|
||||
object : KotlinScriptExternalDependencies {
|
||||
override val javaHome get() = it.javaHome
|
||||
override val classpath get() = it.classpath
|
||||
override val imports get() = it.imports
|
||||
override val sources get() = it.sources
|
||||
override val scripts get() = it.scripts
|
||||
}
|
||||
}
|
||||
).get()
|
||||
return kotlin.script.dependencies.PseudoFuture(legacyDeps?.let {
|
||||
object : kotlin.script.dependencies.KotlinScriptExternalDependencies {
|
||||
override val javaHome get() = it.javaHome
|
||||
override val classpath get() = it.classpath
|
||||
override val imports get() = it.imports
|
||||
override val sources get() = it.sources
|
||||
override val scripts get() = it.scripts
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.dependencies.ScriptDependenciesResolver
|
||||
|
||||
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: ScriptDependenciesResolver? get() = null
|
||||
|
||||
val filePattern: String? get() = null
|
||||
|
||||
val dependenciesClasspath: Iterable<String>
|
||||
|
||||
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, Exception) -> Unit) = { _, ex -> throw ex }
|
||||
): List<KotlinScriptDefinitionFromAnnotatedTemplate> =
|
||||
makeScriptDefsFromTemplatesProviders(Extensions.getArea(project).getExtensionPoint(ScriptTemplatesProvider.EP_NAME).extensions.asIterable(),
|
||||
errorsHandler)
|
||||
|
||||
fun makeScriptDefsFromTemplatesProviders(providers: Iterable<ScriptTemplatesProvider>,
|
||||
errorsHandler: ((ScriptTemplatesProvider, Exception) -> Unit) = { _, ex -> throw ex }
|
||||
): List<KotlinScriptDefinitionFromAnnotatedTemplate> {
|
||||
return providers.filter { it.isValid }.flatMap { provider ->
|
||||
try {
|
||||
Logger.getInstance("makeScriptDefsFromTemplatesProviders")
|
||||
.info("[kts] loading script definitions ${provider.templateClassNames} using cp: ${provider.dependenciesClasspath.joinToString(File.pathSeparator)}")
|
||||
val loader = URLClassLoader(provider.dependenciesClasspath.map { File(it).toURI().toURL() }.toTypedArray(), ScriptTemplatesProvider::class.java.classLoader)
|
||||
provider.templateClassNames.map {
|
||||
val cl = loader.loadClass(it)
|
||||
KotlinScriptDefinitionFromAnnotatedTemplate(cl.kotlin, provider.resolver, provider.filePattern, provider.environment)
|
||||
}
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
errorsHandler(provider, ex)
|
||||
emptyList<KotlinScriptDefinitionFromAnnotatedTemplate>()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.serialization.deserialization.NotFoundClasses
|
||||
import org.jetbrains.kotlin.serialization.deserialization.findNonGenericClassAcrossDependencies
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.KTypeProjection
|
||||
import kotlin.reflect.KVariance
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
|
||||
fun KotlinScriptDefinition.getScriptParameters(scriptDescriptor: ScriptDescriptor): List<ScriptParameter> =
|
||||
template.primaryConstructor?.parameters
|
||||
?.map { ScriptParameter(Name.identifier(it.name!!), getKotlinTypeByKType(scriptDescriptor, it.type)) }
|
||||
?: emptyList()
|
||||
|
||||
fun getKotlinTypeByKClass(scriptDescriptor: ScriptDescriptor, kClass: KClass<out Any>): KotlinType =
|
||||
getKotlinTypeByFqName(scriptDescriptor,
|
||||
kClass.qualifiedName ?: throw RuntimeException("Cannot get FQN from $kClass"))
|
||||
|
||||
private fun getKotlinTypeByFqName(scriptDescriptor: ScriptDescriptor, fqName: String): KotlinType =
|
||||
scriptDescriptor.module.findNonGenericClassAcrossDependencies(
|
||||
ClassId.topLevel(FqName(fqName)),
|
||||
NotFoundClasses(LockBasedStorageManager.NO_LOCKS, scriptDescriptor.module)
|
||||
).defaultType
|
||||
|
||||
// TODO: support star projections
|
||||
// TODO: support annotations on types and type parameters
|
||||
// TODO: support type parameters on types and type projections
|
||||
private fun getKotlinTypeByKType(scriptDescriptor: ScriptDescriptor, kType: KType): KotlinType {
|
||||
val classifier = kType.classifier
|
||||
if (classifier !is KClass<*>)
|
||||
throw java.lang.UnsupportedOperationException("Only classes are supported as parameters in script template: $classifier")
|
||||
|
||||
val type = getKotlinTypeByKClass(scriptDescriptor, classifier)
|
||||
val typeProjections = kType.arguments.map { getTypeProjection(scriptDescriptor, it) }
|
||||
val isNullable = kType.isMarkedNullable
|
||||
|
||||
return KotlinTypeFactory.simpleType(Annotations.EMPTY, type.constructor, typeProjections, isNullable)
|
||||
}
|
||||
|
||||
private fun getTypeProjection(scriptDescriptor: ScriptDescriptor, kTypeProjection: KTypeProjection): TypeProjection {
|
||||
val kType = kTypeProjection.type ?: throw java.lang.UnsupportedOperationException("Star projections are not supported")
|
||||
|
||||
val type = getKotlinTypeByKType(scriptDescriptor, kType)
|
||||
|
||||
val variance = when (kTypeProjection.variance) {
|
||||
KVariance.IN -> Variance.IN_VARIANCE
|
||||
KVariance.OUT -> Variance.OUT_VARIANCE
|
||||
KVariance.INVARIANT -> Variance.INVARIANT
|
||||
null -> throw java.lang.UnsupportedOperationException("Star projections are not supported")
|
||||
}
|
||||
|
||||
return TypeProjectionImpl(variance, type)
|
||||
}
|
||||
Reference in New Issue
Block a user