compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

Update ProGuard to version 7.4 to support new Java versions

Open StefanOltmann opened this issue 2 years ago • 14 comments

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

StefanOltmann avatar Oct 13 '23 10:10 StefanOltmann

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.

mipastgt avatar Oct 30 '23 09:10 mipastgt

The proguard version can be configured like this:

compose.desktop {
  buildTypes.release.proguard {
    version.set("7.4.0")
  }
}

eymar avatar Nov 06 '23 15:11 eymar

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 avatar Nov 06 '23 17:11 mipastgt

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

mipastgt avatar Nov 06 '23 17:11 mipastgt

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.

mikedawson avatar Feb 01 '24 11:02 mikedawson

The configuration works for me.

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

StefanOltmann avatar Feb 01 '24 11:02 StefanOltmann

@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)

mikedawson avatar Feb 01 '24 11:02 mikedawson

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.

StefanOltmann avatar Feb 01 '24 12:02 StefanOltmann

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

mipastgt avatar Feb 01 '24 12:02 mipastgt

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

mikedawson avatar Feb 01 '24 13:02 mikedawson

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.

mipastgt avatar Feb 01 '24 13:02 mipastgt

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).

mikedawson avatar Feb 01 '24 16:02 mikedawson

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.

mipastgt avatar Feb 01 '24 17:02 mipastgt

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.

StefanOltmann avatar Feb 01 '24 18:02 StefanOltmann

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 avatar May 28 '24 04:05 EchoEllet

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

EchoEllet avatar May 29 '24 16:05 EchoEllet