JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
15.86k stars 1.15k forks source link

ShadowJar Executable #5081

Closed makeevrserg closed 1 month ago

makeevrserg commented 2 months ago

Background

Currently, Compose Desktop provides limited set of binaries: [Dmg, Msi, Deb]. Furthermore, macos and linux binaries can only be built on macos/linux.

Bundled .jar can supply us with multiplatform .jar executable which can be built on any platform.

The documentation itself lacks information about .jar packaging. In my opinione, there should be section or another .MD document with tutorial about shadowJar and it's ProGuard obfuscation.

I don't have a knowledge and skill to create high-quality documentation, thus I'm creating this issue with required information about shadowJar packaging with some pitfalls and not PR.

Here's sample guide to enable shadowJar packaging from my project:

Setup

Firstly, to create shadow, we need to specify excactly kotlin.jvm plugin and not kotlin.multiplatform. This is required because shadowJar plugin doesn't work with kotlin.multiplatform. It requers jvm.

plugins {
    kotlin("jvm")
    id("org.jetbrains.compose")
    alias(libs.plugins.kotlin.compose.gradle)
    alias(libs.plugins.shadow)
}

Secondly, to enable binaries for all native targets - we need to specify compose.desktop.ANY_OS:


dependencies {
    // Compose
    implementation(compose.desktop.macos_arm64)
    implementation(compose.desktop.windows_x64)
    implementation(compose.desktop.linux_x64)
}

Then, we need to proceed default Compose Desktop Setu from official README.

Setup Shadow

Now, we need to setup shadowJar.

val shadowJar by tasks.named<ShadowJar>("shadowJar") {
    isReproducibleFileOrder = true
    mergeServiceFiles()
    // Define required configurations
    dependsOn(configurations)
    archiveClassifier = null as String?
    // Setup minimize - exclude some libraries
    minimize {
        exclude(dependency(libs.decompose.compose.get()))
        exclude(dependency(libs.kotlin.coroutines.swing.get()))
        exclude(dependency(dependencies.compose.desktop.currentOs))
        exclude(dependency("org.apache.poi:poi-ooxml:.*"))
        // Exlclude every dependency project from your gradle project
        rootProject.subprojects.map(::dependency).forEach(::exclude)
    }
    archiveVersion = "${projectInfo.versionString}-desktop"
    archiveBaseName = projectInfo.name
    rootProject.file("jars").also { destination ->
        if (!destination.exists()) destination.parentFile?.mkdirs()
        destinationDirectory = destination
    }
    // Don't forget manifest with Main file executable
    manifest {
        attributes("Main-Class" to "com.example.desktop.MainKt")
    }
}

Setup ProGuard 1

To setup proguard, we need to specify an input job - our shadowJar task. We also need to add configuration .pro files from original ComposeDesktop repo

Also, when build, we shouldn't use JetbrainsJDK because it lacks some JDK modules.

tasks.register<ProGuardTask>("obfuscate") {
    // Don't use Jetbrains JDK!!
    val projectInfo = requireProjectInfo
    dependsOn(shadowJar)
    // Specify library jars of current java
    // compose.desktop should be configured as it said above!!
    libraryjars("${compose.desktop.application.javaHome}/jmods")
    val obfuscated = rootProject.file("jars")
        .resolve("${projectInfo.name}-${projectInfo.versionString}-desktop-obf.jar")
    injars(shadowJar.outputs.files)
    outjars(obfuscated)
    printseeds("$buildDir/obfuscated/seeds.txt")
    printmapping("$buildDir/obfuscated/mapping.txt")
    verbose()
    dontoptimize()
    configuration(files("proguard-rules.pro", "default-compose-desktop-rules.pro"))
}

Setup Proguard 2

Now we need to run proguard to see what we're missing

./gradlew :composeDesktop:obfuscate --info --stacktrace

With info ProGuard task will tell you what classes you're missing. You will add it into proguard-rules.pro. To understand this, refer to official proguard documentation.

After all this steps, obfuscated .jar executable for all os's will be created and you're ready to go.

okushnikov commented 1 month ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.

makeevrserg commented 1 month ago

In case someone will be interested in more detailed tutorial - I've created one.

btw related to #518

https://github.com/makeevrserg/ComposeShadow