From 7fe647fd21a5f459453047c15ffac030aba6236e Mon Sep 17 00:00:00 2001 From: badmannersteam Date: Tue, 12 Mar 2024 20:32:28 +0100 Subject: [PATCH] Jars flattening task now flattens to the single jar instead of directory. --- .../internal/configureJvmApplication.kt | 6 +- .../desktop/tasks/AbstractJarsFlattenTask.kt | 77 ++++++++++++++----- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt index 466e6f61b97..d53ad3dd621 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt @@ -458,14 +458,14 @@ private fun JvmApplicationContext.configureFlattenJars( ) { if (runProguard != null) { flattenJars.dependsOn(runProguard) - flattenJars.inputFiles.from(project.fileTree(runProguard.flatMap { it.destinationDir })) + flattenJars.inputFiles.from(runProguard.flatMap { it.destinationDir }) } else { flattenJars.useAppRuntimeFiles { (runtimeJars, _) -> inputFiles.from(runtimeJars) } } - flattenJars.destinationDir.set(appTmpDir.dir("flattenJars")) + flattenJars.flattenedJar.set(appTmpDir.file("flattenJars/flattened.jar")) } private fun JvmApplicationContext.configurePackageUberJarForCurrentOS( @@ -473,7 +473,7 @@ private fun JvmApplicationContext.configurePackageUberJarForCurrentOS( flattenJars: Provider ) { jar.dependsOn(flattenJars) - jar.from(flattenJars.flatMap { it.destinationDir }) + jar.from(project.zipTree(flattenJars.flatMap { it.flattenedJar })) app.mainClass?.let { jar.manifest.attributes["Main-Class"] = it } jar.duplicatesStrategy = DuplicatesStrategy.EXCLUDE diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractJarsFlattenTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractJarsFlattenTask.kt index cf1d436f54b..b0cd78af478 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractJarsFlattenTask.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractJarsFlattenTask.kt @@ -6,40 +6,81 @@ package org.jetbrains.compose.desktop.tasks import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.DuplicatesStrategy -import org.gradle.api.file.FileCollection +import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction -import org.jetbrains.compose.internal.utils.clearDirs +import org.jetbrains.compose.desktop.application.internal.files.copyZipEntry +import org.jetbrains.compose.desktop.application.internal.files.isJarFile +import org.jetbrains.compose.internal.utils.delete +import org.jetbrains.compose.internal.utils.ioFile import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.InputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream + +/** + * This task flattens all jars from the input directory into the single one, + * which is used later as a single source for uberjar. + * + * This task is necessary because the standard Jar/Zip task evaluates own `from()` args eagerly + * [in the configuration phase](https://discuss.gradle.org/t/why-is-the-closure-in-from-method-of-copy-task-evaluated-in-config-phase/23469/4) + * and snapshots an empty list of files in the Proguard destination directory, + * instead of a list of real jars after Proguard task execution. + * + * Also, we use output to the single jar instead of flattening to the directory in the filesystem because: + * - Windows filesystem is case-sensitive and not every jar can be unzipped without losing files + * - it's just faster + */ abstract class AbstractJarsFlattenTask : AbstractComposeDesktopTask() { @get:InputFiles val inputFiles: ConfigurableFileCollection = objects.fileCollection() - @get:OutputDirectory - val destinationDir: DirectoryProperty = objects.directoryProperty() + @get:OutputFile + val flattenedJar: RegularFileProperty = objects.fileProperty() + + @get:Internal + val entries = hashSetOf() @TaskAction fun execute() { - fileOperations.clearDirs(destinationDir) + entries.clear() + fileOperations.delete(flattenedJar) - fileOperations.copy { - it.duplicatesStrategy = DuplicatesStrategy.EXCLUDE - it.from(flattenJars(inputFiles)) - it.into(destinationDir) + ZipOutputStream(FileOutputStream(flattenedJar.ioFile).buffered()).use { outputStream -> + inputFiles.asFileTree.visit { + when { + !it.isDirectory && it.file.isJarFile -> outputStream.writeJarContent(it.file) + !it.isDirectory -> outputStream.writeFile(it.file) + } + } } } - private fun flattenJars(files: FileCollection) = files.map { - when { - it.isZipOrJar() -> this.archiveOperations.zipTree(it) - else -> it + private fun ZipOutputStream.writeJarContent(jarFile: File) = + ZipInputStream(FileInputStream(jarFile)).use { inputStream -> + var inputEntry: ZipEntry? = inputStream.nextEntry + while (inputEntry != null) { + writeEntryIfNotSeen(inputEntry, inputStream) + inputEntry = inputStream.nextEntry + } } - } - private fun File.isZipOrJar() = name.endsWith(".jar", ignoreCase = true) || name.endsWith(".zip", ignoreCase = true) + private fun ZipOutputStream.writeFile(file: File) = + FileInputStream(file).use { inputStream -> + writeEntryIfNotSeen(ZipEntry(file.name), inputStream) + } + + private fun ZipOutputStream.writeEntryIfNotSeen(entry: ZipEntry, inputStream: InputStream) { + if (entry.name !in entries) { + copyZipEntry(entry, inputStream, this) + entries += entry.name + } + } } \ No newline at end of file