Feature description
The reason the CLI jar size is currently large is because it includes native skiko (Skia for kotlin) libraries for all OSes.
By not including skiko in the jar and instead downloading it on the first launch of GUI, the size of the jar file can be significantly reduced.
(On-demand downloading, lazy downloading)
This has the following advantages:
- Since only the skiko file for that OS will be downloaded, the total download size will be smaller.
- The update frequency of the skiko version that the CLI depends on is lower than that of the CUI itself, so once downloaded, the skiko libraries can be used for a long period of time across multiple versions of the CLI. (The skiko version is only bumped when bumping the KMP dependency.)
- The OS will absolutely never change to another OS after the first launch of Morphe GUI, so once skiko is downloaded, no further downloads of skiko will occur until bumping KMP.
- Users who do not use a GUI and only use the CLI (e.g., Termux users) do not need to download unnecessary libraries.
This method has the following limitations, but Morphe CLI happens to satisfy all of them.
- Requires internet on first launch. — ✅ Morphe GUI already requires an internet connection to function. The CLI one will continue to work offline.
- Requires a place to save the Skiko library. — ✅ Morphe GUI has a user data directory in the system's AppData directory.
The following is my summary of the results I obtained by asking ChatGPT how to achieve this.
Answer by ChatGPT
The key points are the following three:
- Exclude the skiko dependency in build.gradle.kts.
- Detect the OS at startup.
- Download the corresponding skiko library and use
System.load().
The implementation plan is outlined below.
1. Architecture
Basic structure
App.jar
↓
Startup
↓
OS detection
↓
Check if Skiko is present
↓
Download if not
↓
Save to AppData
↓
System.load()
↓
Start Compose
2. AppData storage location
fun appDataDir(): File {
val home = System.getProperty("user.home")
return when {
isWindows -> File(System.getenv("APPDATA"), "MyApp")
isMac -> File(home, "Library/Application Support/MyApp")
else -> File(home, ".myapp")
}
}
Example
Windows
C:\Users\user\AppData\Roaming\morphe-gui\skiko\0.148.0\skiko-windows-x64.dll
Mac
~/Library/Application Support/morphe-gui/skiko/0.148.0/libskiko-macos-arm64.dylib
3. Download skiko
You can get it from Skiko's GitHub releases.
Example: https://github.com/JetBrains/skiko/releases/download/0.7.90/
There you will find:
skiko-awt-runtime-windows-x64-0.148.0.jar
skiko-awt-runtime-windows-arm64-0.148.0.jar
skiko-awt-runtime-macos-x64-0.148.0.jar
skiko-awt-runtime-macos-arm64-0.148.0.jar
skiko-awt-runtime-linux-x64-0.148.0.jar
skiko-awt-runtime-linux-arm64-0.148.0.jar
The dynamic libraries are included in the JAR file.
fun download(url: String, dest: File) {
URL(url).openStream().use { input ->
dest.outputStream().use { output ->
input.copyTo(output)
}
}
// Verify the expected file hash
}
Alternatively, you could host the dll/dylib/so yourself somewhere.
I (kitaidai31) noticed that skiko-awt-runtime-macos-arm64-0.148.0.jar contains two dylibs, one for x64 and one for arm64. Therefore, using this GitHub release will result in unnecessarily downloading the x64 dylib on Macs with Apple Silicon, so it might be better to do self hosting.
4. load native
fun loadSkiko() {
val dir = appDataDir().apply { mkdirs() }
val lib = when {
isWindows -> File(dir, "skiko.dll")
isMac -> File(dir, "libskiko.dylib")
else -> File(dir, "libskiko.so")
}
if (!lib.exists()) {
download(getUrlForOS(), lib)
}
System.load(lib.absolutePath)
}
5. Run before launching Compose
fun main() {
loadSkiko()
application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
}
6. Important Note
Skiko requires version matching.
Example:
compose 1.6.0
↓
skiko 0.7.90
You need to match the version of Skiko that Compose expects.
Additionally, for security reasons, hard-code the expected file hash into the Moprhe GUI code.
This ensures that the Morphe GUI is prevented from loading any libraries other than those it expects.
Note that in order to perform a download before launching Compose, you will likely need to implement a download progress dialog using AWT or Swing. This dialog box can be very simple. I think it will only have the message "Downloading necessary library..." and a cancel button.
Motivation
- Reduce download size
- Optimization for users who only use the CLI.
Acknowledgements
Feature description
The reason the CLI jar size is currently large is because it includes native skiko (Skia for kotlin) libraries for all OSes.
By not including skiko in the jar and instead downloading it on the first launch of GUI, the size of the jar file can be significantly reduced.
(On-demand downloading, lazy downloading)
This has the following advantages:
This method has the following limitations, but Morphe CLI happens to satisfy all of them.
The following is my summary of the results I obtained by asking ChatGPT how to achieve this.
Answer by ChatGPT
The key points are the following three:
System.load().The implementation plan is outlined below.
1. Architecture
Basic structure
2. AppData storage location
Example
3. Download skiko
You can get it from Skiko's GitHub releases.
Example:
https://github.com/JetBrains/skiko/releases/download/0.7.90/There you will find:
The dynamic libraries are included in the JAR file.
Alternatively, you could host the dll/dylib/so yourself somewhere.
I (kitaidai31) noticed that
skiko-awt-runtime-macos-arm64-0.148.0.jarcontains two dylibs, one for x64 and one for arm64. Therefore, using this GitHub release will result in unnecessarily downloading the x64 dylib on Macs with Apple Silicon, so it might be better to do self hosting.4. load native
5. Run before launching Compose
6. Important Note
Skiko requires version matching.
Example:
You need to match the version of Skiko that Compose expects.
Additionally, for security reasons, hard-code the expected file hash into the Moprhe GUI code.
This ensures that the Morphe GUI is prevented from loading any libraries other than those it expects.
Note that in order to perform a download before launching Compose, you will likely need to implement a download progress dialog using AWT or Swing. This dialog box can be very simple. I think it will only have the message "Downloading necessary library..." and a cancel button.
Motivation
Acknowledgements