From a6c21fbe85672ee1d75c9a6b99096c40c5753517 Mon Sep 17 00:00:00 2001 From: Michael Nedzelsky Date: Tue, 20 Jan 2015 12:17:33 +0300 Subject: [PATCH] JS backend: LibraryUtils: add support for load and write metadata --- .../utils/KotlinJavascriptMetadataUtils.kt | 101 ++++++++++++++++++ .../jetbrains/kotlin/utils/LibraryUtils.kt | 55 +++++----- 2 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt new file mode 100644 index 00000000000..02fe219af5b --- /dev/null +++ b/compiler/util/src/org/jetbrains/kotlin/utils/KotlinJavascriptMetadataUtils.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2015 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.utils + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.io.FileUtil +import java.io.File +import java.io.IOException +import javax.xml.bind.DatatypeConverter.parseBase64Binary +import javax.xml.bind.DatatypeConverter.printBase64Binary +import kotlin.platform.platformStatic + +public class KotlinJavascriptMetadata(public val moduleName: String, public val body: ByteArray) + +public object KotlinJavascriptMetadataUtils { + private val LOG = Logger.getInstance(javaClass()) + + public val JS_EXT: String = ".js" + private val KOTLIN_JAVASCRIPT_METHOD_NAME = "kotlin_module_metadata" + /** + * Matches string like .kotlin_module_metadata(, , ) + */ + private val METADATA_PATTERN = "(?m)\\w+\\.$KOTLIN_JAVASCRIPT_METHOD_NAME\\((\\d+),\\s*(['\"])([^'\"]*)\\2,\\s*(['\"])([^'\"]*)\\4\\)".toRegex() + private val ABI_VERSION = 1 + + platformStatic + public fun hasMetadata(text: String): Boolean = METADATA_PATTERN.matcher(text).find() + + public fun writeMetadata(moduleName: String, content: ByteArray, metaFile: File) { + val text = "// Kotlin.$KOTLIN_JAVASCRIPT_METHOD_NAME($ABI_VERSION, \"$moduleName\", \"${printBase64Binary(content)}\");\n" + FileUtil.writeToFile(metaFile, text) + } + + platformStatic + public fun loadMetadata(file: File): List { + assert(file.exists()) { "Library " + file + " not found" } + + var result: MutableList = arrayListOf() + if (file.isDirectory()) { + loadMetadataFromDirectory(file, result) + } + else if (file.getName().endsWith(JS_EXT)) { + loadMetadataFromFile(file, result) + } + else { + loadMetadataFromZip(file, result) + } + return result + } + + platformStatic + public fun loadMetadata(path: String): List = loadMetadata(File(path)) + + private fun parseMetadata(text: String, path: String, metadataList: MutableList) { + val matcher = METADATA_PATTERN.matcher(text) + while (matcher.find()) { + var abiVersion = Integer.parseInt(matcher.group(1)); + if (abiVersion != ABI_VERSION) LOG.error("Unsupported abi version in $path, expected $ABI_VERSION, but $abiVersion") + + var moduleName = matcher.group(3) + val data = matcher.group(5) + metadataList.add(KotlinJavascriptMetadata(moduleName, parseBase64Binary(data))) + } + } + + private fun loadMetadataFromFile(file: File, metadataList: MutableList) { + try { + val content = FileUtil.loadFile(file) + parseMetadata(content, file.getPath(), metadataList) + } + catch (ex: IOException) { + LOG.error("Could not read ${file.getAbsolutePath()}: ${ex.getMessage()}") + } + } + + private fun loadMetadataFromDirectory(dir: File, metadataList: MutableList) { + LibraryUtils.traverseDirectory(dir) { + file, relativePath -> parseMetadata(FileUtil.loadFile(file), file.getPath(), metadataList) + } + } + + private fun loadMetadataFromZip(file: File, metadataList: MutableList) { + LibraryUtils.traverseArchive(file) { content, relativePath -> + parseMetadata(content, "${file.getPath()}/$relativePath", metadataList) + } + } +} diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/LibraryUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/LibraryUtils.kt index 54074ac55f3..458c004d65e 100644 --- a/compiler/util/src/org/jetbrains/kotlin/utils/LibraryUtils.kt +++ b/compiler/util/src/org/jetbrains/kotlin/utils/LibraryUtils.kt @@ -20,7 +20,6 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.Processor - import java.io.* import java.util.Properties import java.util.jar.Attributes @@ -28,7 +27,6 @@ import java.util.jar.JarFile import java.util.jar.Manifest import java.util.zip.ZipFile import kotlin.platform.platformStatic -import org.jetbrains.kotlin.utils.fileUtils.* public object LibraryUtils { private val LOG = Logger.getInstance(javaClass()) @@ -36,7 +34,6 @@ public object LibraryUtils { public val KOTLIN_JS_MODULE_NAME: String = "Kotlin-JS-Module-Name" private var TITLE_KOTLIN_JAVASCRIPT_STDLIB: String private var TITLE_KOTLIN_JAVASCRIPT_LIB: String - private val JS_EXT = ".js" private val METAINF = "META-INF/" private val MANIFEST_PATH = "${METAINF}MANIFEST.MF" private val METAINF_RESOURCES = "${METAINF}resources/" @@ -84,9 +81,15 @@ public object LibraryUtils { } platformStatic - public fun isKotlinJavascriptLibrary(library: File): Boolean { - return checkAttributeValue(library, TITLE_KOTLIN_JAVASCRIPT_LIB, Attributes.Name.SPECIFICATION_TITLE) - } + public fun isOldKotlinJavascriptLibrary(library: File): Boolean = + checkAttributeValue(library, TITLE_KOTLIN_JAVASCRIPT_LIB, Attributes.Name.SPECIFICATION_TITLE) && getKotlinJsModuleName(library) != null + + platformStatic + public fun isKotlinJavascriptLibraryWithMetadata(library: File): Boolean = KotlinJavascriptMetadataUtils.loadMetadata(library).isNotEmpty() + + platformStatic + public fun isKotlinJavascriptLibrary(library: File): Boolean = + isOldKotlinJavascriptLibrary(library) || isKotlinJavascriptLibraryWithMetadata(library) platformStatic public fun isKotlinJavascriptStdLibrary(library: File): Boolean { @@ -116,11 +119,11 @@ public object LibraryUtils { for (lib in libs) { when { lib.isDirectory() -> - traverseDirectoryWithReportingIOException(lib) { (file, path) -> + traverseDirectory(lib) { (file, path) -> files.add(FileUtil.loadFile(file)) } FileUtil.isJarOrZip(lib) -> - traverseArchiveWithReportingIOException(lib) { (content, path) -> + traverseArchive(lib) { (content, path) -> files.add(content) } else -> @@ -131,11 +134,17 @@ public object LibraryUtils { return files } - private fun processDirectory(dir: File, action: (File, String) -> Unit) { + private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String) { + traverseDirectory(dir) { + file, relativePath -> FileUtil.copy(file, File(outputLibraryJsPath, relativePath)) + } + } + + private fun processDirectory(dir: File, action: (File, relativePath: String) -> Unit) { FileUtil.processFilesRecursively(dir, object : Processor { override fun process(file: File): Boolean { val relativePath = FileUtil.getRelativePath(dir, file) ?: throw IllegalArgumentException("relativePath should not be null " + dir + " " + file) - if (file.isFile() && relativePath.endsWith(JS_EXT)) { + if (file.isFile() && relativePath.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) { val suggestedRelativePath = getSuggestedPath(relativePath) if (suggestedRelativePath == null) return true @@ -146,13 +155,7 @@ public object LibraryUtils { }) } - private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String) { - traverseDirectoryWithReportingIOException(dir) { - file, relativePath -> FileUtil.copy(file, File(outputLibraryJsPath, relativePath)) - } - } - - private fun traverseDirectoryWithReportingIOException(dir: File, action: (File, String) -> Unit) { + fun traverseDirectory(dir: File, action: (File, relativePath: String) -> Unit) { try { processDirectory(dir, action) } @@ -162,28 +165,19 @@ public object LibraryUtils { } private fun copyJsFilesFromZip(file: File, outputLibraryJsPath: String) { - traverseArchiveWithReportingIOException(file) { content, relativePath -> + traverseArchive(file) { content, relativePath -> FileUtil.writeToFile(File(outputLibraryJsPath, relativePath), content) } } - private fun traverseArchiveWithReportingIOException(file: File, action: (String, String) -> Unit) { - try { - traverseArchive(file, action) - } - catch (ex: IOException) { - LOG.error("Could not extract files from archive ${file.getName()}: ${ex.getMessage()}") - } - } - - private fun traverseArchive(file: File, action: (String, String) -> Unit) { + fun traverseArchive(file: File, action: (content: String, relativePath: String) -> Unit) { val zipFile = ZipFile(file.getPath()) try { val zipEntries = zipFile.entries() while (zipEntries.hasMoreElements()) { val entry = zipEntries.nextElement() val entryName = entry.getName() - if (!entry.isDirectory() && entryName.endsWith(JS_EXT)) { + if (!entry.isDirectory() && entryName.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) { val relativePath = getSuggestedPath(entryName) if (relativePath == null) continue @@ -193,6 +187,9 @@ public object LibraryUtils { } } } + catch (ex: IOException) { + LOG.error("Could not extract files from archive ${file.getName()}: ${ex.getMessage()}") + } finally { zipFile.close() }