Detect K/N library kind if library is a ZIP file (native-specific part)

Issue #KT-26730 Fixed
This commit is contained in:
Dmitriy Dolovov
2018-09-11 10:48:36 +03:00
parent 0d0929922a
commit 324ec7aff8
5 changed files with 98 additions and 56 deletions
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.ide.konan
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFileFactory import com.intellij.psi.PsiFileFactory
import com.intellij.psi.SingleRootFileViewProvider import com.intellij.psi.SingleRootFileViewProvider
@@ -25,6 +25,8 @@ import org.jetbrains.kotlin.idea.decompiler.textBuilder.LoggingErrorReporter
import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.KonanLibrary import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
import org.jetbrains.kotlin.konan.library.KonanLibrarySource.KonanLibraryDir
import org.jetbrains.kotlin.konan.library.KonanLibrarySource.KonanLibraryFile
import org.jetbrains.kotlin.konan.library.MetadataReader import org.jetbrains.kotlin.konan.library.MetadataReader
import org.jetbrains.kotlin.konan.library.createKonanLibrary import org.jetbrains.kotlin.konan.library.createKonanLibrary
import org.jetbrains.kotlin.konan.util.KonanFactories.DefaultPackageFragmentsFactory import org.jetbrains.kotlin.konan.util.KonanFactories.DefaultPackageFragmentsFactory
@@ -98,16 +100,31 @@ private fun KonanLibrary.createPackageFragmentProvider(
internal object CachingIdeMetadataReaderImpl : MetadataReader { internal object CachingIdeMetadataReaderImpl : MetadataReader {
override fun loadSerializedModule(libraryLayout: KonanLibraryLayout): KonanProtoBuf.LinkDataLibrary = override fun loadSerializedModule(libraryLayout: KonanLibraryLayout): KonanProtoBuf.LinkDataLibrary =
cache.getCachedModuleHeader(libraryLayout.moduleHeaderFile.virtualFile) cache.getCachedModuleHeader(libraryLayout.getVirtualFile(libraryLayout.moduleHeaderFile))
override fun loadSerializedPackageFragment( override fun loadSerializedPackageFragment(
libraryLayout: KonanLibraryLayout, libraryLayout: KonanLibraryLayout,
packageFqName: String packageFqName: String
): KonanProtoBuf.LinkDataPackageFragment = ): KonanProtoBuf.LinkDataPackageFragment =
cache.getCachedPackageFragment(libraryLayout.packageFragmentFile(packageFqName).virtualFile) cache.getCachedPackageFragment(libraryLayout.getVirtualFile(libraryLayout.packageFragmentFile(packageFqName)))
private val File.virtualFile: VirtualFile private fun KonanLibraryLayout.getVirtualFile(file: File): VirtualFile {
get() = LocalFileSystem.getInstance().refreshAndFindFileByPath(path) ?: error("File not found: $path") val source = this.source
return when (source) {
is KonanLibraryFile -> asJarFileSystemFile(source.klibFile, file)
is KonanLibraryDir -> asLocalFile(file)
}
}
private fun asJarFileSystemFile(jarFile: File, localFile: File): VirtualFile {
val fullPath = jarFile.absolutePath + "!" + localFile.absolutePath
return StandardFileSystems.jar().findFileByPath(fullPath) ?: error("File not found: $fullPath")
}
private fun asLocalFile(localFile: File): VirtualFile {
val fullPath = localFile.absolutePath
return StandardFileSystems.local().findFileByPath(fullPath) ?: error("File not found: $fullPath")
}
private val cache private val cache
get() = KotlinNativeLoadingMetadataCache.getInstance() get() = KotlinNativeLoadingMetadataCache.getInstance()
@@ -9,6 +9,8 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ApplicationComponent import com.intellij.openapi.components.ApplicationComponent
import com.intellij.openapi.vfs.* import com.intellij.openapi.vfs.*
import com.intellij.util.containers.ContainerUtil.createConcurrentSoftValueMap import com.intellij.util.containers.ContainerUtil.createConcurrentSoftValueMap
import org.jetbrains.kotlin.konan.library.KLIB_METADATA_FILE_EXTENSION
import org.jetbrains.kotlin.konan.library.KLIB_MODULE_METADATA_FILE_NAME
import org.jetbrains.kotlin.metadata.konan.KonanProtoBuf import org.jetbrains.kotlin.metadata.konan.KonanProtoBuf
import org.jetbrains.kotlin.serialization.konan.parseModuleHeader import org.jetbrains.kotlin.serialization.konan.parseModuleHeader
import org.jetbrains.kotlin.serialization.konan.parsePackageFragment import org.jetbrains.kotlin.serialization.konan.parsePackageFragment
@@ -25,12 +27,12 @@ class KotlinNativeLoadingMetadataCache : ApplicationComponent {
private val moduleHeaderCache = createConcurrentSoftValueMap<VirtualFile, KonanProtoBuf.LinkDataLibrary>() private val moduleHeaderCache = createConcurrentSoftValueMap<VirtualFile, KonanProtoBuf.LinkDataLibrary>()
fun getCachedPackageFragment(virtualFile: VirtualFile): KonanProtoBuf.LinkDataPackageFragment = fun getCachedPackageFragment(virtualFile: VirtualFile): KonanProtoBuf.LinkDataPackageFragment =
packageFragmentCache.computeIfAbsent(virtualFile) { packageFragmentCache.computeIfAbsent(virtualFile.surePackageMetadataFile) {
parsePackageFragment(virtualFile.contentsToByteArray(false)) parsePackageFragment(virtualFile.contentsToByteArray(false))
} }
fun getCachedModuleHeader(virtualFile: VirtualFile): KonanProtoBuf.LinkDataLibrary = fun getCachedModuleHeader(virtualFile: VirtualFile): KonanProtoBuf.LinkDataLibrary =
moduleHeaderCache.computeIfAbsent(virtualFile) { moduleHeaderCache.computeIfAbsent(virtualFile.sureModuleHeaderFile) {
parseModuleHeader(virtualFile.contentsToByteArray(false)) parseModuleHeader(virtualFile.contentsToByteArray(false))
} }
@@ -45,7 +47,21 @@ class KotlinNativeLoadingMetadataCache : ApplicationComponent {
} }
private fun invalidateCaches(virtualFile: VirtualFile) { private fun invalidateCaches(virtualFile: VirtualFile) {
packageFragmentCache.remove(virtualFile) when {
moduleHeaderCache.remove(virtualFile) virtualFile.isPackageMetadataFile -> packageFragmentCache.remove(virtualFile)
virtualFile.isModuleHeaderFile -> moduleHeaderCache.remove(virtualFile)
}
} }
private val VirtualFile.surePackageMetadataFile
get() = if (isPackageMetadataFile) this else error("Not a package metadata file: $this")
private val VirtualFile.isPackageMetadataFile
get() = extension == KLIB_METADATA_FILE_EXTENSION
private val VirtualFile.sureModuleHeaderFile
get() = if (isModuleHeaderFile) this else error("Not a module header file: $this")
private val VirtualFile.isModuleHeaderFile
get() = name == KLIB_MODULE_METADATA_FILE_NAME
} }
@@ -14,6 +14,8 @@ const val KLIB_FILE_EXTENSION_WITH_DOT = ".$KLIB_FILE_EXTENSION"
const val KLIB_METADATA_FILE_EXTENSION = "knm" const val KLIB_METADATA_FILE_EXTENSION = "knm"
const val KLIB_METADATA_FILE_EXTENSION_WITH_DOT = ".$KLIB_METADATA_FILE_EXTENSION" const val KLIB_METADATA_FILE_EXTENSION_WITH_DOT = ".$KLIB_METADATA_FILE_EXTENSION"
const val KLIB_MODULE_METADATA_FILE_NAME = "module"
const val KONAN_STDLIB_NAME = "stdlib" const val KONAN_STDLIB_NAME = "stdlib"
val KONAN_COMMON_LIBS_PATH: Path val KONAN_COMMON_LIBS_PATH: Path
@@ -14,36 +14,35 @@ import org.jetbrains.kotlin.konan.target.KonanTarget
interface KonanLibraryLayout { interface KonanLibraryLayout {
val libraryName: String val libraryName: String
val libDir: File val libDir: File
val target: KonanTarget?
// This is a default implementation. Can't make it an assignment.
get() = null
val manifestFile val source: KonanLibrarySource
get() = File(libDir, "manifest")
val resourcesDir
get() = File(libDir, "resources")
val targetsDir // This is a default implementation. Can't make it an assignment.
get() = File(libDir, "targets") val target: KonanTarget? get() = null
val targetDir
get() = File(targetsDir, target!!.visibleName)
val kotlinDir val manifestFile get() = File(libDir, "manifest")
get() = File(targetDir, "kotlin") val resourcesDir get() = File(libDir, "resources")
val nativeDir
get() = File(targetDir, "native")
val includedDir
get() = File(targetDir, "included")
val linkdataDir val targetsDir get() = File(libDir, "targets")
get() = File(libDir, "linkdata") val targetDir get() = File(targetsDir, target!!.visibleName)
val moduleHeaderFile
get() = File(linkdataDir, "module") val kotlinDir get() = File(targetDir, "kotlin")
val dataFlowGraphFile val nativeDir get() = File(targetDir, "native")
get() = File(linkdataDir, "module_data_flow_graph") val includedDir get() = File(targetDir, "included")
val linkdataDir get() = File(libDir, "linkdata")
val moduleHeaderFile get() = File(linkdataDir, KLIB_MODULE_METADATA_FILE_NAME)
val dataFlowGraphFile get() = File(linkdataDir, "module_data_flow_graph")
fun packageFragmentFile(packageFqName: String) = fun packageFragmentFile(packageFqName: String) =
File(linkdataDir, if (packageFqName == "") "root_package.knm" else "package_$packageFqName.knm") File(
linkdataDir,
if (packageFqName == "") "root_package.$KLIB_METADATA_FILE_EXTENSION" else "package_$packageFqName.$KLIB_METADATA_FILE_EXTENSION"
)
}
sealed class KonanLibrarySource {
object KonanLibraryDir : KonanLibrarySource()
class KonanLibraryFile(val klibFile: File) : KonanLibrarySource()
} }
@@ -9,6 +9,8 @@ import org.jetbrains.kotlin.konan.file.*
import org.jetbrains.kotlin.konan.library.KLIB_FILE_EXTENSION import org.jetbrains.kotlin.konan.library.KLIB_FILE_EXTENSION
import org.jetbrains.kotlin.konan.library.KLIB_FILE_EXTENSION_WITH_DOT import org.jetbrains.kotlin.konan.library.KLIB_FILE_EXTENSION_WITH_DOT
import org.jetbrains.kotlin.konan.library.KonanLibraryLayout import org.jetbrains.kotlin.konan.library.KonanLibraryLayout
import org.jetbrains.kotlin.konan.library.KonanLibrarySource.KonanLibraryDir
import org.jetbrains.kotlin.konan.library.KonanLibrarySource.KonanLibraryFile
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import java.nio.file.FileSystem import java.nio.file.FileSystem
@@ -17,24 +19,23 @@ interface KonanLibraryLayoutImpl : KonanLibraryLayout {
fun <T> realFiles(action: (KonanLibraryLayout) -> T): T fun <T> realFiles(action: (KonanLibraryLayout) -> T): T
} }
private class ZippedKonanLibraryLayout(val klibFile: File, override val target: KonanTarget?) : KonanLibraryLayoutImpl { private class ZippedKonanLibraryLayout(
klibFile: File,
override val target: KonanTarget?
) : KonanLibraryLayoutImpl {
init { init {
zippedKonanLibraryChecks(klibFile) zippedKonanLibraryChecks(klibFile)
} }
override val libraryName = klibFile.path.removeSuffix(KLIB_FILE_EXTENSION_WITH_DOT) override val libraryName = klibFile.path.removeSuffix(KLIB_FILE_EXTENSION_WITH_DOT)
override val libDir: File = File("/") override val libDir: File = File("/")
override val source = KonanLibraryFile(klibFile)
override fun <T> realFiles(action: (KonanLibraryLayout) -> T): T { override fun <T> realFiles(action: (KonanLibraryLayout) -> T) = action(FileExtractor(this))
return action(FileExtractor(this))!!
}
override fun <T> inPlace(action: (KonanLibraryLayout) -> T): T { override fun <T> inPlace(action: (KonanLibraryLayout) -> T) = source.klibFile.withZipFileSystem { zipFileSystem ->
return klibFile.withZipFileSystem { zipFileSystem -> action(DirectFromZip(this, zipFileSystem))
action(DirectFromZip(this, zipFileSystem))
}
} }
} }
@@ -46,44 +47,51 @@ internal fun zippedKonanLibraryChecks(klibFile: File) {
check(extension.isEmpty() || extension == KLIB_FILE_EXTENSION) { "Unexpected file extension: $extension" } check(extension.isEmpty() || extension == KLIB_FILE_EXTENSION) { "Unexpected file extension: $extension" }
} }
private class UnzippedKonanLibraryLayout(override val libDir: File, override val target: KonanTarget?) : KonanLibraryLayoutImpl { private class UnzippedKonanLibraryLayout(
override val libraryName = libDir.path override val libDir: File,
override val target: KonanTarget?
) : KonanLibraryLayoutImpl {
override fun <T> inPlace(action: (KonanLibraryLayout) -> T): T = action(this) override val libraryName = libDir.path
override fun <T> realFiles(action: (KonanLibraryLayout) -> T): T = inPlace(action) override val source = KonanLibraryDir
override fun <T> inPlace(action: (KonanLibraryLayout) -> T) = action(this)
override fun <T> realFiles(action: (KonanLibraryLayout) -> T) = inPlace(action)
} }
private class DirectFromZip(zippedLayout: ZippedKonanLibraryLayout, val zipFileSystem: FileSystem) : KonanLibraryLayout { private class DirectFromZip(
zippedLayout: ZippedKonanLibraryLayout,
zipFileSystem: FileSystem
) : KonanLibraryLayout {
override val libraryName = zippedLayout.libraryName override val libraryName = zippedLayout.libraryName
override val libDir = zipFileSystem.file(zippedLayout.libDir) override val libDir = zipFileSystem.file(zippedLayout.libDir)
override val source = zippedLayout.source
} }
/** /**
* This class automatically extracts pieces of the library on first access. Use it if you need * This class automatically extracts pieces of the library on first access. Use it if you need
* to pass extracted files to an external tool. Otherwise, stick to [DirectFromZip]. * to pass extracted files to an external tool. Otherwise, stick to [DirectFromZip].
*/ */
private class FileExtractor(val zippedLibraryLayout: ZippedKonanLibraryLayout) : KonanLibraryLayout by zippedLibraryLayout { private class FileExtractor(
val zippedLayout: ZippedKonanLibraryLayout
) : KonanLibraryLayout by zippedLayout {
override val manifestFile: File by lazy { extract(super.manifestFile) } override val manifestFile: File by lazy { extract(super.manifestFile) }
override val resourcesDir: File by lazy { extractDir(super.resourcesDir) } override val resourcesDir: File by lazy { extractDir(super.resourcesDir) }
override val includedDir: File by lazy { extractDir(super.includedDir) } override val includedDir: File by lazy { extractDir(super.includedDir) }
override val kotlinDir: File by lazy { extractDir(super.kotlinDir) } override val kotlinDir: File by lazy { extractDir(super.kotlinDir) }
override val nativeDir: File by lazy { extractDir(super.nativeDir) } override val nativeDir: File by lazy { extractDir(super.nativeDir) }
override val linkdataDir: File by lazy { extractDir(super.linkdataDir) } override val linkdataDir: File by lazy { extractDir(super.linkdataDir) }
fun extract(file: File): File = zippedLibraryLayout.klibFile.withZipFileSystem { zipFileSystem -> fun extract(file: File): File = zippedLayout.source.klibFile.withZipFileSystem { zipFileSystem ->
val temporary = createTempFile(file.name) val temporary = createTempFile(file.name)
zipFileSystem.file(file).copyTo(temporary) zipFileSystem.file(file).copyTo(temporary)
temporary.deleteOnExit() temporary.deleteOnExit()
temporary temporary
} }
fun extractDir(directory: File): File = zippedLibraryLayout.klibFile.withZipFileSystem { zipFileSystem -> fun extractDir(directory: File): File = zippedLayout.source.klibFile.withZipFileSystem { zipFileSystem ->
val temporary = createTempDir(directory.name) val temporary = createTempDir(directory.name)
zipFileSystem.file(directory).recursiveCopyTo(temporary) zipFileSystem.file(directory).recursiveCopyTo(temporary)
temporary.deleteOnExitRecursively() temporary.deleteOnExitRecursively()