From 59b7dc03168e3d61daa8f81fb3e2dfacc10c423c Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Thu, 22 Aug 2024 09:09:43 -0700 Subject: [PATCH] Add @Keep and "Keep everything" Keep everything will decide whether R8 should keep all public symbols by default. If unchecked, you can now use @Keep to choose what to keep. --- build.gradle.kts | 2 +- .../romainguy/kotlin/explorer/Disassembly.kt | 37 +++++++++++- .../kotlin/explorer/KotlinExplorer.kt | 5 +- .../dev/romainguy/kotlin/explorer/State.kt | 11 +++- .../kotlin/explorer/build/DexCompiler.kt | 56 ++++++++++++++----- .../kotlin/explorer/build/KolinCompiler.kt | 5 +- 6 files changed, 96 insertions(+), 20 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7dfd3678..a69353d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,7 +18,7 @@ repositories { maven("https://packages.jetbrains.team/maven/p/kpm/public/") } -version = "1.4.7" +version = "1.5.0" val baseName = "Kotlin Explorer" kotlin { diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt index 63130b25..b1ce29a3 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt @@ -60,6 +60,7 @@ suspend fun buildAndRun( val path = directory.resolve("KotlinExplorer.kt") Files.writeString(path, source) + writeSupportFiles(directory) val kotlinc = KotlinCompiler(toolPaths, directory).compile(path) @@ -101,7 +102,8 @@ suspend fun buildAndDisassemble( onOat: (CodeContent) -> Unit, onLogs: (AnnotatedString) -> Unit, onStatusUpdate: (String, Float) -> Unit, - optimize: Boolean + optimize: Boolean, + keepEverything: Boolean ) = coroutineScope { val ui = currentCoroutineContext() @@ -117,6 +119,7 @@ suspend fun buildAndDisassemble( val path = directory.resolve("KotlinExplorer.kt") Files.writeString(path, source) + writeSupportFiles(directory) val kotlinc = KotlinCompiler(toolPaths, directory).compile(path) @@ -149,7 +152,7 @@ suspend fun buildAndDisassemble( val dexCompiler = DexCompiler(toolPaths, directory, r8rules) - val dex = dexCompiler.buildDex(optimize) + val dex = dexCompiler.buildDex(optimize, keepEverything) if (dex.exitCode != 0) { updater.addJob(launch(ui) { @@ -271,6 +274,36 @@ private fun cleanupClasses(directory: Path) { } } +private fun writeSupportFiles(directory: Path) { + Files.writeString( + directory.resolve("Keep.kt"), + """ +import java.lang.annotation.ElementType.ANNOTATION_TYPE +import java.lang.annotation.ElementType.CONSTRUCTOR +import java.lang.annotation.ElementType.FIELD +import java.lang.annotation.ElementType.METHOD +import java.lang.annotation.ElementType.PACKAGE +import java.lang.annotation.ElementType.TYPE + +@Retention(AnnotationRetention.BINARY) +@Target( + AnnotationTarget.FILE, + AnnotationTarget.ANNOTATION_CLASS, + AnnotationTarget.CLASS, + AnnotationTarget.ANNOTATION_CLASS, + AnnotationTarget.CONSTRUCTOR, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.FIELD +) +@Suppress("DEPRECATED_JAVA_ANNOTATION", "SupportAnnotationUsage") +@java.lang.annotation.Target(PACKAGE, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, METHOD, FIELD) +public annotation class Keep + """.trimIndent() + ) +} + private fun showError(error: String) = buildAnnotatedString { withStyle(SpanStyle(ErrorColor)) { append(error) diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt index af239c9b..ece3ce9d 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.input.key.Key.Companion.C import androidx.compose.ui.input.key.Key.Companion.D import androidx.compose.ui.input.key.Key.Companion.F import androidx.compose.ui.input.key.Key.Companion.G +import androidx.compose.ui.input.key.Key.Companion.K import androidx.compose.ui.input.key.Key.Companion.L import androidx.compose.ui.input.key.Key.Companion.O import androidx.compose.ui.input.key.Key.Companion.P @@ -438,7 +439,8 @@ private fun FrameWindowScope.MainMenu( onOatUpdate, onLogsUpdate, onStatusUpdate, - explorerState.optimize + explorerState.optimize, + explorerState.keepEverything ) } } @@ -508,6 +510,7 @@ private fun FrameWindowScope.MainMenu( ) Separator() MenuCheckboxItem("Optimize with R8", CtrlShift(O), explorerState::optimize) + MenuCheckboxItem("Keep everything", CtrlShift(K), explorerState::keepEverything) MenuCheckboxItem("Build on Startup", shortcut = null, explorerState::autoBuildOnStartup) MenuItem( "Build & Disassemble", diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt index d9151aba..78f7a8c2 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt @@ -28,6 +28,7 @@ import kotlin.io.path.readLines private const val AndroidHome = "ANDROID_HOME" private const val KotlinHome = "KOTLIN_HOME" private const val Optimize = "OPTIMIZE" +private const val KeepEverything = "KEEP_EVERYTHING" private const val R8Rules = "R8_RULES" private const val AutoBuildOnStartup = "AUTO_BUILD_ON_STARTUP" private const val Presentation = "PRESENTATION" @@ -55,6 +56,7 @@ class ExplorerState { var kotlinHome by StringState(KotlinHome, System.getenv("KOTLIN_HOME") ?: System.getProperty("user.home")) var toolPaths by mutableStateOf(createToolPaths()) var optimize by BooleanState(Optimize, true) + var keepEverything by BooleanState(KeepEverything, true) var r8Rules by StringState(R8Rules, "") var autoBuildOnStartup by BooleanState(AutoBuildOnStartup, false) var presentationMode by BooleanState(Presentation, false) @@ -151,5 +153,12 @@ private fun readSettings(file: Path): MutableMap { private fun readSourceCode(toolPaths: ToolPaths) = if (toolPaths.sourceFile.exists()) { Files.readString(toolPaths.sourceFile) } else { - "fun square(a: Int): Int {\n return a * a\n}\n" + """ + // NOTE: If Build > Keep everything is *not* checked, used the @Keep + // annotation to keep the classes/methods/etc. you want to disassemble + fun square(a: Int): Int { + return a * a + } + + """.trimIndent() } diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/DexCompiler.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/DexCompiler.kt index 8f58e10d..adeb4efb 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/DexCompiler.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/DexCompiler.kt @@ -16,6 +16,7 @@ package dev.romainguy.kotlin.explorer.build +import dev.romainguy.kotlin.explorer.ProcessResult import dev.romainguy.kotlin.explorer.ToolPaths import dev.romainguy.kotlin.explorer.process import java.nio.file.Files @@ -23,7 +24,9 @@ import java.nio.file.Path import kotlin.io.path.* class DexCompiler(private val toolPaths: ToolPaths, private val outputDirectory: Path, private val r8rules: String) { - suspend fun buildDex(optimize: Boolean) = process(*buildDexCommand(optimize), directory = outputDirectory) + suspend fun buildDex(optimize: Boolean, keepEverything: Boolean): ProcessResult { + return process(*buildDexCommand(optimize, keepEverything), directory = outputDirectory) + } suspend fun dumpDex() = process( toolPaths.dexdump.toString(), @@ -33,8 +36,8 @@ class DexCompiler(private val toolPaths: ToolPaths, private val outputDirectory: ) @OptIn(ExperimentalPathApi::class) - private fun buildDexCommand(optimize: Boolean): Array { - writeR8Rules() + private fun buildDexCommand(optimize: Boolean, keepEverything: Boolean): Array { + writeR8Rules(keepEverything) return buildList { add("java") @@ -69,20 +72,47 @@ class DexCompiler(private val toolPaths: ToolPaths, private val outputDirectory: }.toTypedArray() } - private fun writeR8Rules() { + private fun writeR8Rules(keepEverything: Boolean) { // Match $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt Files.writeString( outputDirectory.resolve("rules.txt"), - """ - -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* - -optimizationpasses 5 - -allowaccessmodification - -dontpreverify - -dontobfuscate - -keep,allowoptimization class !kotlin.**,!kotlinx.** { - ; + buildString { + append( + """ + -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* + -optimizationpasses 5 + -allowaccessmodification + -dontpreverify + -dontobfuscate + """.trimIndent() + ) + if (keepEverything) { + append( + """ + -keep,allowoptimization class !kotlin.**,!kotlinx.** { + ; + } + """.trimIndent() + ) + } else { + append( + """ + -keep,allowobfuscation @interface Keep + -keep @Keep class * {*;} + -keepclasseswithmembers class * { + @Keep ; + } + -keepclasseswithmembers class * { + @Keep ; + } + -keepclasseswithmembers class * { + @Keep (...); + } + """.trimIndent() + ) } - """.trimIndent() + r8rules + append(r8rules) + } ) } } diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/KolinCompiler.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/KolinCompiler.kt index af9ec77d..395718b6 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/KolinCompiler.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/build/KolinCompiler.kt @@ -27,13 +27,14 @@ class KotlinCompiler(private val toolPaths: ToolPaths, private val outputDirecto private fun buildCompileCommand(file: Path): Array { val command = mutableListOf( toolPaths.kotlinc.toString(), - file.toString(), "-Xmulti-platform", "-Xno-param-assertions", "-Xno-call-assertions", "-Xno-receiver-assertions", "-classpath", - (toolPaths.kotlinLibs + listOf(toolPaths.platform)).joinToString(":") { jar -> jar.toString() } + (toolPaths.kotlinLibs + listOf(toolPaths.platform)).joinToString(":") { jar -> jar.toString() }, + file.toString(), + file.parent.resolve("Keep.kt").toString() ) return command.toTypedArray()