From 7ddbd8ca80f13b18f08fa2e85308c8fc778ed079 Mon Sep 17 00:00:00 2001 From: Alexander Gorshenev Date: Fri, 31 May 2019 16:51:52 +0300 Subject: [PATCH] Moved the common klib reader functionality from Kotlin/Native to util-klib --- compiler/util-klib/build.gradle.kts | 20 +++ .../kotlin/library/KonanLibraryVersioning.kt | 29 ++++ .../kotlin/library/KotlinAbiVersion.kt | 28 ++++ .../jetbrains/kotlin/library/KotlinLibrary.kt | 51 ++++++ .../kotlin/library/KotlinLibraryLayout.kt | 57 +++++++ .../kotlin/library/KotlinLibraryUtils.kt | 39 +++++ .../kotlin/library/UnresolvedLibrary.kt | 10 ++ .../library/impl/CombinedIrFileAccessor.kt | 94 ++++++++++++ .../kotlin/library/impl/KotlinLibraryImpl.kt | 133 ++++++++++++++++ .../library/impl/KotlinLibraryLayoutImpl.kt | 145 ++++++++++++++++++ 10 files changed, 606 insertions(+) create mode 100644 compiler/util-klib/build.gradle.kts create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/KonanLibraryVersioning.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinAbiVersion.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibrary.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryLayout.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryUtils.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/UnresolvedLibrary.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/impl/CombinedIrFileAccessor.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryImpl.kt create mode 100644 compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryLayoutImpl.kt diff --git a/compiler/util-klib/build.gradle.kts b/compiler/util-klib/build.gradle.kts new file mode 100644 index 00000000000..eff4b593822 --- /dev/null +++ b/compiler/util-klib/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +description = "Common klib reader and writer" + +dependencies { + compile(kotlinStdlib()) + compile(project(":kotlin-util-io")) +} + +sourceSets { + "main" { projectDefault() } + "test" { none() } +} + +publish() + +standardPublicJars() \ No newline at end of file diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/KonanLibraryVersioning.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/KonanLibraryVersioning.kt new file mode 100644 index 00000000000..961192e1d64 --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/KonanLibraryVersioning.kt @@ -0,0 +1,29 @@ +package org.jetbrains.kotlin.library + +import org.jetbrains.kotlin.konan.parseKonanVersion +import org.jetbrains.kotlin.konan.properties.Properties +import org.jetbrains.kotlin.konan.KonanVersion + +data class KonanLibraryVersioning( + val libraryVersion: String?, + val compilerVersion: KonanVersion?, + val abiVersion: KotlinAbiVersion? +) + +fun Properties.writeKonanLibraryVersioning(versions: KonanLibraryVersioning) { + versions.abiVersion ?. let { this.setProperty(KLIB_PROPERTY_ABI_VERSION, it.toString()) } + versions.libraryVersion ?. let { this.setProperty(KLIB_PROPERTY_LIBRARY_VERSION, it) } + versions.compilerVersion ?. let { this.setProperty(KLIB_PROPERTY_COMPILER_VERSION, "${versions.compilerVersion.toString(true, true)}") } +} + +fun Properties.readKonanLibraryVersioning(): KonanLibraryVersioning { + val abiVersion = this.getProperty(KLIB_PROPERTY_ABI_VERSION)?.parseKonanAbiVersion() + val libraryVersion = this.getProperty(KLIB_PROPERTY_LIBRARY_VERSION) + val compilerVersion = this.getProperty(KLIB_PROPERTY_COMPILER_VERSION)?.parseKonanVersion() + + return KonanLibraryVersioning( + abiVersion = abiVersion, + libraryVersion = libraryVersion, + compilerVersion = compilerVersion + ) +} diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinAbiVersion.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinAbiVersion.kt new file mode 100644 index 00000000000..a3a097354ca --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinAbiVersion.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2010-2019 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.library + +fun String.parseKonanAbiVersion(): KotlinAbiVersion { + return KotlinAbiVersion(this.toInt()) +} + +data class KotlinAbiVersion(val version: Int) { + companion object { + val CURRENT = KotlinAbiVersion(11) + } + override fun toString() = "$version" +} diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibrary.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibrary.kt new file mode 100644 index 00000000000..6d151f0ed45 --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibrary.kt @@ -0,0 +1,51 @@ +package org.jetbrains.kotlin.library + +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.konan.properties.Properties +import org.jetbrains.kotlin.konan.properties.propertyList + +const val KLIB_PROPERTY_ABI_VERSION = "abi_version" +const val KLIB_PROPERTY_COMPILER_VERSION = "compiler_version" +const val KLIB_PROPERTY_DEPENDENCY_VERSION = "dependency_version" +const val KLIB_PROPERTY_LIBRARY_VERSION = "library_version" +const val KLIB_PROPERTY_UNIQUE_NAME = "unique_name" +const val KLIB_PROPERTY_DEPENDS = "depends" +const val KLIB_PROPERTY_PACKAGE = "package" +/** + * Abstractions for getting access to the information stored inside of Kotlin/Native library. + */ + +interface BaseKotlinLibrary { + val libraryName: String + val libraryFile: File + val versions: KonanLibraryVersioning + // Whether this library is default (provided by distribution)? + val isDefault: Boolean + val manifestProperties: Properties +} + +interface MetadataLibrary { + val moduleHeaderData: ByteArray + fun packageMetadataParts(fqName: String): Set + fun packageMetadata(fqName: String, partName: String): ByteArray +} + +interface IrLibrary { + val dataFlowGraph: ByteArray? + val irHeader: ByteArray? + fun irDeclaration(index: Long, isLocal: Boolean): ByteArray +} + +val BaseKotlinLibrary.uniqueName: String + get() = manifestProperties.getProperty(KLIB_PROPERTY_UNIQUE_NAME)!! + +val BaseKotlinLibrary.unresolvedDependencies: List + get() = manifestProperties.propertyList(KLIB_PROPERTY_DEPENDS, escapeInQuotes = true) + .map { + UnresolvedLibrary(it, manifestProperties.getProperty("dependency_version_$it")) + } + +interface BackendLibrary : BaseKotlinLibrary, MetadataLibrary, IrLibrary + + +// typealias JsLibrary = BackendLibrary? diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryLayout.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryLayout.kt new file mode 100644 index 00000000000..9a546d7fd87 --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryLayout.kt @@ -0,0 +1,57 @@ +/** + * Copyright 2010-2019 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.library + +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.library.KLIB_METADATA_FILE_EXTENSION_WITH_DOT + +/** + * This scheme describes the Kotlin/Native Library (KLIB) layout. + */ +interface KotlinLibraryLayout { + val libDir: File + val libraryName: String + get() = libDir.path + val manifestFile + get() = File(libDir, "manifest") + val resourcesDir + get() = File(libDir, "resources") +} + +interface MetadataKotlinLibraryLayout : KotlinLibraryLayout { + val metadataDir + get() = File(libDir, "linkdata") + val moduleHeaderFile + get() = File(metadataDir, "module") + + fun packageFragmentsDir(packageName: String) = + File(metadataDir, if (packageName == "") "root_package" else "package_$packageName") + + fun packageFragmentFile(packageFqName: String, partName: String) = + File(packageFragmentsDir(packageFqName), "$partName$KLIB_METADATA_FILE_EXTENSION_WITH_DOT") +} + +interface IrKotlinLibraryLayout : KotlinLibraryLayout { + val irDir + get() = File(libDir, "ir") + val irFile + get() = File(irDir, "irCombined.knd") + val irHeader + get() = File(irDir, "irHeaders.kni") + val dataFlowGraphFile + get() = File(irDir, "module_data_flow_graph") +} diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryUtils.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryUtils.kt new file mode 100644 index 00000000000..3202b529756 --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/KotlinLibraryUtils.kt @@ -0,0 +1,39 @@ +package org.jetbrains.kotlin.library + +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.konan.file.file +import org.jetbrains.kotlin.konan.file.withMutableZipFileSystem +import org.jetbrains.kotlin.library.impl.zippedKotlinLibraryChecks + +const val KLIB_FILE_EXTENSION = "klib" +const val KLIB_FILE_EXTENSION_WITH_DOT = ".$KLIB_FILE_EXTENSION" + +const val KLIB_METADATA_FILE_EXTENSION = "knm" +const val KLIB_METADATA_FILE_EXTENSION_WITH_DOT = ".$KLIB_METADATA_FILE_EXTENSION" + +fun File.unpackZippedKonanLibraryTo(newDir: File) { + + // First, run validity checks for the given KLIB file. + zippedKotlinLibraryChecks(this) + + if (newDir.exists) { + if (newDir.isDirectory) + newDir.deleteRecursively() + else + newDir.delete() + } + + this.withMutableZipFileSystem { + it.file("/").recursiveCopyTo(newDir) + } + check(newDir.exists) { "Could not unpack $this as $newDir." } +} + +val List.toUnresolvedLibraries + get() = this.map { + + val version = it.substringAfterLast('@', "") + .let { if (it.isEmpty()) null else it } + val name = it.substringBeforeLast('@') + UnresolvedLibrary(name, version) + } diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/UnresolvedLibrary.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/UnresolvedLibrary.kt new file mode 100644 index 00000000000..f24067f26cc --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/UnresolvedLibrary.kt @@ -0,0 +1,10 @@ +package org.jetbrains.kotlin.library + +data class UnresolvedLibrary( + val path: String, + val libraryVersion: String?) { + + fun substitutePath(newPath: String): UnresolvedLibrary { + return UnresolvedLibrary(newPath, libraryVersion) + } +} \ No newline at end of file diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/CombinedIrFileAccessor.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/CombinedIrFileAccessor.kt new file mode 100644 index 00000000000..645b4a2eeb4 --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/CombinedIrFileAccessor.kt @@ -0,0 +1,94 @@ +/** + * Copyright 2010-2019 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.library.impl + +import org.jetbrains.kotlin.konan.file.File +import java.io.RandomAccessFile +import java.nio.channels.FileChannel + +data class DeclarationId(val id: Long, val isLocal: Boolean) + +class CombinedIrFileReader(file: File) { + private val buffer = file.map(FileChannel.MapMode.READ_ONLY) + private val declarationToOffsetSize = mutableMapOf>() + + init { + val declarationsCount = buffer.int + for (i in 0 until declarationsCount) { + val id = buffer.long + val isLocal = buffer.int != 0 + val offset = buffer.int + val size = buffer.int + declarationToOffsetSize[DeclarationId(id, isLocal)] = offset to size + } + } + + fun declarationBytes(id: DeclarationId): ByteArray { + val offsetSize = declarationToOffsetSize[id] ?: throw Error("No declaration with $id here") + val result = ByteArray(offsetSize.second) + buffer.position(offsetSize.first) + buffer.get(result, 0, offsetSize.second) + return result + } +} + +private const val SINGLE_INDEX_RECORD_SIZE = 20 // sizeof(Long) + 3 * sizeof(Int). +private const val INDEX_HEADER_SIZE = 4 // sizeof(Int). + +class CombinedIrFileWriter(val declarationCount: Int) { + private var currentDeclaration = 0 + private var currentPosition = 0 + private val file = org.jetbrains.kotlin.konan.file.createTempFile("ir").deleteOnExit() + private val randomAccessFile = RandomAccessFile(file.path, "rw") + + init { + randomAccessFile.writeInt(declarationCount) + assert(randomAccessFile.filePointer.toInt() == INDEX_HEADER_SIZE) + for (i in 0 until declarationCount) { + randomAccessFile.writeLong(-1) // id + randomAccessFile.writeInt(-1) // isLocal + randomAccessFile.writeInt(-1) // offset + randomAccessFile.writeInt(-1) // size + } + currentPosition = randomAccessFile.filePointer.toInt() + assert(currentPosition == INDEX_HEADER_SIZE + SINGLE_INDEX_RECORD_SIZE * declarationCount) + } + + fun skipDeclaration() { + currentDeclaration++ + } + + fun addDeclaration(id: DeclarationId, bytes: ByteArray) { + randomAccessFile.seek((currentDeclaration * SINGLE_INDEX_RECORD_SIZE + INDEX_HEADER_SIZE).toLong()) + randomAccessFile.writeLong(id.id) + randomAccessFile.writeInt(if (id.isLocal) 1 else 0) + randomAccessFile.writeInt(currentPosition) + randomAccessFile.writeInt(bytes.size) + randomAccessFile.seek(currentPosition.toLong()) + randomAccessFile.write(bytes) + assert(randomAccessFile.filePointer < Int.MAX_VALUE.toLong()) + currentPosition = randomAccessFile.filePointer.toInt() + currentDeclaration++ + } + + fun finishWriting(): File { + assert(currentDeclaration == declarationCount) + randomAccessFile.close() + return file + } +} + diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryImpl.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryImpl.kt new file mode 100644 index 00000000000..abd0df77e41 --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryImpl.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2018 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.library.impl + +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.library.* +import org.jetbrains.kotlin.library.impl.* +import org.jetbrains.kotlin.konan.properties.Properties +import org.jetbrains.kotlin.konan.properties.loadProperties +import org.jetbrains.kotlin.konan.properties.propertyList + +open class BaseKotlinLibraryImpl( + private val access: BaseLibraryAccess, + override val isDefault: Boolean +) : BaseKotlinLibrary { + override val libraryFile get() = access.klib + override val libraryName: String by lazy { access.inPlace { it.libraryName } } + + override fun toString() = "$libraryName[default=$isDefault]" + + override val manifestProperties: Properties by lazy { + access.inPlace { it.manifestFile.loadProperties() } + } + + override val versions: KonanLibraryVersioning by lazy { + manifestProperties.readKonanLibraryVersioning() + } +} + +open class MetadataLibraryImpl( + private val access: MetadataLibraryAccess +) : MetadataLibrary { + + override val moduleHeaderData: ByteArray by lazy { + access.inPlace { + it.moduleHeaderFile.readBytes() + } + } + + override fun packageMetadata(fqName: String, partName: String): ByteArray = + access.inPlace { + it.packageFragmentFile(fqName, partName).readBytes() + } + + override fun packageMetadataParts(fqName: String): Set = + access.inPlace { inPlaceaccess -> + val fileList = + inPlaceaccess.packageFragmentsDir(fqName) + .listFiles + .mapNotNull { + it.name + .substringBeforeLast(KLIB_METADATA_FILE_EXTENSION_WITH_DOT, missingDelimiterValue = "") + .takeIf { it.isNotEmpty() } + } + + fileList.toSortedSet().also { + require(it.size == fileList.size) { "Duplicated names: ${fileList.groupingBy { it }.eachCount().filter { (_, count) -> count > 1 }}" } + } + } +} + +open class IrLibraryImpl( + private val access: IrLibraryAccess +) : IrLibrary { + + override val irHeader: ByteArray? by lazy { + access.inPlace { library: IrKotlinLibraryLayout -> + library.irHeader.let { + if (it.exists) loadIrHeader() else null + } + } + } + + override fun irDeclaration(index: Long, isLocal: Boolean) = loadIrDeclaraton(index, isLocal) + + private val combinedDeclarations: CombinedIrFileReader by lazy { + CombinedIrFileReader(access.realFiles { + it.irFile + }) + } + + private fun loadIrHeader(): ByteArray = + access.inPlace { + it.irHeader.readBytes() + } + + private fun loadIrDeclaraton(index: Long, isLocal: Boolean) = + combinedDeclarations.declarationBytes(DeclarationId(index, isLocal)) + + override val dataFlowGraph by lazy { + access.inPlace { it: IrKotlinLibraryLayout -> + it.dataFlowGraphFile.let { if (it.exists) it.readBytes() else null } + } + } +} + +open class BackendLibraryImpl( + base: BaseKotlinLibraryImpl, + metadata: MetadataLibraryImpl, + ir: IrLibraryImpl +) : BackendLibrary, + BaseKotlinLibrary by base, + MetadataLibrary by metadata, + IrLibrary by ir + +fun createBackendLibrary( + libraryFile: File, + isDefault: Boolean = false +): BackendLibrary { + val baseAccess = BaseLibraryAccess(libraryFile) + val metadataAccess = MetadataLibraryAccess(libraryFile) + val irAccess = IrLibraryAccess(libraryFile) + + val base = BaseKotlinLibraryImpl(baseAccess, isDefault) + val metadata = MetadataLibraryImpl(metadataAccess) + val ir = IrLibraryImpl(irAccess) + + return BackendLibraryImpl(base, metadata, ir) +} \ No newline at end of file diff --git a/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryLayoutImpl.kt b/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryLayoutImpl.kt new file mode 100644 index 00000000000..10236ef1573 --- /dev/null +++ b/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryLayoutImpl.kt @@ -0,0 +1,145 @@ +package org.jetbrains.kotlin.library.impl + +import org.jetbrains.kotlin.konan.file.File +import org.jetbrains.kotlin.konan.file.file +import org.jetbrains.kotlin.konan.file.withZipFileSystem +import org.jetbrains.kotlin.library.* +import org.jetbrains.kotlin.konan.util.removeSuffixIfPresent +import java.nio.file.FileSystem + +open class KotlinLibraryLayoutImpl(val klib: File) : KotlinLibraryLayout { + val isZipped = klib.isFile + + init { + if (isZipped) zippedKotlinLibraryChecks(klib) + } + + override val libDir = if (isZipped) File("/") else klib + + override val libraryName + get() = + if (isZipped) + klib.path.removeSuffixIfPresent(KLIB_FILE_EXTENSION_WITH_DOT) + else + libDir.path + + open val extractingToTemp: KotlinLibraryLayout by lazy { + ExtractingBaseLibraryImpl(this) + } + + open fun directlyFromZip(zipFileSystem: FileSystem): KotlinLibraryLayout = + FromZipBaseLibraryImpl(this, zipFileSystem) + +} + +class MetadataLibraryLayoutImpl(klib: File) : KotlinLibraryLayoutImpl(klib), MetadataKotlinLibraryLayout { + + override val extractingToTemp: MetadataKotlinLibraryLayout by lazy { + ExtractingMetadataLibraryImpl(this) + } + + override fun directlyFromZip(zipFileSystem: FileSystem): MetadataKotlinLibraryLayout = + FromZipMetadataLibraryImpl(this, zipFileSystem) +} + +class IrLibraryLayoutImpl(klib: File) : KotlinLibraryLayoutImpl(klib), IrKotlinLibraryLayout { + + override val extractingToTemp: IrKotlinLibraryLayout by lazy { + ExtractingIrLibraryImpl(this) + } + + override fun directlyFromZip(zipFileSystem: FileSystem): IrKotlinLibraryLayout = + FromZipIrLibraryImpl(this, zipFileSystem) +} + +open class BaseLibraryAccess(val klib: File) { + open val layout = KotlinLibraryLayoutImpl(klib) + + fun realFiles(action: (L) -> T): T = + if (layout.isZipped) + action(layout.extractingToTemp as L) + else + action(layout as L) + + fun inPlace(action: (L) -> T): T = + if (layout.isZipped) + layout.klib.withZipFileSystem { zipFileSystem -> + action(layout.directlyFromZip(zipFileSystem) as L) + } + else + action(layout as L) +} + + +open class MetadataLibraryAccess(klib: File) : BaseLibraryAccess(klib) { + override val layout = MetadataLibraryLayoutImpl(klib) +} + +open class IrLibraryAccess(klib: File) : BaseLibraryAccess(klib) { + override val layout = IrLibraryLayoutImpl(klib) +} + +open class FromZipBaseLibraryImpl(zipped: KotlinLibraryLayoutImpl, zipFileSystem: FileSystem) : + KotlinLibraryLayout { + + override val libraryName = zipped.libraryName + override val libDir = zipFileSystem.file(zipped.libDir) +} + +class FromZipMetadataLibraryImpl(zipped: MetadataLibraryLayoutImpl, zipFileSystem: FileSystem) : + FromZipBaseLibraryImpl(zipped, zipFileSystem), MetadataKotlinLibraryLayout + +class FromZipIrLibraryImpl(zipped: IrLibraryLayoutImpl, zipFileSystem: FileSystem) : + FromZipBaseLibraryImpl(zipped, zipFileSystem), IrKotlinLibraryLayout + +/** + * This class and its children 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 [FromZipBaseLibraryImpl]. + */ +open class KotlinLibraryExtractor(private val zipped: KotlinLibraryLayoutImpl) { + fun extract(file: File): File = zipped.klib.withZipFileSystem { zipFileSystem -> + val temporary = org.jetbrains.kotlin.konan.file.createTempFile(file.name) + zipFileSystem.file(file).copyTo(temporary) + temporary.deleteOnExit() + temporary + } + + fun extractDir(directory: File): File = zipped.klib.withZipFileSystem { zipFileSystem -> + val temporary = org.jetbrains.kotlin.konan.file.createTempDir(directory.name) + zipFileSystem.file(directory).recursiveCopyTo(temporary) + temporary.deleteOnExitRecursively() + temporary + } +} + +open class ExtractingBaseLibraryImpl(zipped: KotlinLibraryLayoutImpl) : + KotlinLibraryExtractor(zipped), + KotlinLibraryLayout by zipped { + + override val manifestFile: File by lazy { extract(zipped.manifestFile) } + override val resourcesDir: File by lazy { extractDir(zipped.resourcesDir) } +} + +class ExtractingMetadataLibraryImpl(zipped: MetadataLibraryLayoutImpl) : + KotlinLibraryExtractor(zipped), + MetadataKotlinLibraryLayout by zipped { + + override val metadataDir: File by lazy { extractDir(zipped.metadataDir) } +} + +class ExtractingIrLibraryImpl(zipped: IrLibraryLayoutImpl) : KotlinLibraryExtractor(zipped), + IrKotlinLibraryLayout by zipped { + + override val irFile: File by lazy { extract(zipped.irFile) } +} + + +internal fun zippedKotlinLibraryChecks(klibFile: File) { + check(klibFile.exists) { "Could not find $klibFile." } + check(klibFile.isFile) { "Expected $klibFile to be a regular file." } + + val extension = klibFile.extension + check(extension.isEmpty() || extension == KLIB_FILE_EXTENSION) { + "KLIB path has unexpected extension: $klibFile" + } +} \ No newline at end of file