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

FR: Add support for running R8 on JVM Desktop

Open ScottPierce opened this issue 3 years ago • 12 comments

The performance benefits for R8 and Compose on Android is significant (supposedly because of its optimizations of Lambdas). I recently found out that R8 can just be run on Java class files. It'd be nice to be able to add support to use R8 directly from the compose Gradle plugin on Compose Desktop projects, so that people can easily take advantage of the performance improvements on Compose Desktop.

ScottPierce avatar Dec 15 '21 21:12 ScottPierce

Would love to see R8 support for code obfuscation. This is currently blocking me from publishing my app.

Thomas-Vos avatar Dec 19 '21 01:12 Thomas-Vos

@Thomas-Vos look at the link I posted where a gradle project has a task to run r8 on it.

You should be able to do it yourself in 30 minutes or so:

  1. Use the Shadow Jar Gradle plugin to create an Uber / Fat Jar. This is a jar that contains all your other Jar files.
  2. Run R8 on it similar to my above link
  3. Swap out your jar files in your packaged application with the newly optimized / obfuscated single jar file. There is also a text file that lives alongside the jar files that define the running classpath. Edit it to remove all the jar files from the classpath, and ensure your optimized fat jar is the only jar in that file now.

ScottPierce avatar Dec 20 '21 02:12 ScottPierce

@ScottPierce Is there a way to run R8 selectively on jars e.g. not to obfuscate (certain) third-parties instead of user-jar way?

This has to be somewhat easily hackable to achieve that, just the 30 minutes was not enough for me with my gradle-fu. Generally we would all appreciate if one would share sample gist gradle.kts file for CfD app so that we don't have to arrive to the same solution independently 🙏 .

mcpiroman avatar Dec 20 '21 22:12 mcpiroman

R8 has similar functionality and accepts the same configs as Proguard. You can tell it to ignore certain packages.

ScottPierce avatar Dec 21 '21 00:12 ScottPierce

I finally managed to do that with ProGuard (example setup), without uberjar though. It's actually not that hard. I guess it should be similar for R8.

mcpiroman avatar Mar 22 '22 13:03 mcpiroman

No uber jar has benefits imo. While doing updates, that would allow you to only swap out the file that's been changed. Uber jars with compose can be 50+ MB

ScottPierce avatar Mar 22 '22 18:03 ScottPierce

Another point that was brought up about R8 - R8 has the ability to read rules directly from jars, meaning that its a significantly better user experience to build a library ecosystem with.

ScottPierce avatar Jan 06 '23 00:01 ScottPierce

Taking into account all the recent problems with ProGuard, e.g., https://github.com/JetBrains/compose-multiplatform/issues/3387 https://github.com/JetBrains/compose-multiplatform/pull/3408#discussion_r1274012736 https://github.com/square/okio/issues/1298 it may be worthwile to reconsider supporting R8 on desktops again.

mipastgt avatar Jul 26 '23 09:07 mipastgt

Dropping this tweet here. It might be useful.

YektaDev avatar Jul 26 '23 13:07 YektaDev

bump

gogopro-dev avatar Aug 02 '23 22:08 gogopro-dev

Compose Desktop seems to be speed-running the problems of Android from a decade past by not using R8.

https://github.com/Kotlin/kotlinx.coroutines/issues/4025

JakeWharton avatar Feb 01 '24 15:02 JakeWharton

I think right now the biggest problem is that whilst the Compose/Desktop documentation states that proguard is supported it is not really being fully used even on official-ish examples. Even if rules are not auto-bundled, just enabling proguard (including obfuscation) for release jvm builds would uncover many (if not most) issues.

mikedawson avatar Feb 02 '24 09:02 mikedawson

Another point that was brought up about R8 - R8 has the ability to read rules directly from jars, meaning that its a significantly better user experience to build a library ecosystem with.

This feature is usually easy to implement. Use JarFile to read the fat JAR file. You will usually find the rules of the supported libraries in META-INF/proguard of the JAR file

Which will be included by libraries even if you don't use Proguard.

This approach is less flexible and could produce issues later. For most use cases and projects, it works as expected.

Unless I'm mistaken, R8 uses the rules from META-INF/com.android.tools/r8 and fallback to META-INF/proguard

Examples:

JarFile(shadowJarFile.get().asFile).use { jarFile ->
            val generatedProguardFile =
                project.layout.buildDirectory.file("proguard/generated-proguard-libraries-rules.pro").get()
                    .asFile
            if (!generatedProguardFile.exists()) {
                generatedProguardFile.parentFile.mkdir()
            }
            val generatedRulesFiles =
                jarFile.entries().asSequence()
                    .filter { it.name.startsWith("META-INF/proguard") && !it.isDirectory }
                    .map { entry ->
                        jarFile.getInputStream(entry).bufferedReader().use { reader ->
                            Pair(reader.readText(), entry)
                        }
                    }
                    .toList()
            generatedProguardFile.bufferedWriter().use { bufferedWriter ->
                bufferedWriter.appendLine("# GENERATED FILE - manual changes will be overwritten")
                bufferedWriter.appendLine()
                generatedRulesFiles.forEach { (rulesContent, rulesFileEntry) ->
                    bufferedWriter.appendLine("# START of ($rulesFileEntry)")
                    bufferedWriter.appendLine()
                    bufferedWriter.appendLine(rulesContent)
                    bufferedWriter.appendLine("# END of ($rulesFileEntry)")
                    bufferedWriter.appendLine()
                }
            }
            configuration(generatedProguardFile)
        }

Which will have all rules from all libraries in a single file.

Or separate the rules for each library:

JarFile(shadowJarFile.get().asFile).use { jarFile ->
            val generatedRulesFiles =
                jarFile.entries().asSequence()
                    .filter { it.name.startsWith("META-INF/proguard") && !it.isDirectory }
                    .map { entry ->
                        jarFile.getInputStream(entry).bufferedReader().use { reader ->
                            Pair(reader.readText(), entry)
                        }
                    }
                    .toList()

            val buildProguardDirectory = project.layout.buildDirectory.dir("proguard").get().asFile
            if (!buildProguardDirectory.exists()) {
                buildProguardDirectory.mkdir()
            }
            generatedRulesFiles.forEach { (rulesContent, rulesFileEntry) ->
                val rulesFileNameWithExtension = rulesFileEntry.name.substringAfterLast("/")
                val generatedProguardFile = File(buildProguardDirectory, "generated-$rulesFileNameWithExtension")
                if (!generatedProguardFile.exists()) {
                    generatedProguardFile.createNewFile()
                }
                generatedProguardFile.bufferedWriter().use { bufferedWriter ->
                    bufferedWriter.appendLine("# Generated file from ($rulesFileEntry) - manual changes will be overwritten")
                    bufferedWriter.appendLine()

                    bufferedWriter.appendLine(rulesContent)
                }

                configuration(generatedProguardFile)
            }
        }

EchoEllet avatar Jun 08 '24 14:06 EchoEllet

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

okushnikov avatar Jul 14 '24 14:07 okushnikov

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

okushnikov avatar Jul 14 '24 14:07 okushnikov

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

okushnikov avatar Jul 14 '24 14:07 okushnikov