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.24k stars 1.11k forks source link

Default Proguard rules cause release task failure when using Material 3 instead of 2 in Compose Desktop #4883

Open ellet0 opened 4 weeks ago

ellet0 commented 4 weeks ago

Describe the bug Starting from 1.2, Compose Desktop Plugin supports Proguard without any additional rules to get it working, but if you use Material 3 by usingimplementation(compose.material3) then it will cause failure with all the tasks that use Proguard likecreateReleaseDistributable

Affected platforms

Versions

To Reproduce Steps to reproduce the behavior:

  1. Create a new compose desktop project, either by using https://kmp.jetbrains.com/ or the Compose Desktop template, make sure to use Kotlin 2.0.0 and Compose 1.6.10
  2. Make sure to use a JDK version that's supported by JPackage (17 or above) and if you're on macOS, use Amazon Corretto or a similar distribution. Try to package the application using ./gradlew createReleaseDistributable and see if there is an error, I didn't get any in my case
  3. Go to build.gradle.kts of the desktop source set in thecomposeApp Gradle module (if you used the kmp.jetbrains.com) and replace implementation(compose.material) with implementation(compose.material3), remove any usages to Material module (Material 2) and use anything from Material 3 module
    
    // composeApp/desktopMain/kotlin/App.kt

import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable

@Composable fun App() { Button(onClick = {}) { Text(text = "Hi") } }

5. Try to run any task that uses Proguard like `createReleaseDistributable` and see if there is any error

**Expected behavior**
The bundle size remains quite large, even with Java installed on the machine, which is why I use Proguard. It would be nice to have Proguard support with Material 3.

**Screenshots**
![image](https://github.com/JetBrains/compose-multiplatform/assets/73608287/e7ab212d-37ff-484a-8e31-1d8cc07b7c54)

> Notice that if you have Java 21 (or any version higher than Java 17) installed and configured to be used in the system path, you should make sure to use Java 17 in both Project settings and Project Gradle settings and run the tasks from IDE and not in the command line as it will cause the `java` command from your system which will use Java 21
> See #4125 and #3818 for more details

**Additional context**

```console

> Task :composeApp:proguardReleaseJars
ProGuard, version 7.2.2
Note: duplicate definition of resource file [META-INF/MANIFEST.MF]
Note: duplicate definition of resource file [META-INF/MANIFEST.MF]
Note: duplicate definition of resource file [META-INF/MANIFEST.MF]
Note: duplicate definition of resource file [META-INF/MANIFEST.MF]
Note: duplicate definition of resource file [META-INF/MANIFEST.MF]
Note: duplicate definition of resource file [META-INF/MANIFEST.MF]
Note: duplicate definition of resource file [META-INF/MANIFEST.MF]

Note: androidx.lifecycle.viewmodel.internal.JvmViewModelProviders accesses a declared constructor '<init>()' dynamically
Warning: there were 884 unresolved references to classes or interfaces.
         You may need to add missing library jars or update their versions.
         If your code works fine without the missing classes, you can suppress
         the warnings with '-dontwarn' options.
         (https://www.guardsquare.com/proguard/manual/troubleshooting#unresolvedclass)
Unexpected error
java.io.IOException: Please correct the above warnings first.
        at proguard.Initializer.execute(Initializer.java:544) ~[proguard-base-7.2.2.jar:7.2.2]
        at proguard.pass.PassRunner.run(PassRunner.java:24) ~[proguard-base-7.2.2.jar:7.2.2]
        at proguard.ProGuard.initialize(ProGuard.java:328) ~[proguard-base-7.2.2.jar:7.2.2]
        at proguard.ProGuard.execute(ProGuard.java:130) ~[proguard-base-7.2.2.jar:7.2.2]
        at proguard.ProGuard.main(ProGuard.java:623) [proguard-base-7.2.2.jar:7.2.2]

> Task :composeApp:proguardReleaseJars FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':composeApp:proguardReleaseJars'.
> External tool execution failed:
  * Command: [/Library/Java/JavaVirtualMachines/amazon-corretto-17.jdk/Contents/Home/bin/java, -cp, "/Users/ellet/.gradle/caches/modules-2/files-2.1/com.guardsquare/proguard-gradle/7.2.2/3ee8f512db2bee70d3eca9be10986d96404dc2f0/proguard-gradle-7.2.2.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/com.github.zafarkhaja/java-semver/0.9.0/59a83ca73c72a5e25b3f0b1bb305230a11000329/java-semver-0.9.0.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/com.guardsquare/proguard-base/7.2.2/5c67dd9184bfb7d01b082b45bf3a6599b43d5c25/proguard-base-7.2.2.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.6.10/e80fe6ac3c3573a80305f5ec43f86b829e8ab53d/kotlin-stdlib-jdk8-1.6.10.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.5/f645ed69d595b24d4cf8b3fbb64cc505bede8829/gson-2.8.5.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/com.guardsquare/proguard-core/9.0.1/b1c26f3a6fc947a1bdfa67fd8c96230df0471156/proguard-core-9.0.1.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-core/2.17.1/779f60f3844dadc3ef597976fcb1e5127b1f343d/log4j-core-2.17.1.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-api/2.17.1/d771af8e336e372fb5399c99edabe0919aeaf5b2/log4j-api-2.17.1.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.json/json/20211205/47032dcf2f69880f07dab3dc60b4b0ad97318308/json-20211205.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.6.10/e1c380673654a089c4f0c9f83d0ddfdc1efdb498/kotlin-stdlib-jdk7-1.6.10.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-metadata-jvm/0.4.1/834f5bdb32c504ee72fa50750ad16ab1679c19e6/kotlinx-metadata-jvm-0.4.1.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.6.10/b8af3fe6f1ca88526914929add63cf5e7c5049af/kotlin-stdlib-1.6.10.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.10/c118700e3a33c8a0d9adc920e9dec0831171925/kotlin-stdlib-common-1.6.10.jar:/Users/ellet/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar", proguard.ProGuard, -include, "/Users/ellet/Downloads/KotlinProject/composeApp/build/compose/tmp/proguardReleaseJars/root-config.pro"]
  * Working dir: []
  * Exit code: 1
  * Standard output log: /Users/ellet/Downloads/KotlinProject/composeApp/build/compose/logs/proguardReleaseJars/java-2024-05-28-05-06-55-out.txt
  * Error log: /Users/ellet/Downloads/KotlinProject/composeApp/build/compose/logs/proguardReleaseJars/java-2024-05-28-05-06-55-err.txt

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.7/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD FAILED in 6s
12 actionable tasks: 7 executed, 5 up-to-date

The Proguard version that's used in the Compose desktop Gradle plugin is 7.2.2 and the one that supports Kotlin 2.0.0 and versions of Java 21 or higher is 7.5.0 More details: https://github.com/Guardsquare/proguard/issues/376 and https://github.com/Guardsquare/proguard/issues/387

I was able to run the example application using 7.2.2 with no issues

image

With Material 2 (the module name is called Material)

The file KotlinProject/composeApp/build/compose/logs/proguardReleaseJars/java-2024-05-28-05-06-55-err.txt is empty, the KotlinProject/composeApp/build/compose/logs/proguardReleaseJars/java-2024-05-28-05-06-55-out.txt was mostly warnings (about 940 lines), the end of the file:

Note: androidx.lifecycle.viewmodel.internal.JvmViewModelProviders accesses a declared constructor '<init>()' dynamically
Warning: there were 884 unresolved references to classes or interfaces.
         You may need to add missing library jars or update their versions.
         If your code works fine without the missing classes, you can suppress
         the warnings with '-dontwarn' options.
         (https://www.guardsquare.com/proguard/manual/troubleshooting#unresolvedclass)
Unexpected error
java.io.IOException: Please correct the above warnings first.
    at proguard.Initializer.execute(Initializer.java:544) ~[proguard-base-7.2.2.jar:7.2.2]
    at proguard.pass.PassRunner.run(PassRunner.java:24) ~[proguard-base-7.2.2.jar:7.2.2]
    at proguard.ProGuard.initialize(ProGuard.java:328) ~[proguard-base-7.2.2.jar:7.2.2]
    at proguard.ProGuard.execute(ProGuard.java:130) ~[proguard-base-7.2.2.jar:7.2.2]
    at proguard.ProGuard.main(ProGuard.java:623) [proguard-base-7.2.2.jar:7.2.2]

https://github.com/JetBrains/compose-multiplatform/blob/7131b5b9a665e8b1d72aabc9476947650145c11e/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/ProguardSettings.kt#L15

https://github.com/JetBrains/compose-multiplatform/blob/7131b5b9a665e8b1d72aabc9476947650145c11e/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/tasks/AbstractUnpackDefaultComposeApplicationResourcesTask.kt#L19

This file might need to be updated: https://github.com/JetBrains/compose-multiplatform/blob/master/gradle-plugins/compose/src/main/resources/default-compose-desktop-rules.pro

Disabling Proguard optimization will give you the exact same issue:


buildTypes.release.proguard {
            optimize.set(false)
        }

Related Issues

ellet0 commented 3 weeks ago

I managed to get it working with a workaround that's far from perfect and shouldn't be used in real production app

  1. First of all, try to reproduce the bug mentioned above
  2. Exclude Material module (Material 2) as mentioned in this https://github.com/JetBrains/compose-multiplatform/issues/497#issuecomment-1472218306 in #497 from all compose libraries:
implementation(compose.foundation) {
  exclude("org.jetbrains.compose.material")
}
implementation(compose.material3) {
  exclude("org.jetbrains.compose.material")
}
 implementation(compose.ui) {
    exclude("org.jetbrains.compose.material")
}
// ...

Or:

implementation(compose.desktop.currentOs) {
   exclude("org.jetbrains.compose.material")
}
  1. Create a new Proguard rules file and use it in the compose desktop module/source set

    compose.desktop {
    application {
        // Your configurations
    
        buildTypes.release.proguard {
            configurationFiles.from(files("compose-desktop.pro"))
        }
    }
    }

    The name of the file is up to you, in my case, I created this project using (kmp.jetbrains.com), so I have to include this file in composeApp module (not inside desktopMain source set), the content will be something like

# TODO: Workaround to solve https://github.com/JetBrains/compose-multiplatform/issues/4883
-dontwarn androidx.compose.material.**
-keep class androidx.compose.material3.** { *; }
-ignorewarnings

This will keep all classes from Material 2 and ignore all the warnings from Material 2 and other warnings

  1. Try to launch the release version of the app to use Proguard, a task like runReleaseDistributable, make sure to run it using your IDE as using ./gradlew runReleaseDistributable might use Java version 21 or above if installed and configured in the system path, the app will compile but you will get a runtime error if you use anything from Material 3 that depends on Material 2 which might be another issue, the code snippet:
    
    import androidx.compose.material3.Button
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable

@Composable fun App() { Button(onClick = {}) { Text(text = "Hi") } }

5. You should get the following error when launching the release version of the app:

![image](https://github.com/JetBrains/compose-multiplatform/assets/73608287/ed136f63-d9b2-432a-bf44-562ec8af627e)

This can be easily fixed by not excluding Material 2 from Material 3:

```kotlin
implementation(compose.material3)

or exclude Material 2 completely if used from Material 3 and only include the needed modules:

implementation(compose.material3) {
    // Explicitly exclude Material 2 completely from Material 3 in case Material 3 or any of its modules depends on the Material 2 module or any of its modules
    // At the moment, it seems to be only depending on material-ripple and material-icons-core modules
    exclude("org.jetbrains.compose.material")
}
// TODO: A workaround to include the needed modules after excluding the Material 2 module from Material 3 (see https://github.com/JetBrains/compose-multiplatform/issues/4883#issuecomment-2142445581) for more details
implementation("org.jetbrains.compose.material:material-ripple:${libs.versions.compose.get()}")
implementation("org.jetbrains.compose.material:material-icons-core:${libs.versions.compose.get()}")

The app works now image

Noticed that the default Kotlin icon included in Compose desktop is not included and you will get this instead

image

I didn't investigate further about this

Another unrelated issue is that Material 3 depends on some Material 2 modules like the ripple effect and icons core

https://github.com/JetBrains/compose-multiplatform-core/blob/1694668f0d423babe94805bba603e087bd5ca943/compose/material3/material3/build.gradle#L104

This issue in https://github.com/JetBrains/compose-multiplatform-core which is not in this repository

Additional details:

The binary bundle size that's created with packageReleaseDmg (requires Java installed) after the workaround to use Material 3 with Proguard in the release version of the app

image

Before the workaround (Use Material 2)

image

The bundle size is around 6MB larger when using Material 3, I only used a Button and Text from Material 2 and Material 3, the size might not get much bigger when using more components from Material 3, this is another thing to look at but I'm not sure as I haven't looked into the source code in: https://github.com/JetBrains/compose-multiplatform-core

ellet0 commented 2 weeks ago

A better workaround, to use Material 3 (excluding Material 2), Kotlin 2.0.0, Proguard 7.5.0, and Compose 1.6.10:

  1. Exclude Material 2 and include kotlinx-serialization-core:
implementation(compose.material3)
implementation(compose.desktop.currentOs) {
   exclude("org.jetbrains.compose.material")
}
// Explicitly include this is required to fix Proguard warnings coming from Kotlinx.DateTime
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.0")
  1. Update Compose Desktop Proguard settings to use Proguard 7.5.0 and include rules from a file:
compose.desktop {
    application {
        buildTypes.release.proguard {
            version.set("7.5.0")
            configurationFiles.from("proguard.pro")
        }
    }
}
  1. Create the file proguard.pro if doesn't exist, use the following rules:
-keep class androidx.compose.runtime.** { *; }
-keep class androidx.collection.** { *; }
-keep class androidx.lifecycle.** { *; }
-keep class androidx.compose.ui.text.platform.ReflectionUtil { *; }

# We're excluding Material 2 from the project as we're using Material 3
-dontwarn androidx.compose.material.**

# Kotlinx coroutines rules seems to be outdated with the latest version of Kotlin and Proguard
-keep class kotlinx.coroutines.** { *; }

This doesn't have the icon issue as the previous workaround does.

The Uber JAR on macOS Apple silicon:

image

The native application binary created with packageReleaseDmg

image

Note that there are Proguard notes that need to be solved.

syt0r commented 1 week ago

I'm facing this issue too, is it going to be addressed in the next release? I think it should be mentioned in release notes too

ellet0 commented 1 week ago

I'm facing this issue too, is it going to be addressed in the next release? I think it should be mentioned in release notes too

A workaround solution for now is to disable Proguard in the release or fix the issue with a workaround as mentioned above.

The workaround solution doesn't increase the size much. The difference is due to using Material 3