diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf364586..f00b8ee3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ androidSdkCommonVersion = "31.13.2" # We use current APIs (available in 8.13.2+) to ensure compatibility with # the widest range of AGP versions: from the minimum (8.3) # to the current (9.0+ at this time). -pluginKotlinVersion = "2.3.0" +pluginKotlinVersion = "2.3.20" pluginAndroidGradleVersion = "8.13.2" diff --git a/gradle/moko.versions.toml b/gradle/moko.versions.toml index 316dfd97..166aaf79 100644 --- a/gradle/moko.versions.toml +++ b/gradle/moko.versions.toml @@ -1,5 +1,5 @@ [versions] -resourcesVersion = "0.26.1" +resourcesVersion = "0.26.2" [libraries] resources = { module = "dev.icerock.moko:resources", version.ref = "resourcesVersion" } diff --git a/resources-generator/build.gradle.kts b/resources-generator/build.gradle.kts index bec1e64c..51d0627a 100644 --- a/resources-generator/build.gradle.kts +++ b/resources-generator/build.gradle.kts @@ -7,12 +7,12 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { - id("org.jetbrains.kotlin.jvm") version ("2.3.0") + id("org.jetbrains.kotlin.jvm") version ("2.3.20") id("detekt-convention") id("publication-convention") id("com.gradle.plugin-publish") version ("1.2.0") id("java-gradle-plugin") - kotlin("plugin.serialization") version ("2.3.0") + kotlin("plugin.serialization") version ("2.3.20") id("nexus-publication-convention") } diff --git a/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/apple/CopyResourcesFromKLibsAction.kt b/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/apple/CopyResourcesFromKLibsAction.kt index 86eb2753..1a9d1999 100644 --- a/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/apple/CopyResourcesFromKLibsAction.kt +++ b/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/apple/CopyResourcesFromKLibsAction.kt @@ -4,14 +4,12 @@ package dev.icerock.gradle.actions.apple -import dev.icerock.gradle.data.ExtractingBaseLibraryImpl +import dev.icerock.gradle.data.getKlibResourcesDir import dev.icerock.gradle.utils.klibs import org.gradle.api.Action import org.gradle.api.Task import org.gradle.api.logging.Logger import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink -import org.jetbrains.kotlin.library.KotlinLibraryLayout -import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl import java.io.File internal abstract class CopyResourcesFromKLibsAction : Action { @@ -68,21 +66,9 @@ internal abstract class CopyResourcesFromKLibsAction : Action { private fun getBundlesFromKotlinLibrary( klibFile: File ): List { - val layout: KotlinLibraryLayout = getKotlinLibraryLayout(klibFile) - return layout.resourcesDir.listFilesOrEmpty - .filter { it.isDirectory && it.extension == "bundle" } - .map { File(it.path) } - } - - private fun getKotlinLibraryLayout(file: File): KotlinLibraryLayout { - val klibKonan = org.jetbrains.kotlin.konan.file.File(file.path) - val klib = KotlinLibraryLayoutImpl(klib = klibKonan, component = "default") - - // while klib zipped we can't check resources directory, so we should unpack all klibs :( - // maybe will be better if we will write some state in cache as build result file with - // klib path, hash, resources count. to not extract klibs that we already know that not - // contains any resources. BUT maybe extraction will be faster then hashing for this logic. - // so this improvement should be checked in future - return if (klib.isZipped) ExtractingBaseLibraryImpl(klib) else klib + val resourcesDir: File = getKlibResourcesDir(klibFile) ?: return emptyList() + return resourcesDir.listFiles() + ?.filter { it.isDirectory && it.extension == "bundle" } + ?: emptyList() } } diff --git a/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/js/CopyResourcesToExecutableAction.kt b/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/js/CopyResourcesToExecutableAction.kt index 96371ffb..ab67387a 100644 --- a/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/js/CopyResourcesToExecutableAction.kt +++ b/resources-generator/src/main/kotlin/dev/icerock/gradle/actions/js/CopyResourcesToExecutableAction.kt @@ -4,14 +4,12 @@ package dev.icerock.gradle.actions.js -import dev.icerock.gradle.data.ExtractingBaseLibraryImpl +import dev.icerock.gradle.data.getKlibResourcesDir import dev.icerock.gradle.utils.klibs import org.gradle.api.Action import org.gradle.api.logging.Logger import org.gradle.api.provider.Provider import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile -import org.jetbrains.kotlin.library.KotlinLibraryLayout -import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl import java.io.File internal class CopyResourcesToExecutableAction( @@ -127,16 +125,13 @@ internal class CopyResourcesToExecutableAction( if (inputFile.exists().not()) return logger.info("copy resources from $inputFile into $outputDir") - val klibKonan = org.jetbrains.kotlin.konan.file.File(inputFile.path) - val klib = KotlinLibraryLayoutImpl(klib = klibKonan, component = "default") - val layout: KotlinLibraryLayout = if (klib.isZipped) { - ExtractingBaseLibraryImpl(klib) - } else { - klib + val resourcesDir: File = getKlibResourcesDir(inputFile) ?: run { + logger.info("resources in $inputFile not found") + return } try { - File(layout.resourcesDir.path, "moko-resources-js").copyRecursively( + File(resourcesDir, "moko-resources-js").copyRecursively( target = outputDir, overwrite = true ) diff --git a/resources-generator/src/main/kotlin/dev/icerock/gradle/data/ExtractingKotlinLibraryLayout.kt b/resources-generator/src/main/kotlin/dev/icerock/gradle/data/ExtractingKotlinLibraryLayout.kt index 2e2f28aa..4d149ee3 100644 --- a/resources-generator/src/main/kotlin/dev/icerock/gradle/data/ExtractingKotlinLibraryLayout.kt +++ b/resources-generator/src/main/kotlin/dev/icerock/gradle/data/ExtractingKotlinLibraryLayout.kt @@ -4,44 +4,64 @@ package dev.icerock.gradle.data -import org.jetbrains.kotlin.konan.file.File -import org.jetbrains.kotlin.konan.file.file -import org.jetbrains.kotlin.konan.file.unzipTo -import org.jetbrains.kotlin.konan.file.withZipFileSystem -import org.jetbrains.kotlin.library.KotlinLibraryLayout -import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl +import java.io.File +import java.nio.file.Files +import java.util.zip.ZipEntry +import java.util.zip.ZipFile + +private const val DEFAULT_COMPONENT = "default" +private const val RESOURCES_DIR_NAME = "resources" +private const val RESOURCES_PREFIX = "$DEFAULT_COMPONENT/$RESOURCES_DIR_NAME/" /** - * The code in this file is pulled from a previous version of Kotlin to replicate - * removed functionality that MR relies on for extracting klibs. - * https://github.com/JetBrains/kotlin/blob/00984f32ac1ebc2e7fb71b440c282be2a8b05f36/compiler/util-klib/src/org/jetbrains/kotlin/library/impl/KotlinLibraryLayoutImpl.kt + * Gets the resources directory from a klib file (packed or unpacked). + * + * For a packed (zipped) klib (single .klib file), extracts the resources + * directory to a temporary location and returns it. Returns null if the + * klib does not contain a resources directory. + * + * For an unpacked klib (directory), returns the direct path to the + * resources directory (which may not exist if the klib has no resources). + * + * The structure of a klib is: + * - Packed: a .klib zip containing `default/resources/` + * - Unpacked: a directory with `default/resources/` subdirectory */ - -internal open class ExtractingKotlinLibraryLayout(zipped: KotlinLibraryLayoutImpl) : KotlinLibraryLayout { - override val libFile: File get() = error("Extracting layout doesn't extract its own root") - override val libraryName = zipped.libraryName - override val component = zipped.component +internal fun getKlibResourcesDir(klibFile: File): File? { + return if (klibFile.isFile) { + // Packed (zipped) klib - extract resources to temp if present + extractResourcesFromPackedKlib(klibFile) + } else { + // Unpacked klib directory - navigate to resources (may not exist) + File(klibFile, "$DEFAULT_COMPONENT/$RESOURCES_DIR_NAME") + } } -internal class ExtractingBaseLibraryImpl(zipped: KotlinLibraryLayoutImpl) : ExtractingKotlinLibraryLayout(zipped) { - override val manifestFile: File by lazy { zipped.extract(zipped.manifestFile) } - override val resourcesDir: File by lazy { zipped.extractDir(zipped.resourcesDir) } -} +private fun extractResourcesFromPackedKlib(klibFile: File): File? { + ZipFile(klibFile).use { zip -> + val hasResources = zip.entries().asSequence().any { it.name.startsWith(RESOURCES_PREFIX) } + if (!hasResources) return null -private fun KotlinLibraryLayoutImpl.extract(file: File): File = extract(this.klib, file) - -private fun extract(zipFile: File, file: File) = zipFile.withZipFileSystem { zipFileSystem -> - val temporary = org.jetbrains.kotlin.konan.file.createTempFile(file.name) - zipFileSystem.file(file).copyTo(temporary) - temporary.deleteOnExit() - temporary + val temporary = Files.createTempDirectory(RESOURCES_DIR_NAME).toFile().also { + it.deleteOnExit() + } + zip.entries().asSequence() + .filter { it.name.startsWith(RESOURCES_PREFIX) } + .forEach { entry -> + val relativeName = entry.name.removePrefix(RESOURCES_PREFIX) + if (relativeName.isNotEmpty()) { + extractZipEntry(zip, entry, File(temporary, relativeName)) + } + } + return temporary + } } -private fun KotlinLibraryLayoutImpl.extractDir(directory: File): File = extractDir(this.klib, directory) - -private fun extractDir(zipFile: File, directory: File): File { - val temporary = org.jetbrains.kotlin.konan.file.createTempDir(directory.name) - temporary.deleteOnExitRecursively() - zipFile.unzipTo(temporary, fromSubdirectory = directory) - return temporary +private fun extractZipEntry(zip: ZipFile, entry: ZipEntry, output: File) { + if (entry.isDirectory) { + output.mkdirs() + } else { + output.parentFile?.mkdirs() + zip.getInputStream(entry).use { it.copyTo(output.outputStream()) } + } } diff --git a/resources-generator/src/main/kotlin/dev/icerock/gradle/tasks/CopyExecutableResourcesToApp.kt b/resources-generator/src/main/kotlin/dev/icerock/gradle/tasks/CopyExecutableResourcesToApp.kt index 13790f58..d5812d11 100644 --- a/resources-generator/src/main/kotlin/dev/icerock/gradle/tasks/CopyExecutableResourcesToApp.kt +++ b/resources-generator/src/main/kotlin/dev/icerock/gradle/tasks/CopyExecutableResourcesToApp.kt @@ -4,8 +4,7 @@ package dev.icerock.gradle.tasks -import dev.icerock.gradle.data.ExtractingBaseLibraryImpl -import dev.icerock.gradle.utils.toKonanFile +import dev.icerock.gradle.data.getKlibResourcesDir import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty @@ -13,8 +12,6 @@ import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction -import org.jetbrains.kotlin.library.KotlinLibraryLayout -import org.jetbrains.kotlin.library.impl.KotlinLibraryLayoutImpl import java.io.File import java.io.FileFilter @@ -39,15 +36,10 @@ abstract class CopyExecutableResourcesToApp : DefaultTask() { .filter { library -> library.extension == "klib" } .filter(File::exists) .forEach { inputFile -> - val klibKonan: org.jetbrains.kotlin.konan.file.File = inputFile.toKonanFile() - val klib = KotlinLibraryLayoutImpl(klib = klibKonan, component = "default") - val layout: KotlinLibraryLayout = ExtractingBaseLibraryImpl(klib) + val resourcesDir: File = getKlibResourcesDir(inputFile) ?: return@forEach // extracting bundles - layout - .resourcesDir - .absolutePath - .let(::File) + resourcesDir .listFiles(FileFilter { it.extension == "bundle" }) // copying bundles to app ?.forEach { diff --git a/samples/kotlin-2-tests/gradle/libs.versions.toml b/samples/kotlin-2-tests/gradle/libs.versions.toml index f2bc5a7e..18bd4515 100644 --- a/samples/kotlin-2-tests/gradle/libs.versions.toml +++ b/samples/kotlin-2-tests/gradle/libs.versions.toml @@ -10,7 +10,7 @@ uuid = "0.8.4" kodein = "7.23.1" moko-resources = "0.24.5" wrappers = "1.0.0-pre.839" -kotlin = "2.2.20" +kotlin = "2.3.20" compose-multiplatform = "1.7.1" paparazzi = "1.3.4" kotest = "5.9.1"