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
15.24k stars 1.11k forks source link

Minimize Kotlin Standard library Option #4948

Open ellet0 opened 2 weeks ago

ellet0 commented 2 weeks ago

Add an option for minimizing the imports from Kotlin package kotlin.** for Compose Desktop Gradle Plugin without breaking changes

compose.desktop {
    application {
        buildTypes.release.proguard {
            // Default to false to avoid breaking changes
            minimizeKotlinStandardLibrary.set(true)
        }
    }
}

I created a new example using kmp.jetbrains.com and tested the local changes with the default example:


import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import desktopproguardtest.composeapp.generated.resources.Res
import desktopproguardtest.composeapp.generated.resources.compose_multiplatform
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview

@Composable
@Preview
fun App() {
    MaterialTheme {
        var showContent by remember { mutableStateOf(false) }
        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            Button(onClick = { showContent = !showContent }) {
                Text("Click me! ${Greeting::class.qualifiedName}")
            }
            AnimatedVisibility(showContent) {
                val greeting = remember { Greeting().greet() }
                Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
                    Image(painterResource(Res.drawable.compose_multiplatform), null)
                    Text("Compose: $greeting")
                }
            }
        }
    }
}

Before minimizing Kotlin standard library: telegram-cloud-document-2-5413415490418658153

After: telegram-cloud-document-2-5413415490418658156

The provided screenshots are on macOS DMG using packageReleaseDeb and the difference will be different based on the method and the operating system:

You should save more depending on the imports. In this example, we don't use much, and it's mostly from the libraries.

I have removed

-keep class kotlin.** { *; }

will be included if minimizeKotlinStandardLibrary is false, which is the default. If you're interested in this PR, before merging it, we should make sure of the following:

  1. The name minimizeKotlinStandardLibrary should be renamed for some reasons, one of them, could indicate it will be minimized, and only the used/required imports will be included, which might be not the case as it currently only avoids including -keep class kotlin.** { *; } in the rules, if we will stick to this behavior then the name could be changed to something else, like keepEntireKotlinStdLib or retainFullKotlinStdLib or something similar to indicate rules will be required depending on the usage, and will add a swcond option like includeDefaultKotlinStdLibRules or includeCommonKotlinStdLibRules which will include some common rules such as:
    -keep class kotlin.Metadata
    -keep class kotlin.reflect.jvm.internal.** { *; }
    -keep class kotlin.text.RegexOption { *; }

    See Progaurd Kotlin Manual for more details.

Another option is to provide some common/required rules for the Kotlin standard library and provide a function or option to disable this behavior.

I suggest keeping standard library or std lib or something similar in the name as this will not take kotlinx packages into account to make it clear and understandable for the developer, or we could stick to only having one option (e.g, minimizeKotlinStandardLibrary), and make it nullable boolean if it's null, then it will ignore the call or if true, will use -keep class kotlin.** { *; }, or if false then either ignore the call (in that case the value should not accept null as value) or provide some common/default rules

minimizeKotlinStandardLibrary.orNull?.let { minimize ->
    if (!minimize) {

        return@let
    }
    writer.writeLn("-keep class kotlin.Metadata")
    writer.writeLn("-keep class kotlin.reflect.jvm.internal.** { *; }")
    writer.writeLn("-keep class kotlin.text.RegexOption { *; }")
    // More or include all rules from a file (e.g, default-kotlin-std-lib-rules.pro)
}
  1. It looks like we need some additional rules if Kotlin standard library is minimized as I got some notes from Proguard:
Log ```console Note: androidx.lifecycle.ClassesInfoCache calls 'Method.getAnnotation' Note: androidx.compose.ui.text.platform.ReflectionUtil$findAssignableField$result$1 calls 'Field.getType' Note: androidx.lifecycle.viewmodel.internal.JvmViewModelProviders accesses a declared constructor '()' dynamically Note: the configuration keeps the entry point 'kotlin.coroutines.AbstractCoroutineContextElement { AbstractCoroutineContextElement(kotlin.coroutines.CoroutineContext$Key); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext$Key' Note: the configuration keeps the entry point 'kotlin.coroutines.AbstractCoroutineContextElement { kotlin.coroutines.CoroutineContext$Element get(kotlin.coroutines.CoroutineContext$Key); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext$Key' Note: the configuration keeps the entry point 'kotlin.coroutines.AbstractCoroutineContextElement { java.lang.Object fold(java.lang.Object,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'kotlin.coroutines.AbstractCoroutineContextElement { kotlin.coroutines.CoroutineContext minusKey(kotlin.coroutines.CoroutineContext$Key); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext$Key' Note: the configuration keeps the entry point 'kotlin.coroutines.AbstractCoroutineContextElement { kotlin.coroutines.CoroutineContext plus(kotlin.coroutines.CoroutineContext); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext' Note: the configuration keeps the entry point 'kotlinx.coroutines.CoroutineDispatcher { boolean isDispatchNeeded(kotlin.coroutines.CoroutineContext); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext' Note: the configuration keeps the entry point 'kotlinx.coroutines.CoroutineDispatcher { void dispatch(kotlin.coroutines.CoroutineContext,java.lang.Runnable); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext' Note: the configuration keeps the entry point 'kotlinx.coroutines.CoroutineDispatcher { void dispatchYield(kotlin.coroutines.CoroutineContext,java.lang.Runnable); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext' Note: the configuration keeps the entry point 'kotlinx.coroutines.CoroutineDispatcher { kotlin.coroutines.CoroutineContext$Element get(kotlin.coroutines.CoroutineContext$Key); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext$Key' Note: the configuration keeps the entry point 'kotlinx.coroutines.CoroutineDispatcher { kotlin.coroutines.CoroutineContext minusKey(kotlin.coroutines.CoroutineContext$Key); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext$Key' Note: the configuration keeps the entry point 'org.jetbrains.skia.Actuals_jvmKt { void commonSynchronized(java.lang.Object,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.AnimationFrameInfo$Companion { org.jetbrains.skia.AnimationFrameInfo fromInteropPointer$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.AnimationFrameInfo$Companion { org.jetbrains.skia.AnimationFrameInfo[] fromInteropArrayPointer$skiko(int,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.BreakIteratorKt { long withErrorGuard(java.lang.String,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.BreakIteratorKt { long access$withErrorGuard(java.lang.String,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.DirectContextKt { java.lang.Object useContext(org.jetbrains.skia.DirectContext,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.FontMetricsKt { org.jetbrains.skia.FontMetrics fromInteropPointer(org.jetbrains.skia.FontMetrics$Companion,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.IRangeKt { org.jetbrains.skia.IRange fromInteropPointer(org.jetbrains.skia.IRange$Companion,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.ImageInfo$Companion { org.jetbrains.skia.ImageInfo createUsing(long,kotlin.jvm.functions.Function3); }', but not the descriptor class 'kotlin.jvm.functions.Function3' Note: the configuration keeps the entry point 'org.jetbrains.skia.Matrix22$Companion { org.jetbrains.skia.Matrix22 fromInteropPointer$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.Matrix44$Companion { org.jetbrains.skia.Matrix44 fromInteropPointer$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.Picture { org.jetbrains.skia.Picture playback(org.jetbrains.skia.Canvas,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.Picture { org.jetbrains.skia.Picture playback$default(org.jetbrains.skia.Picture,org.jetbrains.skia.Canvas,kotlin.jvm.functions.Function0,int,java.lang.Object); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.Point$Companion { org.jetbrains.skia.Point fromInteropPointer$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.RRect$Companion { org.jetbrains.skia.RRect fromInteropPointer$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.RRect$Companion { org.jetbrains.skia.RRect fromInteropPointerNullable$skiko(kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.RRect$Companion$fromInteropPointerNullable$rect$1 { RRect$Companion$fromInteropPointerNullable$rect$1(kotlin.jvm.internal.Ref$BooleanRef,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.Rect$Companion { org.jetbrains.skia.Rect fromInteropPointer$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.Rect$Companion { org.jetbrains.skia.Rect[] fromInteropPointer$skiko(int,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.Rect$Companion { org.jetbrains.skia.Rect fromInteropPointerNullable$skiko(kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.Rect$Companion$fromInteropPointerNullable$rect$1 { Rect$Companion$fromInteropPointerNullable$rect$1(kotlin.jvm.internal.Ref$BooleanRef,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.StdVectorDecoderKt { java.lang.Object arrayDecoderScope(kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.StdVectorDecoderKt { java.lang.Object arrayDecoderScope(kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object callback(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object intCallback(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object nativePointerCallback(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object interopPointerCallback(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object booleanCallback(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object virtual(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object virtualInt(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object virtualNativePointer(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object virtualInteropPointer(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.InteropScope { java.lang.Object virtualBoolean(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.ManagedKt { java.lang.Object use(org.jetbrains.skia.impl.Managed,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { byte[] withResult(byte[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { byte[] withNullableResult(byte[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { float[] withResult(float[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { float[] withNullableResult(float[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { int[] withResult(int[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { int[] withNullableResult(int[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { short[] withResult(short[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { double[] withResult(double[],kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { org.jetbrains.skia.impl.NativePointerArray withResult(org.jetbrains.skia.impl.NativePointerArray,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { java.lang.String withStringResult(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { java.lang.String withStringReferenceResult(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.NativeKt { java.lang.String withStringReferenceNullableResult(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.Native_jvmKt { java.lang.Object interopScope(kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.Stats { java.lang.Integer onAllocated$lambda$0(kotlin.jvm.functions.Function2,java.lang.Object,java.lang.Object); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.impl.Stats { java.lang.Integer onDeallocated$lambda$1(kotlin.jvm.functions.Function2,java.lang.Object,java.lang.Object); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.paragraph.DecorationStyleKt { org.jetbrains.skia.paragraph.DecorationStyle fromInteropPointer(org.jetbrains.skia.paragraph.DecorationStyle$Companion,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.paragraph.ShadowKt { org.jetbrains.skia.paragraph.Shadow[] fromInteropPointer(org.jetbrains.skia.paragraph.Shadow$Companion,int,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.svg.SVGLength$Companion { org.jetbrains.skia.svg.SVGLength fromInterop$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skia.svg.SVGPreserveAspectRatio$Companion { org.jetbrains.skia.svg.SVGPreserveAspectRatio fromInterop$skiko(kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skiko.AWTKt { java.lang.Object useDrawingSurfacePlatformInfo(java.awt.Canvas,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.AWTKt { java.lang.Object useDrawingSurfaceInfo(java.awt.Canvas,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.AWTKt$useDrawingSurfacePlatformInfo$1 { AWTKt$useDrawingSurfacePlatformInfo$1(kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.AWTLinuxDrawingSurfaceKt { java.lang.Object lockLinuxDrawingSurface(org.jetbrains.skiko.HardwareLayer,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Actuals_jvmKt { java.lang.Object maybeSynchronized(java.lang.Object,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.DrawingSurface { java.lang.Object withLock(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FPSCounter { FPSCounter(double,boolean,kotlin.jvm.functions.Function0,boolean); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FPSCounter { FPSCounter(double,boolean,kotlin.jvm.functions.Function0,boolean,int,kotlin.jvm.internal.DefaultConstructorMarker); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameDispatcher { FrameDispatcher(kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameDispatcher { FrameDispatcher(kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameDispatcher { FrameDispatcher(kotlin.coroutines.CoroutineContext,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameLimiter { FrameLimiter(kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameLimiter { FrameLimiter(kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameLimiter { FrameLimiter(kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameLimiter { FrameLimiter(kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function0,int,kotlin.jvm.internal.DefaultConstructorMarker); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameLimiter { FrameLimiter(kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function0,int,kotlin.jvm.internal.DefaultConstructorMarker); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skiko.FrameLimiter { FrameLimiter(kotlinx.coroutines.CoroutineScope,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function0,int,kotlin.jvm.internal.DefaultConstructorMarker); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.HardwareLayer { HardwareLayer(kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.HardwareLayer { HardwareLayer(kotlin.jvm.functions.Function1,int,kotlin.jvm.internal.DefaultConstructorMarker); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.HardwareLayerKt { org.jetbrains.skiko.FrameLimiter layerFrameLimiter(kotlinx.coroutines.CoroutineScope,org.jetbrains.skiko.HardwareLayer,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.HardwareLayerKt { org.jetbrains.skiko.FrameLimiter layerFrameLimiter$default(kotlinx.coroutines.CoroutineScope,org.jetbrains.skiko.HardwareLayer,kotlin.jvm.functions.Function1,int,java.lang.Object); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.HardwareLayerKt$layerFrameLimiter$2 { HardwareLayerKt$layerFrameLimiter$2(kotlinx.coroutines.channels.Channel,org.jetbrains.skiko.HardwareLayerKt$layerFrameLimiter$state$1,org.jetbrains.skiko.HardwareLayer,kotlin.jvm.functions.Function1,kotlin.coroutines.Continuation); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void setLoggerFactory(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void trace(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void debug(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void info(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void warn(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void error(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void trace(java.lang.Throwable,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void debug(java.lang.Throwable,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void info(java.lang.Throwable,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void warn(java.lang.Throwable,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Logger { void error(java.lang.Throwable,kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.LoggingKt { void setupSkikoLoggerFactory(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.MetalApiKt { java.lang.Object autoreleasepool(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.NotSupportedAdapter { NotSupportedAdapter(org.jetbrains.skiko.OS,org.jetbrains.skiko.GraphicsApi,kotlin.text.Regex); }', but not the descriptor class 'kotlin.text.Regex' Note: the configuration keeps the entry point 'org.jetbrains.skiko.NotSupportedAdapter { org.jetbrains.skiko.NotSupportedAdapter copy(org.jetbrains.skiko.OS,org.jetbrains.skiko.GraphicsApi,kotlin.text.Regex); }', but not the descriptor class 'kotlin.text.Regex' Note: the configuration keeps the entry point 'org.jetbrains.skiko.NotSupportedAdapter { org.jetbrains.skiko.NotSupportedAdapter copy$default(org.jetbrains.skiko.NotSupportedAdapter,org.jetbrains.skiko.OS,org.jetbrains.skiko.GraphicsApi,kotlin.text.Regex,int,java.lang.Object); }', but not the descriptor class 'kotlin.text.Regex' Note: the configuration keeps the entry point 'org.jetbrains.skiko.ResourceUtilsKt { void autoCloseScope(kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer { SkiaLayer(kotlin.jvm.functions.Function1,org.jetbrains.skiko.SkiaLayerProperties,org.jetbrains.skiko.RenderFactory,org.jetbrains.skiko.SkiaLayerAnalytics,org.jetbrains.skia.PixelGeometry); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer { SkiaLayer(kotlin.jvm.functions.Function1,org.jetbrains.skiko.SkiaLayerProperties,org.jetbrains.skiko.RenderFactory,org.jetbrains.skiko.SkiaLayerAnalytics,org.jetbrains.skia.PixelGeometry,int,kotlin.jvm.internal.DefaultConstructorMarker); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer { SkiaLayer(kotlin.jvm.functions.Function1,boolean,boolean,org.jetbrains.skiko.GraphicsApi,org.jetbrains.skiko.SkiaLayerAnalytics,org.jetbrains.skia.PixelGeometry); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer { SkiaLayer(kotlin.jvm.functions.Function1,boolean,boolean,org.jetbrains.skiko.GraphicsApi,org.jetbrains.skiko.SkiaLayerAnalytics,org.jetbrains.skia.PixelGeometry,int,kotlin.jvm.internal.DefaultConstructorMarker); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer { void onStateChanged(org.jetbrains.skiko.SkiaLayer$PropertyKind,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer { void inDrawScope$skiko(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer { java.lang.Object lockPicture(kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer$1 { SkiaLayer$1(kotlin.jvm.functions.Function1,org.jetbrains.skiko.SkiaLayer); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer_awtKt { int defaultFPSCounter$lambda$1$lambda$0(kotlin.Lazy); }', but not the descriptor class 'kotlin.Lazy' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer_awtKt { int access$defaultFPSCounter$lambda$1$lambda$0(kotlin.Lazy); }', but not the descriptor class 'kotlin.Lazy' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SkiaLayer_awtKt$defaultFPSCounter$1$1 { SkiaLayer_awtKt$defaultFPSCounter$1$1(org.jetbrains.skiko.SkikoProperties,kotlin.Lazy); }', but not the descriptor class 'kotlin.Lazy' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SwingDispatcher { void dispatch(kotlin.coroutines.CoroutineContext,java.lang.Runnable); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext' Note: the configuration keeps the entry point 'org.jetbrains.skiko.SwingDispatcher { kotlinx.coroutines.DisposableHandle invokeOnTimeout(long,java.lang.Runnable,kotlin.coroutines.CoroutineContext); }', but not the descriptor class 'kotlin.coroutines.CoroutineContext' Note: the configuration keeps the entry point 'org.jetbrains.skiko.Task { java.lang.Object runAndAwait(kotlin.jvm.functions.Function1,kotlin.coroutines.Continuation); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.context.ContextHandler { ContextHandler(org.jetbrains.skiko.SkiaLayer,kotlin.jvm.functions.Function1); }', but not the descriptor class 'kotlin.jvm.functions.Function1' Note: the configuration keeps the entry point 'org.jetbrains.skiko.redrawer.AWTRedrawer { void inDrawScope(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' Note: the configuration keeps the entry point 'org.jetbrains.skiko.redrawer.RedrawerManager { RedrawerManager(org.jetbrains.skiko.GraphicsApi,kotlin.jvm.functions.Function2); }', but not the descriptor class 'kotlin.jvm.functions.Function2' Note: the configuration keeps the entry point 'org.jetbrains.skiko.swing.SwingRedrawerBase { void inDrawScope(kotlin.jvm.functions.Function0); }', but not the descriptor class 'kotlin.jvm.functions.Function0' ```

The reason I didn't handle this as I don't have enough information about the approach that you prefer to use as discussed above, and I don't have any Compose Desktop projects, the examples provided in this repository use the libraries and plugins of this repository from maven central instead of the ones locally in the machine.

This change is only tested on the Jetbrians example, and it seems to work as expected.

4.49% may not sound very significant, however, it's one step toward minimizing the bundle size and it's optional (disabled by default), I haven't looked into the source code although it seems that Skia is quite big, to save more, we need rules to explicitly keep the classes, methods when necessary, the current setup will exclude Skia from minimization

https://github.com/JetBrains/compose-multiplatform/blob/8432577f5d8a373981af436d242d832c7e6f5637/gradle-plugins/compose/src/main/resources/default-compose-desktop-rules.pro#L2-L3