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
16.21k stars 1.17k forks source link

Update ProGuard to version 7.4 to support new Java versions #3818

Closed StefanOltmann closed 1 month ago

StefanOltmann commented 1 year ago

Currently, Compose Multiplatform relies on ProGuard version 7.2.2, which provides Java support only up to Java 18.

However, the latest ProGuard release, version 7.4, offers compatibility with Kotlin 1.9 and Java 21. I recommend updating this dependency for improved functionality and compatibility.

For more details, please refer to: https://github.com/Guardsquare/proguard/releases/tag/v7.4

mipastgt commented 1 year ago

This missing upgrade is the only blocker to use Java 21 in Compose desktop projects as far as I can tell. Debug releases (without ProGuard) seem to work out of the box.

eymar commented 12 months ago

The proguard version can be configured like this:

compose.desktop {
  buildTypes.release.proguard {
    version.set("7.4.0")
  }
}
mipastgt commented 12 months ago

Ahh, now I know why it did not work when I tried it according to this advice on Slack: https://kotlinlang.slack.com/archives/C01D6HTPATV/p1699268691268389?thread_ts=1699231842.202439&cid=C01D6HTPATV I figured out that the argument has to be a String but I tried "7.4" instead of "7.4.0".

mipastgt commented 12 months ago

And packaging a release with Java 21 works too now. Thanks a lot.

mikedawson commented 9 months ago

Doesn't work for me: and I was able to reproduce the error using the out-of-the-box kotlin multiplatform project wizard (including desktop only).

Reproduction project here: https://github.com/mikedawson/ComposeProguardVersionTest/

Tested using: Kotlin 1.9.21 / Compose Multiplatform 1.5.11 (the latest production release until 3 days ago as per compatibility and versioning docs). Built on Ubuntu 23.10 using OpenJDK 17.

All you have to do is take the out-of-the-box project wizard, set the Proguard version to 7.4.0 (or 7.4.2), and you'll get:

./gradlew clean composeApp:runReleaseDistributable

...

pure virtual method called
terminate called without an active exception
Gtk-Message: 15:05:16.435: Failed to load module "canberra-gtk-module"
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    kotlinx/coroutines/scheduling/CoroutineScheduler$WorkerState.values$6b08e0a8()[I @3: invokevirtual
  Reason:
    Type '[I' (current frame, stack[0]) is not assignable to '[Ljava/lang/Object;'
  Current Frame:
    bci: @3
    flags: { }
    locals: { }
    stack: { '[I' }
  Bytecode:
    0000000: b200 0ab6 0010 c000 05b0               

        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:627)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:606)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.createNewWorker(CoroutineScheduler.kt:496)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.tryCreateWorker(CoroutineScheduler.kt:453)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.dispatch(CoroutineScheduler.kt:4434)
        at kotlinx.coroutines.scheduling.SchedulerCoroutineDispatcher.dispatchWithContext$kotlinx_coroutines_core(Dispatcher.kt:118)
        at kotlinx.coroutines.scheduling.UnlimitedIoScheduler.dispatch(Dispatcher.kt:47)
        at kotlinx.coroutines.internal.LimitedDispatcher.dispatch(LimitedDispatcher.kt:49)
        at kotlinx.coroutines.scheduling.DefaultIoScheduler.dispatch(Dispatcher.kt:80)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:322)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default$6a2188af(Cancellable.kt:25)
        at kotlinx.coroutines.AbstractCoroutine.start$62a77ca7(AbstractCoroutine.kt:2110)
        at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
        at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
        at org.jetbrains.skiko.FrameWatcher.start(FrameWatcher.kt:22)
        at org.jetbrains.skiko.Setup.init(Setup.kt:35)
        at org.jetbrains.skiko.Setup.init$default(Setup.kt:6)
        at org.jetbrains.skiko.Library.load(Library.kt:62)
        at org.jetbrains.skia.impl.Library$Companion.staticLoad(Library.jvm.kt:12)
        at androidx.compose.ui.ConfigureSwingGlobalsForCompose_desktopKt.configureSwingGlobalsForCompose$default$626a2300(ConfigureSwingGlobalsForCompose.desktop.kt:1049)
        at androidx.compose.ui.window.Application_desktopKt.application(Application.desktop.kt:110)
        at MainKt.main(main.kt:1105)
        Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@1b1426f4, Dispatchers.IO]
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    kotlinx/coroutines/scheduling/CoroutineScheduler$WorkerState.values$6b08e0a8()[I @3: invokevirtual
  Reason:
    Type '[I' (current frame, stack[0]) is not assignable to '[Ljava/lang/Object;'
  Current Frame:
    bci: @3
    flags: { }
    locals: { }
    stack: { '[I' }
  Bytecode:
    0000000: b200 0ab6 0010 c000 05b0               

        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:627)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.<init>(CoroutineScheduler.kt:606)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.createNewWorker(CoroutineScheduler.kt:496)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.tryCreateWorker(CoroutineScheduler.kt:453)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.dispatch(CoroutineScheduler.kt:4434)
        at kotlinx.coroutines.scheduling.SchedulerCoroutineDispatcher.dispatchWithContext$kotlinx_coroutines_core(Dispatcher.kt:118)
        at kotlinx.coroutines.scheduling.UnlimitedIoScheduler.dispatch(Dispatcher.kt:47)
        at kotlinx.coroutines.internal.LimitedDispatcher.dispatch(LimitedDispatcher.kt:49)
        at kotlinx.coroutines.scheduling.DefaultIoScheduler.dispatch(Dispatcher.kt:80)
        at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:322)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
        at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default$6a2188af(Cancellable.kt:25)
        at kotlinx.coroutines.AbstractCoroutine.start$62a77ca7(AbstractCoroutine.kt:2110)
        at kotlinx.coroutines.BuildersKt.launch(Unknown Source)
        at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source)
        at org.jetbrains.skiko.FrameWatcher.start(FrameWatcher.kt:22)
        at org.jetbrains.skiko.Setup.init(Setup.kt:35)
        at org.jetbrains.skiko.Setup.init$default(Setup.kt:6)
        at org.jetbrains.skiko.Library.load(Library.kt:62)
        at org.jetbrains.skia.impl.Library$Companion.staticLoad(Library.jvm.kt:12)
        at androidx.compose.ui.ConfigureSwingGlobalsForCompose_desktopKt.configureSwingGlobalsForCompose$default$626a2300(ConfigureSwingGlobalsForCompose.desktop.kt:1049)
        at androidx.compose.ui.window.Application_desktopKt.application(Application.desktop.kt:110)
        at MainKt.main(main.kt:1105)
        Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelled}@1b1426f4, Dispatchers.IO]

I really like Compose/Desktop: this is allowing us to get our Android app out on the desktop in a way that otherwise would not be possible (and we need things that don't work in web browsers: offline, peer-to-peer networking, etc). That said, the support for shrinking (using Proguard) seems in need of polishing. As far as I can see (and have documented/filed issues for), it's not working on JDK21 (which is supposed to be a supported JDK as per the docs), and KTOR serialization fails.

I would suggest that at all example desktop projects should have proguard enabled with obfuscation enabled (not just the proguard usage demo). That might help such issues get spotted.

StefanOltmann commented 9 months ago

The configuration works for me.

I kept the issue open as a reminder to set the default version ProGuard to a more current version.

mikedawson commented 9 months ago

@StefanOltmann interesting. What system were you building on? I just updated my post to include the versions being used (of course the Kotlin version, compose version, etc are in the Gradle files)

StefanOltmann commented 9 months ago

Kotlin 1.9.21, Compose Multiplatform 1.5.11, ProGuard 7.4.1, Gradle 8.5, latest macOS, Eclipse Temurin 18.0.2+9

I did not try your reproduction project. I meant ProGuard 7.4.1 works in general.

mipastgt commented 9 months ago

@mikedawson Just to be sure. Are we talking about a build failure or a run-time failure here?

mikedawson commented 9 months ago

Run-time failure. Compilation will succeed. I edited my post above to include the gradle tasks I was running just to be sure.

mipastgt commented 9 months ago

I can confirm that your example fails for me too on my Mac with the same error. The reason is that you did not switch off ProGuard optimization. With this

        buildTypes.release.proguard {
            obfuscate.set(false)
            optimize.set(false)
            version.set("7.4.0")
        }

setup your example works perfectly. I have noticed previously that optimization causes nothing but problems on desktop, so I always switch it off.

mikedawson commented 9 months ago

Thanks @mipastgt for confirming the reproducer project reproduces the issue for you.

I think "working perfectly" would here should mean works as per the documentation. That means optimization (which itself is enabled by default) and obfuscation should work. As per the docs, compilation is supposed to support JDK17+ AND proguard.

Compose/Desktop is defined as stable, however, a key feature (code shrinking) does not work with the current LTS JDK release (21).

mipastgt commented 9 months ago

Yes, that should work out of the box according to the docs. From my previous reports I had the impression that optimization was switched off by default as a fix for these issues but at least as of today this assumption seems to be wrong. I still had the manual switch off in all my projects. That's the reason why I did not notice the problem at first.

StefanOltmann commented 9 months ago

In Ashampoo Photos I have optimization and obfuscation turned on. That works so far. So there is a way making ProGuard work. From that perspective the ProGuard default version could be updated.

But I also have rules like keep !com.ashampoo.photos.* because I had issues.

Mike, from our discussion on Slack I remember that you have a rather fine-grained and complex configuration while mine is short, but does not optimize that much. I guess this has to do with the problems you experience.

I see my request to update the default ProGuard version unrelated to the fact that right now a proper ProGuard configuration is difficult to achieve.

I initially requested ProGuard support in Compose Multiplatform, because I have a closed source app that I need to obfuscate. I turned obfuscation on for all my own code, but for none of the third party libraries. So while it could be better the current ProGuard state satisfies the obfuscation needs of commercial apps.

On a side note: Some libs like Apache commons-compress just don’t work with ProGuard and Kotlin - at least for me. I needed to strip them using excludes in Gradle dependencies.

EchoEllet commented 5 months ago

Using Java 21, with Kotlin 2.0.0 and Compose 1.6.10, will cause this error when using any of the tasks that use Proguard:

Caused by: java.io.IOException: Can't process class [AppKt.class] (Unsupported version number [65.0] (maximum 62.65535, Java 18))

Which can be solved by:

buildTypes.release.proguard {
    version.set("7.4.2") // Or 7.5.0 when it's published for compatibility with Kotlin 2.0.0
}

Another task failure:


Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    androidx/compose/runtime/SnapshotStateKt.snapshotFlow(Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/flow/Flow; @20: invokestatic
  Reason:
    Type 'kotlin/jvm/functions/Function2' (current frame, stack[0]) is not assignable to 'androidx/compose/runtime/SnapshotStateKt__SnapshotFlowKt$snapshotFlow$1'
  Current Frame:
    bci: @20
    flags: { }
    locals: { 'kotlin/jvm/functions/Function0' }
    stack: { 'kotlin/jvm/functions/Function2' }
  Bytecode:
    0000000: 2a59 4b12 0ab8 0031 bb00 1659 2a01 b700
    0000010: 29c0 001e b800 32b0                    

    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1.invokeSuspend(Application.desktop.kt:205)
    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1.invoke(Application.desktop.kt:1000)
    at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn$42ea79c7(Undispatched.kt:61)
    at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:163)
    at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2.invokeSuspend(Application.desktop.kt:201)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

> Task :composeApp:runRelease FAILED

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.
11 actionable tasks: 6 executed, 5 up-to-date

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':composeApp:runRelease'.
> Process 'command '/Library/Java/JavaVirtualMachines/amazon-corretto-21.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

This issue will happen when using Proguard 7.4.2 with Java 17 too

In short, Proguard might need to be updated to 7.5.0 to not only support Java 21, but also Kotlin 2.0.0

EchoEllet commented 5 months ago

Issue should be fixed once updated to 7.5.0 as it's published now.

Overriding the Proguard version:

buildTypes.release.proguard {
            version.set("7.5.0")
        }

Doesn't solve the issue, it looks like the rules might needs to be updated or some additional rules

okushnikov commented 2 months ago

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