Infer API version from older runtime in compiler's classpath

For example, if you invoke kotlinc 1.1 with kotlin-stdlib 1.0 in the classpath,
we now infer -api-version 1.0 automatically
This commit is contained in:
Alexander Udalov
2017-02-15 19:19:46 +03:00
parent b56639a775
commit 71fcb07fad
5 changed files with 77 additions and 18 deletions
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.utils.StringsKt;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
@@ -280,7 +281,12 @@ public abstract class CLICompiler<A extends CommonCompilerArguments> {
configuration.put(
CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS,
new LanguageVersionSettingsImpl(languageVersion, ApiVersion.createByLanguageVersion(apiVersion), extraLanguageFeatures)
new LanguageVersionSettingsImpl(
languageVersion,
ApiVersion.createByLanguageVersion(apiVersion),
extraLanguageFeatures,
arguments.apiVersion != null
)
);
}
@@ -19,12 +19,11 @@ package org.jetbrains.kotlin.cli.jvm
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.MavenComparableVersion
import org.jetbrains.kotlin.config.*
import java.io.IOException
import java.util.*
import java.util.jar.Attributes
@@ -113,29 +112,71 @@ object JvmRuntimeVersionsConsistencyChecker {
fun checkCompilerClasspathConsistency(
messageCollector: MessageCollector,
languageVersionSettings: LanguageVersionSettings?,
configuration: CompilerConfiguration,
classpathJarRoots: List<VirtualFile>
) {
val runtimeJarsInfo = collectRuntimeJarsInfo(classpathJarRoots)
if (runtimeJarsInfo.jars.isEmpty()) return
val languageVersionSettings = configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS)
val apiVersion = languageVersionSettings?.apiVersion?.version ?: CURRENT_COMPILER_VERSION
val consistency = checkCompilerClasspathConsistency(messageCollector, apiVersion, runtimeJarsInfo)
if (consistency != ClasspathConsistency.Consistent) {
val message = when (consistency) {
is ClasspathConsistency.InconsistentWithApiVersion ->
"Runtime JAR files in the classpath have the version ${consistency.actualRuntimeVersion}, " +
if (consistency is ClasspathConsistency.InconsistentWithApiVersion) {
val actualRuntimeVersion = consistency.actualRuntimeVersion
messageCollector.issue(
null,
"Runtime JAR files in the classpath have the version $actualRuntimeVersion, " +
"which is older than the API version $apiVersion. " +
"Consider removing them from the classpath or passing '-api-version ${consistency.actualRuntimeVersion}' explicitly. " +
"You can also pass '-language-version ${consistency.actualRuntimeVersion}' instead, which will restrict " +
"Consider using the runtime of version $apiVersion, or pass '-api-version $actualRuntimeVersion' explicitly to " +
"restrict the available APIs to the runtime of version $actualRuntimeVersion. " +
"You can also pass '-language-version $actualRuntimeVersion' instead, which will restrict " +
"not only the APIs to the specified version, but also the language features. " +
"Alternatively, you can use '-Xskip-runtime-version-check' to suppress this warning"
else ->
)
val actualApi = ApiVersion.parse(actualRuntimeVersion.toString())
if (actualApi != null) {
val newSettings = if (languageVersionSettings == null) {
LanguageVersionSettingsImpl(
LanguageVersionSettingsImpl.DEFAULT.languageVersion,
actualApi,
listOf(LanguageFeature.WarnOnCoroutines)
)
}
else {
val inferredApiVersion =
if (@Suppress("DEPRECATION") languageVersionSettings.isApiVersionExplicit)
languageVersionSettings.apiVersion
else
minOf(languageVersionSettings.apiVersion, actualApi)
// "minOf" is needed in case when API version was inferred from language version and it's older than actualApi.
// For example, in "kotlinc-1.2 -language-version 1.0 -cp kotlin-runtime-1.1.jar" we should still infer API = 1.0
LanguageVersionSettingsImpl(
languageVersionSettings.languageVersion,
inferredApiVersion,
languageVersionSettings.additionalFeatures,
isApiVersionExplicit = false
)
}
messageCollector.issue(null, "Old runtime has been found in the classpath. " +
"Initial language version settings: $languageVersionSettings. " +
"Updated language version settings: $newSettings", CompilerMessageSeverity.LOGGING)
configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, newSettings)
}
else {
messageCollector.issue(null, "Could not parse runtime JAR version: $actualRuntimeVersion")
}
}
else if (consistency != ClasspathConsistency.Consistent) {
messageCollector.issue(
null,
"Some runtime JAR files in the classpath have an incompatible version. " +
"Consider removing them from the classpath or use '-Xskip-runtime-version-check' to suppress this warning"
}
messageCollector.issue(null, message)
)
}
val librariesWithBundled = runtimeJarsInfo.otherLibrariesWithBundledRuntime
@@ -159,7 +159,7 @@ class KotlinCoreEnvironment private constructor(
if (messageCollector != null) {
JvmRuntimeVersionsConsistencyChecker.checkCompilerClasspathConsistency(
messageCollector,
configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS),
configuration,
initialRoots.mapNotNull { (file, type) -> if (type == JavaRoot.RootType.BINARY) file else null }
)
}
@@ -36,7 +36,6 @@ import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.utils.addIfNotNull
import org.junit.Assert
@@ -108,6 +107,12 @@ abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava<TestModule, Tes
override fun supportsFeature(feature: LanguageFeature): Boolean =
languageFeatures[feature] ?: delegate.supportsFeature(feature)
override val additionalFeatures: Collection<LanguageFeature>
get() = error("Must not be called")
override val isApiVersionExplicit: Boolean
get() = error("Must not be called")
}
inner class TestFile(
@@ -99,14 +99,21 @@ interface LanguageVersionSettings {
// Please do not use this to enable/disable specific features/checks. Instead add a new LanguageFeature entry and call supportsFeature
val languageVersion: LanguageVersion
// TODO: refactor arguments related to coroutines so that this list is empty by default
val additionalFeatures: Collection<LanguageFeature>
@Deprecated("This is a temporary solution, please do not use.")
val isApiVersionExplicit: Boolean
}
class LanguageVersionSettingsImpl @JvmOverloads constructor(
override val languageVersion: LanguageVersion,
override val apiVersion: ApiVersion,
additionalFeatures: Collection<LanguageFeature> = emptySet()
additionalFeatures: Collection<LanguageFeature> = emptySet(),
override val isApiVersionExplicit: Boolean = false
) : LanguageVersionSettings {
private val additionalFeatures = additionalFeatures.toSet()
override val additionalFeatures = additionalFeatures.toSet()
override fun supportsFeature(feature: LanguageFeature): Boolean {
val since = feature.sinceVersion