diff --git a/build.xml b/build.xml index 35c836a7541..c555cdd2ac4 100644 --- a/build.xml +++ b/build.xml @@ -453,6 +453,8 @@ + + @@ -469,6 +471,8 @@ + + @@ -1155,6 +1159,9 @@ + + + diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/JvmRuntimeVersionsConsistencyChecker.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/JvmRuntimeVersionsConsistencyChecker.kt new file mode 100644 index 00000000000..94d9fb24aaa --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/JvmRuntimeVersionsConsistencyChecker.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2010-2016 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.cli.jvm + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.vfs.VirtualFile +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.LanguageVersion +import org.jetbrains.kotlin.config.LanguageVersionSettings +import java.util.* +import java.util.jar.Manifest + +internal inline fun Properties.getString(propertyName: String, otherwise: () -> String): String = + getProperty(propertyName) ?: otherwise() + +object JvmRuntimeVersionsConsistencyChecker { + private val LOG = Logger.getInstance(JvmRuntimeVersionsConsistencyChecker::class.java) + + private fun fatal(message: String): Nothing { + LOG.error(message) + throw AssertionError(message) + } + + private fun T?.assertNotNull(message: () -> String): T = + if (this == null) fatal(message()) else this + + // TODO replace with ERROR after bootstrapping + private val VERSION_ISSUE_SEVERITY = CompilerMessageSeverity.WARNING + + private const val META_INF = "META-INF" + private const val MANIFEST_MF = "$META_INF/MANIFEST.MF" + + private const val MANIFEST_KOTLIN_VERSION_ATTRIBUTE = "manifest.impl.attribute.kotlin.version" + private const val MANIFEST_KOTLIN_VERSION_VALUE = "manifest.impl.value.kotlin.version" + private const val MANIFEST_KOTLIN_RUNTIME_COMPONENT = "manifest.impl.attribute.kotlin.runtime.component" + private const val MANIFEST_KOTLIN_RUNTIME_COMPONENT_CORE = "manifest.impl.value.kotlin.runtime.component.core" + + private const val KOTLIN_STDLIB_MODULE = "$META_INF/kotlin-stdlib.kotlin_module" + private const val KOTLIN_REFLECT_MODULE = "$META_INF/kotlin-reflection.kotlin_module" + + private val KOTLIN_VERSION_ATTRIBUTE: String + private val CURRENT_COMPILER_VERSION: LanguageVersion + + private val KOTLIN_RUNTIME_COMPONENT_ATTRIBUTE: String + private val KOTLIN_RUNTIME_COMPONENT_CORE: String + + init { + val manifestProperties: Properties = try { + JvmRuntimeVersionsConsistencyChecker::class.java + .getResourceAsStream("/kotlinManifest.properties") + .let { input -> Properties().apply { load(input) } } + } + catch (e: Exception) { + LOG.error(e) + throw e + } + + KOTLIN_VERSION_ATTRIBUTE = manifestProperties.getProperty(MANIFEST_KOTLIN_VERSION_ATTRIBUTE) + .assertNotNull { "$MANIFEST_KOTLIN_VERSION_ATTRIBUTE not found in kotlinManifest.properties" } + + CURRENT_COMPILER_VERSION = run { + val kotlinVersionString = manifestProperties.getProperty(MANIFEST_KOTLIN_VERSION_VALUE) + .assertNotNull { "$MANIFEST_KOTLIN_VERSION_VALUE not found in kotlinManifest.properties" } + + LanguageVersion.fromFullVersionString(kotlinVersionString) + .assertNotNull { "Incorrect Kotlin version: $kotlinVersionString" } + } + + if (CURRENT_COMPILER_VERSION != LanguageVersion.LATEST) { + fatal("Kotlin compiler version $CURRENT_COMPILER_VERSION in kotlinManifest.properties doesn't match ${LanguageVersion.LATEST}") + } + + KOTLIN_RUNTIME_COMPONENT_ATTRIBUTE = manifestProperties.getProperty(MANIFEST_KOTLIN_RUNTIME_COMPONENT) + .assertNotNull { "$MANIFEST_KOTLIN_RUNTIME_COMPONENT not found in kotlinManifest.properties" } + KOTLIN_RUNTIME_COMPONENT_CORE = manifestProperties.getProperty(MANIFEST_KOTLIN_RUNTIME_COMPONENT_CORE) + .assertNotNull { "$MANIFEST_KOTLIN_RUNTIME_COMPONENT_CORE not found in kotlinManifest.properties" } + } + + class FileWithLanguageVersion(val component: String, val file: VirtualFile, val version: LanguageVersion) { + override fun toString(): String = + "${file.name}:$version ($component)" + } + + class RuntimeJarsInfo( + val coreJars: List + ) { + val hasAnyJarsToCheck: Boolean get() = coreJars.isNotEmpty() + } + + fun checkCompilerClasspathConsistency( + messageCollector: MessageCollector, + languageVersionSettings: LanguageVersionSettings?, + classpathJars: List + ) { + val runtimeJarsInfo = collectRuntimeJarsInfo(classpathJars) + if (!runtimeJarsInfo.hasAnyJarsToCheck) return + + val languageVersion = languageVersionSettings?.languageVersion ?: CURRENT_COMPILER_VERSION + + // Even if language version option was explicitly specified, the JAR files SHOULD NOT be newer than the compiler. + runtimeJarsInfo.coreJars.forEach { + checkNotNewerThanCompiler(messageCollector, it) + } + + runtimeJarsInfo.coreJars.forEach { + checkCompatibleWithLanguageVersion(messageCollector, it, languageVersion) + } + + checkMatchingVersions(messageCollector, runtimeJarsInfo) + } + + private fun checkNotNewerThanCompiler(messageCollector: MessageCollector, jar: FileWithLanguageVersion) { + if (jar.version > CURRENT_COMPILER_VERSION) { + messageCollector.issue("Run-time JAR file $jar is newer than compiler version $CURRENT_COMPILER_VERSION") + } + } + + private fun checkCompatibleWithLanguageVersion(messageCollector: MessageCollector, jar: FileWithLanguageVersion, languageVersion: LanguageVersion) { + if (jar.version < languageVersion) { + messageCollector.issue("Run-time JAR file $jar is older than required for language version $languageVersion") + } + } + + private fun checkMatchingVersions(messageCollector: MessageCollector, runtimeJarsInfo: RuntimeJarsInfo) { + val oldestCoreJar = runtimeJarsInfo.coreJars.minBy { it.version } ?: return + val newestCoreJar = runtimeJarsInfo.coreJars.maxBy { it.version } ?: return + + if (oldestCoreJar.version != newestCoreJar.version) { + messageCollector.issue("Run-time JAR file $oldestCoreJar is not compatible with JAR file $newestCoreJar") + } + } + + private fun MessageCollector.issue(message: String) { + report(VERSION_ISSUE_SEVERITY, message, CompilerMessageLocation.NO_LOCATION) + } + + private fun collectRuntimeJarsInfo(classpathJars: List): RuntimeJarsInfo { + val kotlinCoreJars = ArrayList(2) + + for (jar in classpathJars) { + val manifest = try { + val manifestFile = jar.findFileByRelativePath(MANIFEST_MF) ?: continue + Manifest(manifestFile.inputStream) + } + catch (e: Exception) { + continue + } + + val runtimeComponent = getKotlinRuntimeComponent(jar, manifest) ?: continue + val version = manifest.getKotlinLanguageVersion() + + if (runtimeComponent == KOTLIN_RUNTIME_COMPONENT_CORE) { + kotlinCoreJars.add(FileWithLanguageVersion(runtimeComponent, jar, version)) + } + } + + return RuntimeJarsInfo(kotlinCoreJars) + } + + private fun getKotlinRuntimeComponent(jar: VirtualFile, manifest: Manifest): String? { + manifest.mainAttributes.getValue(KOTLIN_RUNTIME_COMPONENT_ATTRIBUTE)?.let { return it } + + if (jar.findFileByRelativePath(KOTLIN_STDLIB_MODULE) != null) return KOTLIN_RUNTIME_COMPONENT_CORE + if (jar.findFileByRelativePath(KOTLIN_REFLECT_MODULE) != null) return KOTLIN_RUNTIME_COMPONENT_CORE + + return null + } + + private fun Manifest.getKotlinLanguageVersion(): LanguageVersion = + mainAttributes.getValue(KOTLIN_VERSION_ATTRIBUTE)?.let { + LanguageVersion.fromFullVersionString(it) + } + ?: LanguageVersion.KOTLIN_1_0 + +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 72a5a1caaac..c863831400c 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -70,6 +70,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.WARNING import org.jetbrains.kotlin.cli.common.toBooleanLenient +import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot import org.jetbrains.kotlin.cli.jvm.config.JvmContentRoot @@ -154,6 +155,13 @@ class KotlinCoreEnvironment private constructor( val initialRoots = configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS).classpathRoots() + val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + if (messageCollector != null) { + val languageVersionSettings = configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS) + val classpathJars = initialRoots.mapNotNull { if (it.type == JavaRoot.RootType.BINARY) it.file else null } + JvmRuntimeVersionsConsistencyChecker.checkCompilerClasspathConsistency(messageCollector, languageVersionSettings, classpathJars) + } + // REPL and kapt2 update classpath dynamically val indexFactory = JvmUpdateableDependenciesIndexFactory() diff --git a/compiler/tests-common/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java b/compiler/tests-common/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java index ecde141cd6b..eb2dcfb0e07 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java +++ b/compiler/tests-common/org/jetbrains/kotlin/checkers/BaseDiagnosticsTest.java @@ -34,10 +34,7 @@ import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.asJava.DuplicateJvmSignatureUtilKt; -import org.jetbrains.kotlin.config.ApiVersion; -import org.jetbrains.kotlin.config.LanguageFeature; -import org.jetbrains.kotlin.config.LanguageVersionSettings; -import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl; +import org.jetbrains.kotlin.config.*; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.diagnostics.*; import org.jetbrains.kotlin.load.java.InternalFlexibleTypeTransformer; @@ -327,6 +324,13 @@ public abstract class BaseDiagnosticsTest return enabled != null ? enabled : LanguageVersionSettingsImpl.DEFAULT.supportsFeature(feature); } + @NotNull + @Override + public LanguageVersion getLanguageVersion() { + // TODO provide base language version + throw new UnsupportedOperationException("This instance of LanguageVersionSettings should be used for tests only"); + } + @NotNull @Override public ApiVersion getApiVersion() { diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 0a139b3ac56..3b584efd498 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -77,11 +77,12 @@ enum class LanguageVersion(val versionString: String) : DescriptionAware { interface LanguageVersionSettings { fun supportsFeature(feature: LanguageFeature): Boolean + val languageVersion: LanguageVersion val apiVersion: ApiVersion } class LanguageVersionSettingsImpl @JvmOverloads constructor( - private val languageVersion: LanguageVersion, + override val languageVersion: LanguageVersion, override val apiVersion: ApiVersion, additionalFeatures: Collection = emptySet() ) : LanguageVersionSettings { diff --git a/resources/kotlinManifest.properties b/resources/kotlinManifest.properties index 9bb554fb986..a6218e48a6f 100644 --- a/resources/kotlinManifest.properties +++ b/resources/kotlinManifest.properties @@ -1,5 +1,10 @@ manifest.impl.vendor=JetBrains +manifest.impl.attribute.kotlin.version=Kotlin-version +manifest.impl.value.kotlin.version=1.1 +manifest.impl.attribute.kotlin.runtime.component=Kotlin-runtime-component +manifest.impl.value.kotlin.runtime.component.core=Core + manifest.impl.title.kotlin.compiler=Kotlin Compiler manifest.impl.title.kotlin.compiler.javadoc=Kotlin Compiler Javadoc manifest.impl.title.kotlin.compiler.sources=Kotlin Compiler Sources