Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AbstractMethodError When Using Kotlin Extension Functions (reified, inline, and crossinline keywords) with Java Swing Components that Use ActionEvent #412

Closed
EchoEllet opened this issue Jun 6, 2024 · 4 comments

Comments

@EchoEllet
Copy link
Contributor

EchoEllet commented Jun 6, 2024

AbstractMethodError with Kotlin Extension Functions and Java Swing ActionEvent Using ProGuard

Description

The title and details might need to be updated, the issue happens when using Kotlin with Kotlin extension function with (reified, inline and crossinline) and usage of addActionListener in JComboBox or any other Swing component like JButton

I'm uncertain if this problem is isolated to the utilization of Kotlin extension functions (reified, inline, and crossinline keywords) with Java Swing components.

// Will throw AbstractMethodError

inline fun <reified T> JComboBox<T>.onItemSelected(crossinline onItemSelected: (item: T?, event: ActionEvent) -> Unit): JComboBox<T> {
    addActionListener { event ->
        onItemSelected(this.getSelectedItemSafe(), event)
    }
    return this
}
// Calling the function in JComboBox won't throw any Error, it will work as expected.

fun <T> JComboBox<T>.onItemSelected(onItemSelected: (item: T?, event: ActionEvent) -> Unit): JComboBox<T> {
    addActionListener { event ->
        @Suppress("UNCHECKED_CAST")
        onItemSelected(this.selectedItem as T, event)
    }
    return this
}
// Issue is not specific to JComboBox, calling the bellow extension function on `JButton` instance will throw a similar error

inline fun <reified T: JButton> T.onClicked(crossinline onClicked: (item: String?, event: ActionEvent) -> Unit): T {
    addActionListener { event ->
        onClicked("Test", event)
    }
    return this
}
// Won't throw any error

inline fun <reified T: JButton> T.onClicked(crossinline onClicked: (item: String?, event: ActionEvent) -> Unit): T {
    return this
}
// Will throw AbstractMethodError as before

inline fun <reified T: JButton> T.onClicked(crossinline onClicked: (item: String?, event: ActionEvent) -> Unit): T {
    addActionListener {}
    return this
}
Example of the throwing error
Exception in thread "AWT-EventQueue-0" java.lang.AbstractMethodError: Receiver class MainKt$onClicked$1 does not define or inherit an implementation of the resolved method 'abstract void actionPerformed(java.awt.event.ActionEvent)' of interface java.awt.event.ActionListener.
        at java.desktop/javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1972)
        at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2314)
        at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:407)
        at java.desktop/javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:262)
        at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:279)
        at java.desktop/java.awt.Component.processMouseEvent(Component.java:6621)
        at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3398)
        at java.desktop/java.awt.Component.processEvent(Component.java:6386)
        at java.desktop/java.awt.Container.processEvent(Container.java:2266)
        at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4996)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
        at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
        at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
        at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
        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.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:98)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
        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:744)
        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)

Steps to Reproduce:

Details steps (From Scratch)

This example might be specific to JComboBox component, the same issue can be encountered with JButton and other Java Swing components that may use java.awt.event.ActionListener and java.awt.event.ActionEvent

  1. Create a new Kotlin project, I suggest using IntelliJ IDEA Community Edition, Choosing Kotlin and Gradle as the build System, and any JDK version that supports Proguard, for this example, I used both 11 and 17
  2. Use the latest version of Gradle and Kotlin:
    1. Gradle to 8.8 by running ./gradlew wrapper --gradle-version=8.8
    2. Kotlin by going to build.gradle or build.gradle.kts (if you use Gradle Kotlin DSL) and update to 2.0.0:
    plugins {
    kotlin("jvm") version "2.0.0"
    }
    
  3. Configure application plugin, add it in the plugins {} block, and configure it:
application {
    mainClass = "MainKt"
}

if you create the project with IntelliJ IDEA Community Edition, make sure to go to src/main/kotlin/Main.kt and remove the package org.example at the start of the file

  1. Use application-kotlin as an example to get started with Proguard, create proguard.pro file in the root project folder:
-verbose

-keepattributes *Annotation*

-keep class kotlin.Metadata { *; }

# Entry point to the app.
-keep class MainKt { *; }

I wasn't able to use anything that's from Kotlin like extension functions or keywords like suspend or Imports from Kotlin even when using:

// This will include the Kotlin library jars
injars(sourceSets.main.get().compileClasspath)

So I had to use Shadow JAR to build the Fat JAR, in addition to that the JARs that are built by Gradle task jar are usually used by Gradle for Gradle modules for example, disabling them will cause issues with Gradle

The build.gradle.kts (I'm using Gradle Kotlin DSL in this case) will be something like this:

plugins {
    application
    kotlin("jvm") version "2.0.0"
    id("com.github.johnrengelman.shadow") version "8.1.1"
}

repositories {
    mavenCentral()
}

kotlin {
    jvmToolchain(17)
}

application {
    mainClass = "MainKt"
}

buildscript {
    repositories {
        mavenCentral()
        google()
    }
    dependencies {
        classpath("com.guardsquare:proguard-gradle:7.5.0")
    }
}

tasks.register<proguard.gradle.ProGuardTask>("proguard") {
    dependsOn(tasks.shadowJar)
    configuration("proguard.pro")

    injars(tasks.shadowJar.flatMap { it.archiveFile })
    outjars(project.layout.buildDirectory.file("proguard-obfuscated.jar"))

    val javaHome = System.getProperty("java.home")
    // Automatically handle the Java version of this build.
    if (System.getProperty("java.version").startsWith("1.")) {
        // Before Java 9, the runtime classes were packaged in a single jar file.
        libraryjars("$javaHome/lib/rt.jar")
    } else {
        // As of Java 9, the runtime classes are packaged in modular jmod files.
        libraryjars(
            // filters must be specified first, as a map
            mapOf(
                "jarfilter" to "!**.jar",
                "filter" to "!module-info.class"
            ),
            "$javaHome/jmods/java.base.jmod"
        )
    }

    allowaccessmodification()
    dontobfuscate() // This will help us seeing the stack trace and errors in details
    dontoptimize()
    verbose()

    // This will include the Kotlin library jars, Shadow JAR already includes it
    injars(sourceSets.main.get().compileClasspath)

    printmapping(project.layout.buildDirectory.file("proguard-mapping.txt"))
}

If you try to run the Proguard JAR with the following code snippet in Main.kt

import kotlin.random.Random

fun main() {
    println("Hello, World! ${Random.nextInt()}")
}

The application will work as expected, once you start using anything from Java Swing you will get warnings and errors, Swing uses reflections, and the code will usually be in the JDK or JRE, if I understand Proguard correctly, it should work as the code of Java Swing won't be in the application as a result it won't be shrunk or minimized by Proguard,

If you update Main.kt to the following:

import java.awt.Dimension
import javax.swing.JFrame

fun main() {
    JFrame().apply {
        size = Dimension(800, 600)
        defaultCloseOperation = JFrame.EXIT_ON_CLOSE
        isVisible = true
    }
}

You will get warnings when trying to build the minimized JAR by Proguard, either using ignorewarnings or adding the following to proguard.pro file:

# Ignore all the warnings from Java Swing and related packages
-dontwarn javax.swing.**
-dontwarn java.awt.**

You might need to ignore more if you're using other Java swing-related Packages.

Or include the following in your Proguard task:

libraryjars(
                mapOf("jarfilter" to "!**.jar", "filter" to "!module-info.class"),
                "$javaHome/jmods/java.desktop.jmod",
)

The application now works perfectly as expected, the size is less than before (from 1.7MB to 15,477 bytes (16 KB on disk))

Let's try to move to more advance example:

import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.JComboBox
import javax.swing.JFrame

fun main() {
    JFrame().apply {
        size = Dimension(800, 600)
        defaultCloseOperation = JFrame.EXIT_ON_CLOSE
        isVisible = true

        val comboBox = JComboBox<String>().apply {
            listOf("Item 1", "Item 2", "Item 3").forEach { item ->
                addItem(item)
            }
        }
        comboBox.addActionListener { _ ->
            println(comboBox.selectedItem)
        }

        add(comboBox, BorderLayout.CENTER)
        validate()
    }
}

Running the application, you won't notice any issues,

add the following extension function to Main.kt:

inline fun <reified T> JComboBox<T>.getSelectedItemSafe(): T? {
    if (this.selectedItem !is T) {
        return null
    }
    return this.selectedItem as T
}

And use it inside the addActionListener lambda block:

val comboBox = JComboBox<String>().apply {
            listOf("Item 1", "Item 2", "Item 3").forEach { item ->
                addItem(item)
            }
        }
        comboBox.addActionListener {
            println("Selected item: ${comboBox.getSelectedItemSafe()}")
        }

Running the application and everything seems to work.

Add another extension function:

inline fun <reified T> JComboBox<T>.onItemSelected(crossinline onItemSelected: (item: T?, event: ActionEvent) -> Unit): JComboBox<T> {
    addActionListener { event ->
        onItemSelected(this.getSelectedItemSafe(), event)
    }
    return this
}

it can be used with any instance of JComboBox or its subclasses. Calling it from comboBox:

val comboBox = JComboBox<String>().apply {
            listOf("Item 1", "Item 2", "Item 3").forEach { item ->
                addItem(item)
            }
        }
        comboBox.onItemSelected { item, _ ->
            println("Selected item: $item")
        }

Launch the Proguard JAR and change the dropdown item in the swing window, you will get an exception:

Exception in thread "AWT-EventQueue-0" java.lang.AbstractMethodError: Receiver class MainKt$main$lambda$3$$inlined$onItemSelected$1 does not define or inherit an implementation of the resolved method 'abstract void actionPerformed(java.awt.event.ActionEvent)' of interface java.awt.event.ActionListener.
        at java.desktop/javax.swing.JComboBox.fireActionEvent(JComboBox.java:1294)
        at java.desktop/javax.swing.JComboBox.setSelectedItem(JComboBox.java:619)
        at java.desktop/javax.swing.JComboBox.setSelectedIndex(JComboBox.java:654)
        at java.desktop/javax.swing.plaf.basic.BasicComboPopup$Handler.mouseReleased(BasicComboPopup.java:946)
        at java.desktop/java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:298)
        at java.desktop/java.awt.Component.processMouseEvent(Component.java:6621)
        at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3398)
        at java.desktop/com.apple.laf.AquaComboBoxPopup$1.processMouseEvent(AquaComboBoxPopup.java:178)
        at java.desktop/java.awt.Component.processEvent(Component.java:6386)
        at java.desktop/java.awt.Container.processEvent(Container.java:2266)
        at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4996)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
        at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
        at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
        at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
        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.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:98)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
        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:744)
        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)

The issue won't occur when launching the unminimized JAR directly, I don't understand why this is an issue, all those imports are part of the JDK and usually won't be included in the JAR file, I didn't use any usage of reflections or anything else that Proguard can't know (like loading a class dynamically) if it should be removed or keep, I'm interested to know more about Proguard

It's not directly an issue with Proguard or Java Swing, instead, it will only happen when using crossinline, reified and inline keywords in the function:

inline fun <reified T> JComboBox<T>.onItemSelected(crossinline onItemSelected: (item: T?, event: ActionEvent) -> Unit): JComboBox<T> {
    addActionListener { event ->
        onItemSelected(this as T, event)
    }
    return this
}

A workaround is to update the function to remove them:

fun <T> JComboBox<T>.onItemSelected(onItemSelected: (item: T?, event: ActionEvent) -> Unit): JComboBox<T> {
    addActionListener { event ->
        @Suppress("UNCHECKED_CAST")
        onItemSelected(this as T, event)
    }
    return this
}

The application will work without them

Quickly reproduce the issue
  1. Download the project SwingKotlinExtensionsError.zip

Which has minimal steps to reproduce the issue. it only changes proguard.pro, build.gradle.kts, src/main/kotlin/Main.kt and Gradle wrapper

  1. Unzip the folder and navigate to the folder and then run ./gradlew proguard --info && java -jar ./build/proguard-obfuscated.jar if you're on Linux or macOS, on Windows replace gradlew with gradlew.bat

  2. Once the window is opened, change the dropdown item to any other item, you will notice the dropdown won't be closed and in the console, you will get an exception:

Exception in thread "AWT-EventQueue-0" java.lang.AbstractMethodError: Receiver class MainKt$main$lambda$3$$inlined$onItemSelected$1 does not define or inherit an implementation of the resolved method 'abstract void actionPerformed(java.awt.event.ActionEvent)' of interface java.awt.event.ActionListener.
        at java.desktop/javax.swing.JComboBox.fireActionEvent(JComboBox.java:1294)
        at java.desktop/javax.swing.JComboBox.setSelectedItem(JComboBox.java:619)
        at java.desktop/javax.swing.JComboBox.setSelectedIndex(JComboBox.java:654)
        at java.desktop/javax.swing.plaf.basic.BasicComboPopup$Handler.mouseReleased(BasicComboPopup.java:946)
        at java.desktop/java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:298)
        at java.desktop/java.awt.Component.processMouseEvent(Component.java:6621)
        at java.desktop/javax.swing.JComponent.processMouseEvent(JComponent.java:3398)
        at java.desktop/com.apple.laf.AquaComboBoxPopup$1.processMouseEvent(AquaComboBoxPopup.java:178)
        at java.desktop/java.awt.Component.processEvent(Component.java:6386)
        at java.desktop/java.awt.Container.processEvent(Container.java:2266)
        at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4996)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4948)
        at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Container.java:4575)
        at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Container.java:4516)
        at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2310)
        at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2780)
        at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
        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.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:98)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
        at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
        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:744)
        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)

Expected Behavior:

To be able to run the application with Proguard JAR without AbstractMethodError

Actual Behavior:

Encountered AbstractMethodError when running the Proguard JAR

Workarounds

One of the workarounds will solve it

  1. Remove all usages of reified, inline, and crossinline keywords in the extension function
  2. Remove addActionListener {} in the Kotlin extension function on the Java swing component
  3. Update the rules to keep the class, the Kotlin file, or the function in proguard.pro

An example without encountering the issue:

fun <T> JComboBox<T>.onItemSelected(onItemSelected: (item: T?, event: ActionEvent) -> Unit): JComboBox<T> {
    addActionListener { event ->
        @Suppress("UNCHECKED_CAST")
        onItemSelected(this.selectedItem as T, event)
    }
    return this
}

Another example to encounter the issue:

inline fun <reified T> JComboBox<T>.onItemSelected(crossinline onItemSelected: (item: T?, event: ActionEvent) -> Unit): JComboBox<T> {
    addActionListener { event ->
        onItemSelected(this.getSelectedItemSafe(), event)
    }
    return this
}

inline fun <reified T> JComboBox<T>.getSelectedItemSafe(): T? {
    if (this.selectedItem !is T) {
        return null
    }
    return this.selectedItem as T
}

Images

Image

Once you change the dropdown item, you will get an exception, more details in Details steps (From Scratch)

image

Environment

JDK: 11 or 17
ProGuard: 7.5.0
Gradle: 8.8
Kotlin: 2.0.0
Operating System: macOS 14.2.1 (23C71)

Additional Context

  • Gradle Kotlin DSL is utilized.
  • Shadow JAR is used for creating the fat JAR. however the issue won't be encountered when executing the Shadow JAR directly, the minimization of Shadow JAR is disabled, it is only used for support using Kotlin-specific features and imports inside the code by building fat JAR for this example.
  • This issue is specific to Java Swing, the issue might be encountered outside of Java Swing with a similar use case
  • Proguard obfuscation is disabled to help debug the stack trace easily.
  • This example is based on application-kotlin and gradle-kotlin-dsl
  • This issue is not specific to Kotlin 2.0.0, Proguard 7.5.0 or Java 17, I was able to reproduce the same issue on Kotlin 1.9.24, Proguard 7.4.1 and Java 11

Thank you for your efforts.

@EchoEllet
Copy link
Contributor Author

EchoEllet commented Jun 6, 2024

Not directly related: it seems this line is needed even when using Shadow JAR:

       // This will include the Kotlin library jars, it will be needed even though Shadow JAR already includes it
        // to solve all warnings that are related to Kotlin without ignoring them
        injars(sourceSets.main.get().compileClasspath)

To solve all warnings coming from Kotlin library without using -dontwarn kotlin.** in proguard.pro

@EchoEllet
Copy link
Contributor Author

It seems this issue is from my side (incorrect configurations), I didn't include java.desktop.jmod from the JDK using libraryjars which is needed to avoid such issues, the final Proguard task will be:

tasks.register<proguard.gradle.ProGuardTask>("proguard") {
    dependsOn(tasks.shadowJar)
    configuration("proguard.pro")

    injars(tasks.shadowJar.flatMap { it.archiveFile })
    outjars(project.layout.buildDirectory.file("proguard-obfuscated.jar"))

    val javaHome = System.getProperty("java.home")
    // Automatically handle the Java version of this build.
    if (System.getProperty("java.version").startsWith("1.")) {
        // Before Java 9, the runtime classes were packaged in a single jar file.
        libraryjars("$javaHome/lib/rt.jar")
    } else {
        // As of Java 9, the runtime classes are packaged in modular jmod files.
        libraryjars(
            // filters must be specified first, as a map
            mapOf("jarfilter" to "!**.jar", "filter" to "!module-info.class"),
            "$javaHome/jmods/java.base.jmod"
        )
        // Needed to support Java Swing/Desktop
        libraryjars(
            mapOf("jarfilter" to "!**.jar", "filter" to "!module-info.class"),
            "$javaHome/jmods/java.desktop.jmod",
        )
    }

    allowaccessmodification()
    dontobfuscate()
    dontoptimize()
    verbose()

    // This will include the Kotlin library jars, Shadow JAR already includes it
    libraryjars(sourceSets.main.get().compileClasspath)

    printmapping(project.layout.buildDirectory.file("proguard-mapping.txt"))
}

And proguard.pro:

-verbose

-keepattributes *Annotation*

-keep class kotlin.Metadata { *; }

# Entry point to the app.
-keep class MainKt { *; }

This issue can still be useful for anyone who has similar issues as it solves other unrelated issues and can be useful to get started with Proguard on Kotlin/JVM in general.

@EchoEllet EchoEllet closed this as not planned Won't fix, can't repro, duplicate, stale Jun 6, 2024
@mrjameshamilton
Copy link
Collaborator

Hi @ellet0 ! Glad you solved your issue!

In these kind of cases, you would normally see "Referenced class not found" warnings or similar which by default would fail the build.

But since you added -dontwarn javax.swing.** -dontwarn java.awt.** the build was not failing; it's usually fine to ignore some warnings but they should give you an indication of classes that are missing and that should potentially be provided as libraryjars.

@EchoEllet
Copy link
Contributor Author

But since you added -dontwarn javax.swing.** -dontwarn java.awt.** the build was not failing; it's usually fine to ignore some warnings but they should give you an indication of classes that are missing and that should potentially be provided as libraryjars.

Agreed. which made it harder to debug and caused unexpected behavior.

It's a common issue since many tutorials and examples online use ignorewarnings or ignore the warnings from the JDK which is something I didn't quite understand

I appreciate your guidance on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants