Browse Source
## Proposed changes 1. Added support to join JARs to the uber JAR with ProGuard, disabled by default: ``` compose.desktop { application { buildTypes.release.proguard { joinOutputJars.set(true) } } } ``` 2. All 'release' tasks now really depend on ProGuard, as stated in [tutorial](https://github.com/JetBrains/compose-multiplatform/tree/master/tutorials/Native_distributions_and_local_execution#minification--obfuscation). ## Testing - A new auto test - Manual: 1. Test on Windows/macOs/Linux 2. Test the new Gradle parameter `joinOutputJars`: ``` compose.desktop { application { buildTypes.release.proguard { joinOutputJars.set(true) } } } ``` `false` (by default) should generate multiple jars (except for `package*UberJarForCurrentOS`) `true` should generate a single jar in a result distribution 3. Test debug tasks: ``` run runDistributable createDistributable packageUberJarForCurrentOS ``` 4. Test release tasks: ``` runRelease runReleaseDistributable createReleaseDistributable packageReleaseUberJarForCurrentOS ``` The jars should be reduced in size (because Proguard is enabled in the release mode) This should be test by QA. ## Issues fixed Fixes https://github.com/JetBrains/compose-multiplatform/issues/4129 --------- Co-authored-by: Igor Demin <igordmn@users.noreply.github.com>pull/4628/head v1.6.10-dev1580
badmannersteam
8 months ago
committed by
GitHub
7 changed files with 275 additions and 47 deletions
@ -0,0 +1,86 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2024 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.desktop.tasks |
||||||
|
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection |
||||||
|
import org.gradle.api.file.RegularFileProperty |
||||||
|
import org.gradle.api.tasks.InputFiles |
||||||
|
import org.gradle.api.tasks.Internal |
||||||
|
import org.gradle.api.tasks.OutputFile |
||||||
|
import org.gradle.api.tasks.TaskAction |
||||||
|
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-insensitive 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:OutputFile |
||||||
|
val flattenedJar: RegularFileProperty = objects.fileProperty() |
||||||
|
|
||||||
|
@get:Internal |
||||||
|
val seenEntryNames = hashSetOf<String>() |
||||||
|
|
||||||
|
@TaskAction |
||||||
|
fun execute() { |
||||||
|
seenEntryNames.clear() |
||||||
|
fileOperations.delete(flattenedJar) |
||||||
|
|
||||||
|
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 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 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 seenEntryNames) { |
||||||
|
copyZipEntry(entry, inputStream, this) |
||||||
|
seenEntryNames += entry.name |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue