Move JS binary version utilities to 'js.config'
This allows to replace dependency of 'util' on 'deserialization' with dependency on 'descriptors'.
This commit is contained in:
committed by
Alexander Udalov
parent
fe5104b865
commit
7bb77e5672
@@ -7,7 +7,7 @@ plugins {
|
||||
dependencies {
|
||||
api(kotlinStdlib())
|
||||
api(project(":compiler:compiler.version"))
|
||||
api(project(":core:deserialization"))
|
||||
api(project(":core:descriptors"))
|
||||
|
||||
compileOnly(project(":kotlin-reflect-api"))
|
||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* 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 com.intellij.util.Processor
|
||||
import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipException
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
object JsLibraryUtils {
|
||||
private val LOG = Logger.getInstance(LibraryUtils::class.java)
|
||||
|
||||
private val META_INF_RESOURCES = "${LibraryUtils.META_INF}resources/"
|
||||
|
||||
@JvmStatic fun copyJsFilesFromLibraries(libraries: List<String>, outputLibraryJsPath: String, copySourceMap: Boolean = false) {
|
||||
for (library in libraries) {
|
||||
val file = File(library)
|
||||
assert(file.exists()) { "Library $library not found" }
|
||||
|
||||
if (file.isDirectory) {
|
||||
copyJsFilesFromDirectory(file, outputLibraryJsPath, copySourceMap)
|
||||
}
|
||||
else {
|
||||
copyJsFilesFromZip(file, outputLibraryJsPath, copySourceMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic fun traverseJsLibraries(libs: List<File>, action: (JsLibrary) -> Unit) {
|
||||
libs.forEach { traverseJsLibrary(it, action) }
|
||||
}
|
||||
|
||||
@JvmStatic fun traverseJsLibrary(lib: File, action: (JsLibrary) -> Unit) {
|
||||
when {
|
||||
lib.isDirectory -> traverseDirectory(lib, action)
|
||||
FileUtil.isJarOrZip(lib) -> traverseArchive(lib, action)
|
||||
lib.name.endsWith(KotlinJavascriptMetadataUtils.JS_EXT) -> {
|
||||
lib.runIfFileExists(lib.path, action)
|
||||
val jsFile = lib.withReplacedExtensionOrNull(
|
||||
KotlinJavascriptMetadataUtils.META_JS_SUFFIX, KotlinJavascriptMetadataUtils.JS_EXT
|
||||
)
|
||||
jsFile?.runIfFileExists(jsFile.path, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun File.runIfFileExists(relativePath: String, action: (JsLibrary) -> Unit) {
|
||||
if (isFile) {
|
||||
action(JsLibrary(readText(), relativePath, correspondingSourceMapFile().contentIfExists(), this))
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyJsFilesFromDirectory(dir: File, outputLibraryJsPath: String, copySourceMap: Boolean) {
|
||||
traverseDirectory(dir) { copyLibrary(outputLibraryJsPath, it, copySourceMap) }
|
||||
}
|
||||
|
||||
private fun File.contentIfExists(): String? = if (exists()) readText() else null
|
||||
|
||||
private fun File.correspondingSourceMapFile(): File = File(parentFile, name + ".map")
|
||||
|
||||
private fun processDirectory(dir: File, action: (JsLibrary) -> Unit) {
|
||||
FileUtil.processFilesRecursively(dir, Processor<File> { file ->
|
||||
val relativePath = FileUtil.getRelativePath(dir, file)
|
||||
?: throw IllegalArgumentException("relativePath should not be null $dir $file")
|
||||
if (relativePath.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
||||
val suggestedRelativePath = getSuggestedPath(relativePath) ?: return@Processor true
|
||||
file.runIfFileExists(suggestedRelativePath, action)
|
||||
}
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
private fun traverseDirectory(dir: File, action: (JsLibrary) -> Unit) {
|
||||
try {
|
||||
processDirectory(dir, action)
|
||||
}
|
||||
catch (ex: IOException) {
|
||||
LOG.error("Could not read files from directory ${dir.name}: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyJsFilesFromZip(file: File, outputLibraryJsPath: String, copySourceMap: Boolean) {
|
||||
traverseArchive(file) { copyLibrary(outputLibraryJsPath, it, copySourceMap) }
|
||||
}
|
||||
|
||||
private fun copyLibrary(outputPath: String, library: JsLibrary, copySourceMap: Boolean) {
|
||||
val targetFile = File(outputPath, library.path)
|
||||
targetFile.parentFile.mkdirs()
|
||||
targetFile.writeText(library.content)
|
||||
if (copySourceMap) {
|
||||
library.sourceMapContent?.let { File(targetFile.parent, targetFile.name + ".map").writeText(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun traverseArchive(file: File, action: (JsLibrary) -> Unit) {
|
||||
val zipFile = try {
|
||||
ZipFile(file.path)
|
||||
} catch (e: ZipException) {
|
||||
throw IOException("Failed to open zip file: $file", e)
|
||||
}
|
||||
try {
|
||||
val zipEntries = zipFile.entries()
|
||||
val librariesWithoutSourceMaps = mutableListOf<JsLibrary>()
|
||||
val possibleMapFiles = mutableMapOf<String, ZipEntry>()
|
||||
|
||||
while (zipEntries.hasMoreElements()) {
|
||||
val entry = zipEntries.nextElement()
|
||||
val entryName = entry.name
|
||||
if (!entry.isDirectory) {
|
||||
if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_EXT)) {
|
||||
val relativePath = getSuggestedPath(entryName) ?: continue
|
||||
|
||||
val stream = zipFile.getInputStream(entry)
|
||||
val content = FileUtil.loadTextAndClose(stream)
|
||||
librariesWithoutSourceMaps += JsLibrary(content, relativePath, null, null)
|
||||
}
|
||||
else if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_MAP_EXT)) {
|
||||
val correspondingJsPath = entryName.removeSuffix(KotlinJavascriptMetadataUtils.JS_MAP_EXT) +
|
||||
KotlinJavascriptMetadataUtils.JS_EXT
|
||||
possibleMapFiles[correspondingJsPath] = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
librariesWithoutSourceMaps
|
||||
.map {
|
||||
val zipEntry = possibleMapFiles[it.path]
|
||||
if (zipEntry != null) {
|
||||
val stream = zipFile.getInputStream(zipEntry)
|
||||
val content = FileUtil.loadTextAndClose(stream)
|
||||
it.copy(sourceMapContent = content)
|
||||
}
|
||||
else {
|
||||
it
|
||||
}
|
||||
}
|
||||
.forEach(action)
|
||||
}
|
||||
catch (ex: IOException) {
|
||||
LOG.error("Could not extract files from archive ${file.name}: ${ex.message}")
|
||||
}
|
||||
finally {
|
||||
zipFile.close()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSuggestedPath(path: String): String? {
|
||||
val systemIndependentPath = FileUtil.toSystemIndependentName(path)
|
||||
if (systemIndependentPath.startsWith(LibraryUtils.META_INF)) {
|
||||
if (systemIndependentPath.startsWith(META_INF_RESOURCES)) {
|
||||
return path.substring(META_INF_RESOURCES.length)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
data class JsLibrary(val content: String, val path: String, val sourceMapContent: String?, val file: File?)
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
||||
import java.io.DataInputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
class KotlinJavascriptMetadata(val version: JsMetadataVersion, val moduleName: String, val body: ByteArray)
|
||||
|
||||
// TODO: move to JS modules
|
||||
class JsMetadataVersion(vararg numbers: Int) : BinaryVersion(*numbers) {
|
||||
override fun isCompatible(): Boolean =
|
||||
this.isCompatibleTo(INSTANCE)
|
||||
|
||||
fun toInteger() = (patch shl 16) + (minOf(minor, 255) shl 8) + minOf(major, 255)
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val INSTANCE = JsMetadataVersion(1, 2, 6)
|
||||
|
||||
@JvmField
|
||||
val INVALID_VERSION = JsMetadataVersion()
|
||||
|
||||
fun fromInteger(version: Int): JsMetadataVersion =
|
||||
JsMetadataVersion(version and 255, (version shr 8) and 255, version shr 16)
|
||||
|
||||
fun readFrom(stream: InputStream): JsMetadataVersion {
|
||||
val dataInput = DataInputStream(stream)
|
||||
val size = dataInput.readInt()
|
||||
|
||||
// We assume here that the version will always have 3 components. This is needed to prevent reading an unpredictable amount
|
||||
// of integers from old .kjsm files (pre-1.1) because they did not have the version in the beginning
|
||||
if (size != INSTANCE.toArray().size) return INVALID_VERSION
|
||||
|
||||
return JsMetadataVersion(*(1..size).map { dataInput.readInt() }.toIntArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object KotlinJavascriptMetadataUtils {
|
||||
const val JS_EXT: String = ".js"
|
||||
const val META_JS_SUFFIX: String = ".meta.js"
|
||||
const val JS_MAP_EXT: String = ".js.map"
|
||||
private val KOTLIN_JAVASCRIPT_METHOD_NAME = "kotlin_module_metadata"
|
||||
private val KOTLIN_JAVASCRIPT_METHOD_NAME_PATTERN = "\\.kotlin_module_metadata\\(".toPattern()
|
||||
|
||||
/**
|
||||
* Matches string like <name>.kotlin_module_metadata(<abi version>, <module name>, <base64 data>)
|
||||
*/
|
||||
private val METADATA_PATTERN = "(?m)\\w+\\.$KOTLIN_JAVASCRIPT_METHOD_NAME\\((\\d+),\\s*(['\"])([^'\"]*)\\2,\\s*(['\"])([^'\"]*)\\4\\)".toPattern()
|
||||
|
||||
fun replaceSuffix(filePath: String): String = filePath.substringBeforeLast(JS_EXT) + META_JS_SUFFIX
|
||||
|
||||
@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, metadataVersion: JsMetadataVersion): String =
|
||||
"// Kotlin.$KOTLIN_JAVASCRIPT_METHOD_NAME(${metadataVersion.toInteger()}, \"$moduleName\", " +
|
||||
"\"${Base64.getEncoder().encodeToString(content)}\");\n"
|
||||
|
||||
@JvmStatic
|
||||
fun loadMetadata(file: File): List<KotlinJavascriptMetadata> {
|
||||
assert(file.exists()) { "Library $file not found" }
|
||||
val metadataList = arrayListOf<KotlinJavascriptMetadata>()
|
||||
JsLibraryUtils.traverseJsLibrary(file) { library ->
|
||||
parseMetadata(library.content, metadataList)
|
||||
}
|
||||
|
||||
return metadataList
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun loadMetadata(path: String): List<KotlinJavascriptMetadata> = loadMetadata(File(path))
|
||||
|
||||
@JvmStatic
|
||||
fun parseMetadata(text: CharSequence, metadataList: MutableList<KotlinJavascriptMetadata>) {
|
||||
// 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 = JsMetadataVersion.fromInteger(matcher.group(1).toInt())
|
||||
val moduleName = matcher.group(3)
|
||||
val data = matcher.group(5)
|
||||
metadataList.add(KotlinJavascriptMetadata(abiVersion, moduleName, Base64.getDecoder().decode(data)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,12 @@
|
||||
package org.jetbrains.kotlin.utils
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import java.util.jar.Attributes
|
||||
import java.util.jar.JarFile
|
||||
import java.util.jar.Manifest
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
object LibraryUtils {
|
||||
private val LOG = Logger.getInstance(LibraryUtils::class.java)
|
||||
@@ -60,38 +58,10 @@ object LibraryUtils {
|
||||
return classesRoots.firstOrNull { it.name == jarName }
|
||||
}
|
||||
|
||||
@Suppress("unused") // used in K2JSCompilerMojo
|
||||
@JvmStatic fun isKotlinJavascriptLibrary(library: File): Boolean = KotlinJavascriptMetadataUtils.loadMetadata(library).isNotEmpty()
|
||||
|
||||
@JvmStatic fun isKotlinJavascriptStdLibrary(library: File): Boolean {
|
||||
return checkAttributeValue(library, TITLE_KOTLIN_JAVASCRIPT_STDLIB, Attributes.Name.IMPLEMENTATION_TITLE)
|
||||
}
|
||||
|
||||
private fun isZippedKlibInZip(candidate: File): Boolean {
|
||||
var manifestFound = false
|
||||
var irFound = false
|
||||
for (entry in ZipFile(candidate).entries()) {
|
||||
if (entry.name == "manifest") manifestFound = true
|
||||
if (entry.name == "ir/") irFound = true
|
||||
}
|
||||
return manifestFound && irFound
|
||||
}
|
||||
|
||||
private fun isZippedKlib(candidate: File): Boolean {
|
||||
return candidate.extension == "klib"
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isKotlinJavascriptIrLibrary(candidate: File): Boolean {
|
||||
return when {
|
||||
isZippedKlib(candidate) -> true
|
||||
FileUtil.isJarOrZip(candidate) -> isZippedKlibInZip(candidate)
|
||||
!File(candidate, "manifest").isFile -> false
|
||||
!File(candidate, "ir").isDirectory -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun getManifestFromJar(library: File): Manifest? {
|
||||
if (!library.canRead()) return null
|
||||
|
||||
|
||||
Reference in New Issue
Block a user