Pick up script language level from used stdlib in a gradle's classpath

^KT-41283 Fixed
This commit is contained in:
Vladimir Dolzhenko
2020-08-26 14:09:58 +00:00
parent 949c67078d
commit 54d3c5fb0a
7 changed files with 87 additions and 35 deletions
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.idea.compiler package org.jetbrains.kotlin.idea.compiler
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.roots.ProjectFileIndex
@@ -32,20 +31,14 @@ import org.jetbrains.kotlin.cli.common.arguments.Jsr305Parser
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.idea.caches.project.* import org.jetbrains.kotlin.idea.caches.project.*
import org.jetbrains.kotlin.idea.core.script.scriptRelatedModuleName import org.jetbrains.kotlin.idea.core.script.scriptRelatedModuleName
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector import org.jetbrains.kotlin.idea.project.*
import org.jetbrains.kotlin.idea.project.getLanguageVersionSettings
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.platform.TargetPlatform import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.TargetPlatformVersion import org.jetbrains.kotlin.platform.TargetPlatformVersion
import org.jetbrains.kotlin.platform.jvm.JdkPlatform import org.jetbrains.kotlin.platform.jvm.JdkPlatform
import org.jetbrains.kotlin.platform.subplatformsOfType import org.jetbrains.kotlin.platform.subplatformsOfType
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.utils.Jsr305State import org.jetbrains.kotlin.utils.Jsr305State
@@ -122,34 +115,58 @@ private fun detectDefaultTargetPlatformVersion(platform: TargetPlatform?): Targe
private fun getLanguageSettingsForScripts(project: Project, file: VirtualFile, scriptDefinition: ScriptDefinition): ScriptLanguageSettings { private fun getLanguageSettingsForScripts(project: Project, file: VirtualFile, scriptDefinition: ScriptDefinition): ScriptLanguageSettings {
val scriptModule = file.let { val scriptModule = file.let {
it.scriptRelatedModuleName?.let { ModuleManager.getInstance(project).findModuleByName(it) } it.scriptRelatedModuleName?.let { module -> ModuleManager.getInstance(project).findModuleByName(module) }
?: ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(it) ?: ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(it)
} }
val environmentCompilerOptions = scriptDefinition.defaultCompilerOptions
val args = scriptDefinition.compilerOptions val args = scriptDefinition.compilerOptions
return if (args == null || args.none()) { return if (environmentCompilerOptions.none() && args.none()) {
ScriptLanguageSettings(project.getLanguageVersionSettings(), detectDefaultTargetPlatformVersion(scriptModule?.platform)) ScriptLanguageSettings(
project.getLanguageVersionSettings(contextModule = scriptModule),
detectDefaultTargetPlatformVersion(scriptModule?.platform)
)
} else { } else {
val settings = scriptDefinition.getUserData(SCRIPT_LANGUAGE_SETTINGS) ?: createCachedValue(project) { val settings = scriptDefinition.getUserData(SCRIPT_LANGUAGE_SETTINGS) ?: createCachedValue(project) {
val compilerArguments = K2JVMCompilerArguments() val compilerArguments = K2JVMCompilerArguments()
parseCommandLineArguments(environmentCompilerOptions.toList(), compilerArguments)
parseCommandLineArguments(args.toList(), compilerArguments) parseCommandLineArguments(args.toList(), compilerArguments)
// TODO: reporting // TODO: reporting
val verSettings = compilerArguments.toLanguageVersionSettings(MessageCollector.NONE) val verSettings = compilerArguments.toLanguageVersionSettings(MessageCollector.NONE)
val jvmTarget = compilerArguments.jvmTarget?.let { JvmTarget.fromString(it) } ?: detectDefaultTargetPlatformVersion(scriptModule?.platform) val jvmTarget =
ScriptLanguageSettings(verSettings, jvmTarget) compilerArguments.jvmTarget?.let { JvmTarget.fromString(it) } ?: detectDefaultTargetPlatformVersion(scriptModule?.platform)
val languageVersionSettings = project.getLanguageVersionSettings(contextModule = scriptModule)
val versionSettings = if (languageVersionSettings.languageVersion.isLess(verSettings.languageVersion)) {
languageVersionSettings
} else {
verSettings
}
ScriptLanguageSettings(versionSettings, jvmTarget)
}.also { scriptDefinition.putUserData(SCRIPT_LANGUAGE_SETTINGS, it) } }.also { scriptDefinition.putUserData(SCRIPT_LANGUAGE_SETTINGS, it) }
settings.value settings.value
} }
} }
private fun createCachedValue(project: Project, body: () -> ScriptLanguageSettings): CachedValue<ScriptLanguageSettings> { private fun LanguageVersion.isLess(languageVersion: LanguageVersion): Boolean =
if (major < languageVersion.major) {
true
} else {
minor < languageVersion.minor
}
private inline fun createCachedValue(
project: Project,
crossinline body: () -> ScriptLanguageSettings
): CachedValue<ScriptLanguageSettings> {
return CachedValuesManager return CachedValuesManager
.getManager(project) .getManager(project)
.createCachedValue( .createCachedValue(
{ {
CachedValueProvider.Result( CachedValueProvider.Result(
body(), body(),
ProjectRootModificationTracker.getInstance(project) ProjectRootModificationTracker.getInstance(project), ModuleManager.getInstance(project)
) )
}, false }, false
) )
@@ -119,11 +119,15 @@ fun Project.getLanguageVersionSettings(
jsr305State: Jsr305State? = null, jsr305State: Jsr305State? = null,
isReleaseCoroutines: Boolean? = null isReleaseCoroutines: Boolean? = null
): LanguageVersionSettings { ): LanguageVersionSettings {
val arguments = KotlinCommonCompilerArgumentsHolder.getInstance(this).settings val kotlinFacetSettings = contextModule?.let {
KotlinFacetSettingsProvider.getInstance(this)?.getInitializedSettings(it)
}
val arguments = kotlinFacetSettings?.compilerArguments ?: KotlinCommonCompilerArgumentsHolder.getInstance(this).settings
val languageVersion = val languageVersion =
LanguageVersion.fromVersionString(arguments.languageVersion) kotlinFacetSettings?.languageLevel ?: LanguageVersion.fromVersionString(arguments.languageVersion)
?: contextModule?.getAndCacheLanguageLevelByDependencies() ?: contextModule?.getAndCacheLanguageLevelByDependencies()
?: VersionView.RELEASED_VERSION ?: VersionView.RELEASED_VERSION
val apiVersion = ApiVersion.createByLanguageVersion(LanguageVersion.fromVersionString(arguments.apiVersion) ?: languageVersion) val apiVersion = ApiVersion.createByLanguageVersion(LanguageVersion.fromVersionString(arguments.apiVersion) ?: languageVersion)
val compilerSettings = KotlinCompilerSettings.getInstance(this).settings val compilerSettings = KotlinCompilerSettings.getInstance(this).settings
@@ -264,7 +264,8 @@ fun loadDefinitionsFromTemplates(
* Script template dependencies naturally become (part of) dependencies of the script which is not always desired for resolver dependencies. * Script template dependencies naturally become (part of) dependencies of the script which is not always desired for resolver dependencies.
* i.e. gradle resolver may depend on some jars that 'built.gradle.kts' files should not depend on. * i.e. gradle resolver may depend on some jars that 'built.gradle.kts' files should not depend on.
*/ */
additionalResolverClasspath: List<File> = emptyList() additionalResolverClasspath: List<File> = emptyList(),
defaultCompilerOptions: Iterable<String> = emptyList()
): List<ScriptDefinition> { ): List<ScriptDefinition> {
val classpath = templateClasspath + additionalResolverClasspath val classpath = templateClasspath + additionalResolverClasspath
scriptingInfoLog("Loading script definitions $templateClassNames using cp: ${classpath.joinToString(File.pathSeparator)}") scriptingInfoLog("Loading script definitions $templateClassNames using cp: ${classpath.joinToString(File.pathSeparator)}")
@@ -281,10 +282,10 @@ fun loadDefinitionsFromTemplates(
} }
when { when {
template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>() != null -> { template.annotations.firstIsInstanceOrNull<kotlin.script.templates.ScriptTemplateDefinition>() != null -> {
ScriptDefinition.FromLegacyTemplate(hostConfiguration, template, templateClasspath) ScriptDefinition.FromLegacyTemplate(hostConfiguration, template, templateClasspath, defaultCompilerOptions)
} }
template.annotations.firstIsInstanceOrNull<kotlin.script.experimental.annotations.KotlinScript>() != null -> { template.annotations.firstIsInstanceOrNull<kotlin.script.experimental.annotations.KotlinScript>() != null -> {
ScriptDefinition.FromTemplate(hostConfiguration, template, ScriptDefinition::class) ScriptDefinition.FromTemplate(hostConfiguration, template, ScriptDefinition::class, defaultCompilerOptions)
} }
else -> { else -> {
scriptingWarnLog("Cannot find a valid script definition annotation on the class $template") scriptingWarnLog("Cannot find a valid script definition annotation on the class $template")
@@ -16,7 +16,8 @@ class GradleKotlinScriptDefinitionWrapper(
hostConfiguration: ScriptingHostConfiguration, hostConfiguration: ScriptingHostConfiguration,
legacyDefinition: KotlinScriptDefinitionFromAnnotatedTemplate, legacyDefinition: KotlinScriptDefinitionFromAnnotatedTemplate,
gradleVersion: String, gradleVersion: String,
) : ScriptDefinition.FromLegacy(hostConfiguration, legacyDefinition) { defaultCompilerOptions: Iterable<String>
) : ScriptDefinition.FromLegacy(hostConfiguration, legacyDefinition, defaultCompilerOptions) {
override val compilationConfiguration by lazy { override val compilationConfiguration by lazy {
ScriptCompilationConfigurationFromDefinition( ScriptCompilationConfigurationFromDefinition(
hostConfiguration, hostConfiguration,
@@ -15,7 +15,8 @@ class GradleKotlinScriptDefinitionWrapper(
hostConfiguration: ScriptingHostConfiguration, hostConfiguration: ScriptingHostConfiguration,
legacyDefinition: KotlinScriptDefinitionFromAnnotatedTemplate, legacyDefinition: KotlinScriptDefinitionFromAnnotatedTemplate,
gradleVersion: String, gradleVersion: String,
) : ScriptDefinition.FromLegacy(hostConfiguration, legacyDefinition) { defaultCompilerOptions: Iterable<String>
) : ScriptDefinition.FromLegacy(hostConfiguration, legacyDefinition, defaultCompilerOptions) {
override val compilationConfiguration by lazy { override val compilationConfiguration by lazy {
ScriptCompilationConfigurationFromDefinition( ScriptCompilationConfigurationFromDefinition(
hostConfiguration, hostConfiguration,
@@ -11,6 +11,7 @@ import com.intellij.openapi.project.Project
import com.intellij.util.EnvironmentUtil import com.intellij.util.EnvironmentUtil
import com.intellij.util.PathUtil import com.intellij.util.PathUtil
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle import org.jetbrains.kotlin.idea.KotlinIdeaGradleBundle
import org.jetbrains.kotlin.idea.core.script.* import org.jetbrains.kotlin.idea.core.script.*
import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslSyncListener import org.jetbrains.kotlin.idea.scripting.gradle.importing.KotlinDslSyncListener
@@ -125,6 +126,19 @@ class GradleScriptDefinitionsContributor(private val project: Project) : ScriptD
file?.name?.startsWith("kotlin-compiler-embeddable") == true || file?.name?.startsWith("kotlin-stdlib") == true file?.name?.startsWith("kotlin-compiler-embeddable") == true || file?.name?.startsWith("kotlin-stdlib") == true
}?.firstOrNull()?.let(::listOf).orEmpty() }?.firstOrNull()?.let(::listOf).orEmpty()
} }
private val kotlinStdLibSelector = Regex("^(kotlin-compiler-embeddable|kotlin-stdlib)-(\\d+\\.\\d+).*\\.jar\$")
fun findStdLibLanguageVersion(classpath: List<File>): LanguageVersion? {
return classpath.map { it.parentFile }.toSet().map {
it.listFiles { file ->
kotlinStdLibSelector.find(file.name) != null
}.firstOrNull()?.let { file ->
val matchResult = kotlinStdLibSelector.find(file.name) ?: return@let null
LanguageVersion.fromVersionString(matchResult.groupValues[2])
}
}.firstOrNull()
}
} }
override val id: String = "Gradle Kotlin DSL" override val id: String = "Gradle Kotlin DSL"
@@ -236,12 +250,16 @@ class GradleScriptDefinitionsContributor(private val project: Project) : ScriptD
projectPath, projectPath,
GradleConstants.SYSTEM_ID GradleConstants.SYSTEM_ID
) )
val defaultCompilerOptions = findStdLibLanguageVersion(templateClasspath)?.let {
listOf("-language-version", it.versionString)
} ?: emptyList<String>()
val hostConfiguration = createHostConfiguration(projectPath, gradleHome, javaHome, gradleExeSettings) val hostConfiguration = createHostConfiguration(projectPath, gradleHome, javaHome, gradleExeSettings)
return loadDefinitionsFromTemplates( return loadDefinitionsFromTemplates(
listOf(templateClass), listOf(templateClass),
templateClasspath, templateClasspath,
hostConfiguration, hostConfiguration,
additionalClassPath additionalClassPath,
defaultCompilerOptions
).map { ).map {
it.asLegacyOrNull<KotlinScriptDefinitionFromAnnotatedTemplate>()?.let { legacyDef -> it.asLegacyOrNull<KotlinScriptDefinitionFromAnnotatedTemplate>()?.let { legacyDef ->
// Expand scope for old gradle script definition // Expand scope for old gradle script definition
@@ -249,7 +267,8 @@ class GradleScriptDefinitionsContributor(private val project: Project) : ScriptD
GradleKotlinScriptDefinitionWrapper( GradleKotlinScriptDefinitionWrapper(
it.hostConfiguration, it.hostConfiguration,
legacyDef, legacyDef,
version version,
defaultCompilerOptions
) )
} ?: it } ?: it
} }
@@ -30,6 +30,7 @@ abstract class ScriptDefinition : UserDataHolderBase() {
abstract val fileExtension: String abstract val fileExtension: String
abstract val name: String abstract val name: String
open val defaultClassName: String = "Script" open val defaultClassName: String = "Script"
// TODO: used in settings, find out the reason and refactor accordingly // TODO: used in settings, find out the reason and refactor accordingly
abstract val definitionId: String abstract val definitionId: String
@@ -47,6 +48,7 @@ abstract class ScriptDefinition : UserDataHolderBase() {
open val canDefinitionBeSwitchedOff: Boolean get() = true open val canDefinitionBeSwitchedOff: Boolean get() = true
abstract val baseClassType: KotlinType abstract val baseClassType: KotlinType
open val defaultCompilerOptions: Iterable<String> = emptyList()
abstract val compilerOptions: Iterable<String> abstract val compilerOptions: Iterable<String>
abstract val annotationsForSamWithReceivers: List<String> abstract val annotationsForSamWithReceivers: List<String>
@@ -61,7 +63,8 @@ abstract class ScriptDefinition : UserDataHolderBase() {
@Suppress("OverridingDeprecatedMember", "DEPRECATION") @Suppress("OverridingDeprecatedMember", "DEPRECATION")
open class FromLegacy( open class FromLegacy(
override val hostConfiguration: ScriptingHostConfiguration, override val hostConfiguration: ScriptingHostConfiguration,
override val legacyDefinition: KotlinScriptDefinition override val legacyDefinition: KotlinScriptDefinition,
override val defaultCompilerOptions: Iterable<String> = emptyList()
) : ScriptDefinition() { ) : ScriptDefinition() {
override val compilationConfiguration: ScriptCompilationConfiguration by lazy { override val compilationConfiguration: ScriptCompilationConfiguration by lazy {
@@ -109,17 +112,19 @@ abstract class ScriptDefinition : UserDataHolderBase() {
open class FromLegacyTemplate( open class FromLegacyTemplate(
hostConfiguration: ScriptingHostConfiguration, hostConfiguration: ScriptingHostConfiguration,
template: KClass<*>, template: KClass<*>,
templateClasspath: List<File> = emptyList() templateClasspath: List<File> = emptyList(),
defaultCompilerOptions: Iterable<String> = emptyList()
) : FromLegacy( ) : FromLegacy(
hostConfiguration, hostConfiguration,
KotlinScriptDefinitionFromAnnotatedTemplate( KotlinScriptDefinitionFromAnnotatedTemplate(
template, template,
hostConfiguration[ScriptingHostConfiguration.getEnvironment]?.invoke(), hostConfiguration[ScriptingHostConfiguration.getEnvironment]?.invoke(),
templateClasspath templateClasspath
) ),
defaultCompilerOptions
) )
abstract class FromConfigurationsBase : ScriptDefinition() { abstract class FromConfigurationsBase() : ScriptDefinition() {
@Suppress("OverridingDeprecatedMember", "DEPRECATION") @Suppress("OverridingDeprecatedMember", "DEPRECATION")
override val legacyDefinition by lazy { override val legacyDefinition by lazy {
@@ -180,12 +185,14 @@ abstract class ScriptDefinition : UserDataHolderBase() {
open class FromConfigurations( open class FromConfigurations(
override val hostConfiguration: ScriptingHostConfiguration, override val hostConfiguration: ScriptingHostConfiguration,
override val compilationConfiguration: ScriptCompilationConfiguration, override val compilationConfiguration: ScriptCompilationConfiguration,
override val evaluationConfiguration: ScriptEvaluationConfiguration? override val evaluationConfiguration: ScriptEvaluationConfiguration?,
override val defaultCompilerOptions: Iterable<String> = emptyList()
) : FromConfigurationsBase() ) : FromConfigurationsBase()
open class FromNewDefinition( open class FromNewDefinition(
private val baseHostConfiguration: ScriptingHostConfiguration, private val baseHostConfiguration: ScriptingHostConfiguration,
private val definition: kotlin.script.experimental.host.ScriptDefinition private val definition: kotlin.script.experimental.host.ScriptDefinition,
override val defaultCompilerOptions: Iterable<String> = emptyList()
) : FromConfigurationsBase() { ) : FromConfigurationsBase() {
override val hostConfiguration: ScriptingHostConfiguration override val hostConfiguration: ScriptingHostConfiguration
get() = definition.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: baseHostConfiguration get() = definition.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: baseHostConfiguration
@@ -197,10 +204,12 @@ abstract class ScriptDefinition : UserDataHolderBase() {
open class FromTemplate( open class FromTemplate(
baseHostConfiguration: ScriptingHostConfiguration, baseHostConfiguration: ScriptingHostConfiguration,
template: KClass<*>, template: KClass<*>,
contextClass: KClass<*> = ScriptCompilationConfiguration::class contextClass: KClass<*> = ScriptCompilationConfiguration::class,
defaultCompilerOptions: Iterable<String> = emptyList()
) : FromNewDefinition( ) : FromNewDefinition(
baseHostConfiguration, baseHostConfiguration,
createScriptDefinitionFromTemplate(KotlinType(template), baseHostConfiguration, contextClass) createScriptDefinitionFromTemplate(KotlinType(template), baseHostConfiguration, contextClass),
defaultCompilerOptions
) )
companion object { companion object {