diff --git a/compiler/testData/cli/js/wrongAbiVersion.out b/compiler/testData/cli/js/wrongAbiVersion.out index 097674f9c17..82e0584333c 100644 --- a/compiler/testData/cli/js/wrongAbiVersion.out +++ b/compiler/testData/cli/js/wrongAbiVersion.out @@ -1,2 +1,2 @@ -error: file 'compiler/testData/cli/js/wrongAbiVersionLib/wrongAbiLib.meta.js' was compiled with an incompatible version of Kotlin. Its ABI version is 0, expected version is $ABI_VERSION$ +error: file 'compiler/testData/cli/js/wrongAbiVersionLib/wrongAbiLib.meta.js' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 0.0.0, expected version is $ABI_VERSION$ COMPILATION_ERROR diff --git a/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java b/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java index 971b23cc80b..c6e169eebf1 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java @@ -36,7 +36,7 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils; import org.jetbrains.kotlin.test.KotlinTestUtils; import org.jetbrains.kotlin.test.TestCaseWithTmpdir; import org.jetbrains.kotlin.utils.ExceptionUtilsKt; -import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils; +import org.jetbrains.kotlin.utils.JsBinaryVersion; import org.jetbrains.kotlin.utils.PathUtil; import org.jetbrains.kotlin.utils.StringsKt; import org.junit.Assert; @@ -159,18 +159,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { } protected void doJsTest(@NotNull String fileName) throws Exception { - doTest(fileName, new K2JSCompiler(), new BinaryVersion() { - @Override - public boolean isCompatible() { - throw new UnsupportedOperationException(); - } - - @NotNull - @Override - public String toString() { - return String.valueOf(KotlinJavascriptMetadataUtils.ABI_VERSION); - } - }); + doTest(fileName, new K2JSCompiler(), JsBinaryVersion.INSTANCE); } public static String removePerfOutput(String output) { diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt index d2dee7020c5..3429c2c4d5f 100644 --- a/compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt +++ b/compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt @@ -23,14 +23,14 @@ import java.io.InputStream import javax.xml.bind.DatatypeConverter.parseBase64Binary import javax.xml.bind.DatatypeConverter.printBase64Binary -class KotlinJavascriptMetadata(val abiVersion: Int, val moduleName: String, val body: ByteArray) { - val isAbiVersionCompatible: Boolean = KotlinJavascriptMetadataUtils.isAbiVersionCompatible(abiVersion) -} +class KotlinJavascriptMetadata(val version: JsBinaryVersion, val moduleName: String, val body: ByteArray) // TODO: move to JS modules class JsBinaryVersion(vararg numbers: Int) : BinaryVersion(*numbers) { override fun isCompatible() = this.isCompatibleTo(INSTANCE) + fun toInteger() = (patch shl 16) + (minOf(minor, 255) shl 8) + minOf(major, 255) + companion object { @JvmField val INSTANCE = JsBinaryVersion(0, 8, 0) @@ -38,6 +38,9 @@ class JsBinaryVersion(vararg numbers: Int) : BinaryVersion(*numbers) { @JvmField val INVALID_VERSION = JsBinaryVersion() + fun fromInteger(version: Int): JsBinaryVersion = + JsBinaryVersion(version and 255, (version shr 8) and 255, version shr 16) + fun readFrom(stream: InputStream): JsBinaryVersion { val dataInput = DataInputStream(stream) val size = dataInput.readInt() @@ -63,37 +66,37 @@ object KotlinJavascriptMetadataUtils { */ private val METADATA_PATTERN = "(?m)\\w+\\.$KOTLIN_JAVASCRIPT_METHOD_NAME\\((\\d+),\\s*(['\"])([^'\"]*)\\2,\\s*(['\"])([^'\"]*)\\4\\)".toPattern() - @JvmField val ABI_VERSION: Int = JsBinaryVersion.INSTANCE.minor - fun replaceSuffix(filePath: String): String = filePath.substringBeforeLast(JS_EXT) + META_JS_SUFFIX - @JvmStatic fun isAbiVersionCompatible(abiVersion: Int): Boolean = abiVersion == ABI_VERSION - - @JvmStatic fun hasMetadata(text: String): Boolean = + @JvmStatic + fun hasMetadata(text: String): Boolean = KOTLIN_JAVASCRIPT_METHOD_NAME_PATTERN.matcher(text).find() && METADATA_PATTERN.matcher(text).find() fun formatMetadataAsString(moduleName: String, content: ByteArray): String = - "// Kotlin.$KOTLIN_JAVASCRIPT_METHOD_NAME($ABI_VERSION, \"$moduleName\", \"${printBase64Binary(content)}\");\n" + "// Kotlin.$KOTLIN_JAVASCRIPT_METHOD_NAME(${JsBinaryVersion.INSTANCE.toInteger()}, \"$moduleName\", \"${printBase64Binary(content)}\");\n" - @JvmStatic fun loadMetadata(file: File): List { + @JvmStatic + fun loadMetadata(file: File): List { assert(file.exists()) { "Library $file not found" } val metadataList = arrayListOf() - JsLibraryUtils.traverseJsLibrary(file) { content, relativePath -> + JsLibraryUtils.traverseJsLibrary(file) { content, _ -> parseMetadata(content, metadataList) } return metadataList } - @JvmStatic fun loadMetadata(path: String): List = loadMetadata(File(path)) + @JvmStatic + fun loadMetadata(path: String): List = loadMetadata(File(path)) - @JvmStatic fun parseMetadata(text: String, metadataList: MutableList) { + @JvmStatic + fun parseMetadata(text: String, metadataList: MutableList) { // Check for literal pattern first in order to reduce time for large files without metadata if (!KOTLIN_JAVASCRIPT_METHOD_NAME_PATTERN.matcher(text).find()) return val matcher = METADATA_PATTERN.matcher(text) while (matcher.find()) { - val abiVersion = matcher.group(1).toInt() + val abiVersion = JsBinaryVersion.fromInteger(matcher.group(1).toInt()) val moduleName = matcher.group(3) val data = matcher.group(5) metadataList.add(KotlinJavascriptMetadata(abiVersion, moduleName, parseBase64Binary(data))) diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/JsAnalyzerFacade.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/JsAnalyzerFacade.kt index b26fc63243e..7e4ef4464f4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/JsAnalyzerFacade.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/JsAnalyzerFacade.kt @@ -68,7 +68,7 @@ object JsAnalyzerFacade : AnalyzerFacade() { if (moduleInfo is LibraryInfo && KotlinJavaScriptLibraryDetectionUtil.isKotlinJavaScriptLibrary(moduleInfo.library)) { val providers = moduleInfo.library.getFiles(OrderRootType.CLASSES) .flatMap { KotlinJavascriptMetadataUtils.loadMetadata(PathUtil.getLocalPath(it)!!) } - .filter { it.isAbiVersionCompatible } + .filter { it.version.isCompatible() } .mapNotNull { KotlinJavascriptSerializationUtil.readModule( it.body, moduleContext.storageManager, moduleDescriptor, container.get() diff --git a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinJavaScriptAbiVersionIndex.kt b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinJavaScriptAbiVersionIndex.kt index ccbc6829c77..5594b55d8c8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinJavaScriptAbiVersionIndex.kt +++ b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinJavaScriptAbiVersionIndex.kt @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils import java.util.* object KotlinJavaScriptAbiVersionIndex : KotlinAbiVersionIndexBase( - KotlinJavaScriptAbiVersionIndex::class.java, { JsBinaryVersion(*it) } + KotlinJavaScriptAbiVersionIndex::class.java, ::JsBinaryVersion ) { override fun getIndexer() = INDEXER @@ -35,7 +35,7 @@ object KotlinJavaScriptAbiVersionIndex : KotlinAbiVersionIndexBase val result = HashMap() @@ -45,13 +45,9 @@ object KotlinJavaScriptAbiVersionIndex : KotlinAbiVersionIndexBase() KotlinJavascriptMetadataUtils.parseMetadata(text, metadataList) for (metadata in metadataList) { - val version = if (KotlinJavascriptMetadataUtils.isAbiVersionCompatible(metadata.abiVersion)) { - JsBinaryVersion(0, metadata.abiVersion, 0) - } - else { - // Version is set to something weird - JsBinaryVersion.INVALID_VERSION - } + val version = metadata.version.takeIf { it.isCompatible() } + // Version is set to something weird + ?: JsBinaryVersion.INVALID_VERSION result[version] = null } } diff --git a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt index 0a952c80763..412e7a83e66 100644 --- a/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/versions/KotlinRuntimeLibraryUtil.kt @@ -56,26 +56,17 @@ import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion import org.jetbrains.kotlin.utils.JsBinaryVersion -import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils import org.jetbrains.kotlin.utils.PathUtil import org.jetbrains.kotlin.utils.addToStdlib.constant import java.io.File import java.io.IOException fun getLibraryRootsWithAbiIncompatibleKotlinClasses(module: Module): Collection> { - return getLibraryRootsWithAbiIncompatibleVersion( - module, - JvmMetadataVersion.INSTANCE, - KotlinMetadataVersionIndex, - { version -> !version.isCompatible() }) + return getLibraryRootsWithAbiIncompatibleVersion(module, JvmMetadataVersion.INSTANCE, KotlinMetadataVersionIndex) } fun getLibraryRootsWithAbiIncompatibleForKotlinJs(module: Module): Collection> { - return getLibraryRootsWithAbiIncompatibleVersion( - module, - JsBinaryVersion.INSTANCE, - KotlinJavaScriptAbiVersionIndex, - { version -> !KotlinJavascriptMetadataUtils.isAbiVersionCompatible(version.minor) }) // TODO: support major.minor.patch version in JS metadata + return getLibraryRootsWithAbiIncompatibleVersion(module, JsBinaryVersion.INSTANCE, KotlinJavaScriptAbiVersionIndex) } fun updateLibraries(project: Project, libraries: Collection) { @@ -243,8 +234,8 @@ data class BinaryVersionedFile(val file: VirtualFile, val private fun getLibraryRootsWithAbiIncompatibleVersion( module: Module, supportedVersion: T, - index: ScalarIndexExtension, - checkVersion: (T) -> Boolean): Collection> { + index: ScalarIndexExtension +): Collection> { val id = index.name val moduleWithAllDependencies = setOf(module) + ModuleUtil.getAllDependentModules(module) @@ -252,7 +243,7 @@ private fun getLibraryRootsWithAbiIncompatibleVersion( moduleWithAllDependencies.map { it.moduleWithLibrariesScope }.toTypedArray()) val allVersions = FileBasedIndex.getInstance().getAllKeys(id, module.project) - val badVersions = allVersions.filter(checkVersion).toHashSet() + val badVersions = allVersions.filterNot(BinaryVersion::isCompatible).toHashSet() val badRoots = Sets.newHashSet>() val fileIndex = ProjectFileIndex.SERVICE.getInstance(module.project) diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java index d7eaecb1e2a..e8beea23203 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java @@ -37,8 +37,8 @@ import org.jetbrains.kotlin.serialization.js.JsModuleDescriptor; import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil; import org.jetbrains.kotlin.serialization.js.ModuleKind; import org.jetbrains.kotlin.storage.LockBasedStorageManager; +import org.jetbrains.kotlin.utils.JsBinaryVersion; import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata; -import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils; import java.util.ArrayList; import java.util.Collection; @@ -128,9 +128,8 @@ public abstract class JsConfig { } private JsModuleDescriptor createModuleDescriptor(KotlinJavascriptMetadata metadata) { - assert metadata.isAbiVersionCompatible() : - "expected abi version " + KotlinJavascriptMetadataUtils.ABI_VERSION + - ", but metadata.abiVersion = " + metadata.getAbiVersion(); + assert metadata.getVersion().isCompatible() : + "Expected JS metadata version " + JsBinaryVersion.INSTANCE + ", but actual metadata version is " + metadata.getVersion(); ModuleDescriptorImpl moduleDescriptor = new ModuleDescriptorImpl( Name.special("<" + metadata.getModuleName() + ">"), storageManager, JsPlatform.INSTANCE.getBuiltIns() diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/LibrarySourcesConfig.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/LibrarySourcesConfig.java index 5ac64f0bb86..6d2603f88d0 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/LibrarySourcesConfig.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/LibrarySourcesConfig.java @@ -32,6 +32,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.idea.KotlinFileType; import org.jetbrains.kotlin.psi.KtFile; +import org.jetbrains.kotlin.utils.JsBinaryVersion; import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata; import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils; import org.jetbrains.kotlin.utils.LibraryUtils; @@ -144,10 +145,10 @@ public class LibrarySourcesConfig extends JsConfig { } for (KotlinJavascriptMetadata metadata : metadataList) { - if (!metadata.isAbiVersionCompatible()) { + if (!metadata.getVersion().isCompatible()) { report.invoke("File '" + path + "' was compiled with an incompatible version of Kotlin. " + - "Its ABI version is " + metadata.getAbiVersion() + - ", expected version is " + KotlinJavascriptMetadataUtils.ABI_VERSION); + "The binary version of its metadata is " + metadata.getVersion() + + ", expected version is " + JsBinaryVersion.INSTANCE); return true; } } diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt index cffb3c45d07..8dceb27d712 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/KotlinJavascriptSerializationUtil.kt @@ -158,13 +158,6 @@ object KotlinJavascriptSerializationUtil { packageProto.functionCount == 0 && packageProto.propertyCount == 0 && packageProto.typeAliasCount == 0 }) continue - val packageFqName = fqName.asString() - - val header = JsProtoBuf.Header.newBuilder() - // TODO: write pre-release flag if needed - // TODO: write JS code binary version - header.packageFqName = packageFqName - val stream = ByteArrayOutputStream() with(DataOutputStream(stream)) { val version = JsBinaryVersion.INSTANCE.toArray() @@ -172,7 +165,7 @@ object KotlinJavascriptSerializationUtil { version.forEach(this::writeInt) } - header.build().writeDelimitedTo(stream) + serializeHeader(fqName).writeDelimitedTo(stream) part.writeTo(stream) contentMap[JsSerializerProtocol.getKjsmFilePath(fqName)] = stream.toByteArray() @@ -181,6 +174,19 @@ object KotlinJavascriptSerializationUtil { return contentMap } + fun serializeHeader(packageFqName: FqName?): JsProtoBuf.Header { + val header = JsProtoBuf.Header.newBuilder() + + if (packageFqName != null) { + header.packageFqName = packageFqName.asString() + } + + // TODO: write pre-release flag if needed + // TODO: write JS code binary version + + return header.build() + } + private fun getPackagesFqNames(module: ModuleDescriptor): Set { return HashSet().apply { getSubPackagesFqNames(module.getPackage(FqName.ROOT), this) @@ -202,14 +208,17 @@ object KotlinJavascriptSerializationUtil { } private fun JsModuleDescriptor.serializeToBinaryMetadata(bindingContext: BindingContext): ByteArray { - val proto = serializeMetadata(bindingContext, data, kind, imported) return ByteArrayOutputStream().apply { - GZIPOutputStream(this).use(proto::writeTo) + GZIPOutputStream(this).use { stream -> + serializeHeader(null).writeDelimitedTo(stream) + serializeMetadata(bindingContext, data, kind, imported).writeTo(stream) + } }.toByteArray() } private fun ByteArray.deserializeToLibraryParts(name: String): JsModuleDescriptor> { val content = GZIPInputStream(ByteArrayInputStream(this)).use { stream -> + JsProtoBuf.Header.parseDelimitedFrom(stream, JsSerializerProtocol.extensionRegistry) JsProtoBuf.Library.parseFrom(stream, JsSerializerProtocol.extensionRegistry) }