Implement support for additional compiler arguments for scripts in IDE

fixes #KT-19120
This commit is contained in:
Ilya Chernikov
2018-03-16 17:26:57 +01:00
parent 643e81f0ae
commit d6792f5f52
5 changed files with 89 additions and 5 deletions
@@ -28,21 +28,22 @@ 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.ScriptDependenciesResolver
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.templates.AcceptedAnnotations
import kotlin.script.templates.*
open class KotlinScriptDefinitionFromAnnotatedTemplate(
template: KClass<out Any>,
val environment: Map<String, Any?>? = null,
val templateClasspath: List<File> = emptyList()
) : KotlinScriptDefinition(template) {
val scriptFilePattern by lazy {
val pattern =
takeUnlessError {
template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>()?.scriptFilePattern
val ann = template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>()
ann?.scriptFilePattern
}
?: takeUnlessError { template.annotations.firstIsInstanceOrNull<ScriptTemplateDefinition>()?.scriptFilePattern }
?: DEFAULT_SCRIPT_FILE_PATTERN
@@ -144,6 +145,15 @@ open class KotlinScriptDefinitionFromAnnotatedTemplate(
override val annotationsForSamWithReceivers: List<String>
get() = samWithReceiverAnnotations ?: super.annotationsForSamWithReceivers
override val additionalCompilerArguments: Iterable<String>? by lazy {
takeUnlessError {
template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateAdditionalCompilerArguments>()?.let {
val res = it.provider.primaryConstructor?.call(it.arguments.asIterable())
res
}
}?.getAdditionalCompilerArguments(environment)
}
private inline fun<T> takeUnlessError(reportError: Boolean = true, body: () -> T?): T? =
try {
body()
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.script
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
@@ -38,13 +39,15 @@ class ScriptContentLoader(private val project: Project) {
private fun loadAnnotations(scriptDefinition: KotlinScriptDefinition, file: VirtualFile): List<Annotation> {
val classLoader = scriptDefinition.template.java.classLoader
// TODO_R: report error on failure to load annotation class
return getAnnotationEntries(file, project)
return ApplicationManager.getApplication().runReadAction<List<Annotation>> {
getAnnotationEntries(file, project)
.mapNotNull { psiAnn ->
// TODO: consider advanced matching using semantic similar to actual resolving
scriptDefinition.acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let { constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}
}
}
private fun getAnnotationEntries(file: VirtualFile, project: Project): Iterable<KtAnnotationEntry> {
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.script
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.util.UserDataHolderBase
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.NameUtils
@@ -26,7 +27,7 @@ import kotlin.reflect.KClass
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.templates.standard.ScriptTemplateWithArgs
open class KotlinScriptDefinition(val template: KClass<out Any>) {
open class KotlinScriptDefinition(val template: KClass<out Any>) : UserDataHolderBase() {
open val name: String = "Kotlin Script"
@@ -45,6 +46,9 @@ open class KotlinScriptDefinition(val template: KClass<out Any>) {
open val dependencyResolver: DependenciesResolver get() = DependenciesResolver.NoDependencies
open val acceptedAnnotations: List<KClass<out Annotation>> get() = emptyList()
@Deprecated("temporary workaround for missing functionality, will be replaced by the new API soon")
open val additionalCompilerArguments: Iterable<String>? = null
}
object StandardScriptDefinition : KotlinScriptDefinition(ScriptTemplateWithArgs::class)
@@ -0,0 +1,28 @@
/*
* 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 2000-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 kotlin.script.templates
import kotlin.reflect.KClass
import kotlin.script.dependencies.Environment
@Deprecated("temporary workaround for missing functionality, will be replaced by the new API soon")
// Note: all subclasses should provide the same constructor
open class ScriptTemplateAdditionalCompilerArgumentsProvider(val arguments: Iterable<String> = emptyList()) {
open fun getAdditionalCompilerArguments(@Suppress("UNUSED_PARAMETER") environment: Environment?): Iterable<String> = arguments
}
// Should be deprecated as well, but since we don't have replacement as of yet, leaving it as is
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ScriptTemplateAdditionalCompilerArguments(
val arguments: Array<String> = [],
val provider: KClass<out ScriptTemplateAdditionalCompilerArgumentsProvider> = ScriptTemplateAdditionalCompilerArgumentsProvider::class
)
@@ -18,10 +18,16 @@ package org.jetbrains.kotlin.idea.compiler
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.openapi.util.Key
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.kotlin.analyzer.LanguageSettingsProvider
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.cli.common.arguments.Jsr305Parser
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.AnalysisFlag
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
@@ -29,15 +35,18 @@ import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.TargetPlatformVersion
import org.jetbrains.kotlin.idea.caches.project.LibraryInfo
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
import org.jetbrains.kotlin.idea.caches.project.ScriptModuleInfo
import org.jetbrains.kotlin.idea.project.getLanguageVersionSettings
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.project.targetPlatform
import org.jetbrains.kotlin.script.KotlinScriptDefinition
object IDELanguageSettingsProvider : LanguageSettingsProvider {
override fun getLanguageVersionSettings(moduleInfo: ModuleInfo, project: Project): LanguageVersionSettings =
when (moduleInfo) {
is ModuleSourceInfo -> moduleInfo.module.languageVersionSettings
is LibraryInfo -> project.getLanguageVersionSettings(extraAnalysisFlags = getExtraAnalysisFlags(project))
is ScriptModuleInfo -> getVersionLanguageSettingsForScripts(project, moduleInfo.scriptDefinition)
else -> project.getLanguageVersionSettings()
}
@@ -60,3 +69,33 @@ object IDELanguageSettingsProvider : LanguageSettingsProvider {
return (moduleInfo as? ModuleSourceInfo)?.module?.targetPlatform?.version ?: TargetPlatformVersion.NoVersion
}
}
private val LANGUAGE_VERSION_SETTINGS = Key.create<CachedValue<LanguageVersionSettings>>("LANGUAGE_VERSION_SETTINGS")
private fun getVersionLanguageSettingsForScripts(project: Project, scriptDefinition: KotlinScriptDefinition): LanguageVersionSettings {
val args = scriptDefinition.additionalCompilerArguments
return if (args == null || args.none()) {
project.getLanguageVersionSettings()
} else {
val settings = scriptDefinition.getUserData(LANGUAGE_VERSION_SETTINGS) ?: createCachedValue(project) {
val compilerArguments = K2JVMCompilerArguments()
parseCommandLineArguments(args.toList(), compilerArguments)
// TODO: reporting
compilerArguments.configureLanguageVersionSettings(MessageCollector.NONE)
}.also { scriptDefinition.putUserData(LANGUAGE_VERSION_SETTINGS, it) }
settings.value
}
}
private fun createCachedValue(project: Project, body: () -> LanguageVersionSettings): CachedValue<LanguageVersionSettings> {
return CachedValuesManager
.getManager(project)
.createCachedValue(
{
CachedValueProvider.Result(
body(),
ProjectRootModificationTracker.getInstance(project)
)
}, false
)
}