-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Support for AppCDS #5336
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
base: master
Are you sure you want to change the base?
Support for AppCDS #5336
Changes from 13 commits
c815098
6aad554
a8e376f
ef4b437
c55d5c4
6154135
5ed97e1
7599528
7e85ae7
ac1c2e8
34ef580
44330bd
216b85a
9b58449
3f5edad
39abf04
e18e47d
a660def
79437f9
7176c6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package org.jetbrains.compose.desktop.application.dsl | ||
|
||
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import org.gradle.api.file.Directory | ||
import org.gradle.api.file.RegularFile | ||
import org.jetbrains.compose.desktop.application.internal.JvmApplicationContext | ||
import org.jetbrains.compose.internal.utils.packagedAppJarFilesDir | ||
import java.io.Serializable | ||
|
||
/** | ||
* The configuration of AppCDS for the native distribution. | ||
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
abstract class AppCdsConfiguration { | ||
/** | ||
* The AppCDS mode to use. | ||
*/ | ||
var mode: AppCdsMode = AppCdsMode.None | ||
|
||
/** | ||
* Whether to fail running the app if unable to load the AppCDS archive. | ||
*/ | ||
var exitAppOnCdsFailure: Boolean = false | ||
} | ||
|
||
/** | ||
* Returns the AppCDS-related arguments to pass the JVM when running the app. | ||
*/ | ||
internal fun AppCdsConfiguration.runtimeJvmArgs(context: JvmApplicationContext) = buildList { | ||
addAll(mode.runtimeJvmArgs()) | ||
if (exitAppOnCdsFailure) { | ||
add("-Xshare:on") | ||
} | ||
if (context.buildType.cdsLogging.get()) { | ||
add("-Xlog:cds") | ||
} | ||
} | ||
|
||
/** | ||
* The mode of use of AppCDS. | ||
*/ | ||
abstract class AppCdsMode(val name: String) : Serializable { | ||
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Whether to generate a classes.jsa archive for the JRE classes. | ||
*/ | ||
internal abstract val generateJreClassesArchive: Boolean | ||
|
||
/** | ||
* Returns whether this mode creates an archive of app classes at build time. | ||
*/ | ||
internal open val generateAppClassesArchive: Boolean get() = false | ||
|
||
/** | ||
* The arguments to pass to the JVM when running the app to create | ||
* the archive for the app's class files. | ||
* | ||
* This will only be called if [generateAppClassesArchive] is `true`. | ||
*/ | ||
internal open fun appClassesArchiveCreationJvmArgs(): List<String> = | ||
error("AppCdsMode '$this' does not create an archive") | ||
|
||
/** | ||
* Returns the app's classes archive file, given the root directory of | ||
* the packaged app. | ||
*/ | ||
internal open fun appClassesArchiveFile(packagedAppRootDir: Directory): RegularFile = | ||
error("AppCdsMode '$this' does not create an archive") | ||
|
||
/** | ||
* The arguments to pass to the JVM when running the final app. | ||
*/ | ||
internal abstract fun runtimeJvmArgs(): List<String> | ||
|
||
/** | ||
* Checks whether this mode is compatible with the given JDK major version. | ||
* Throws an exception if not. | ||
*/ | ||
internal open fun checkJdkCompatibility(jdkMajorVersion: Int) = Unit | ||
|
||
override fun toString() = name | ||
|
||
companion object { | ||
|
||
/** | ||
* The name of the AppCDS archive file. | ||
*/ | ||
private const val ARCHIVE_NAME = "app.jsa" | ||
|
||
/** | ||
* The AppCDS archive file. | ||
*/ | ||
internal const val ARCHIVE_FILE_ARGUMENT = "\$APPDIR/$ARCHIVE_NAME" | ||
|
||
/** | ||
* AppCDS is not used. | ||
*/ | ||
val None = object : AppCdsMode("None") { | ||
override val generateJreClassesArchive: Boolean get() = false | ||
override fun runtimeJvmArgs() = emptyList<String>() | ||
} | ||
|
||
/** | ||
* AppCDS is used via a dynamic shared archive created automatically | ||
* when the app is run (using `-XX:+AutoCreateSharedArchive`). | ||
* Due to the drawbacks below, this mode is mostly recommended only | ||
* to experiment and see how fast your app starts up with AppCDS. | ||
* In production, we recomment [Prebuild] mode. | ||
* | ||
* Advantages: | ||
* - Simplest - no additional step is needed to build the archive. | ||
* - Creates a smaller distributable. | ||
* | ||
* Drawbacks: | ||
* - Requires JDK 19 or later. | ||
* - The archive is not available at the first execution of the app, | ||
* so it is slower (and possibly even slower than regular execution), | ||
* The archive is created when at shutdown time of the first execution, | ||
* which also takes a little longer. | ||
* - Some OSes may block writing the archive file to the application's | ||
* directory at runtime. | ||
*/ | ||
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@Suppress("unused") | ||
val Auto = object : AppCdsMode("Auto") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Windows controlled access blocked creating the file:
It blocks silently, until I reenable it in the notification list. Is it possible to change its location to a local data folder? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll see if it's possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no expandable macro for any local data folder, unfortunately. It's supposed to expand environment variables (so I could at least construct the directory from, say, the user name), but even that didn't work in my tests. So I can't construct an argument to Why does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It looks like it is possible to specify the location of the file, but the issue is how to know the local folder?
This seems was cut There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But not sure that Auto worth the trouble finding a way to write There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes.
Don't remember what I was writing there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pushed new commends, that was added before your comment, please read There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If it's that folder then maybe it's possible. The only uncertain part here is
As I wrote in the kdoc, it's at least a quick way to get a feel of how fast your app will start. But also, not everyone needs to deploy on OSes that prevent writing to the app directory. And maybe we'll be able to solve it... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It is only visible after the second run, as with Prebuilt. The first run actually slower, and can give wrong impression.
Don't macOs/Linux also prohibit writing into the installed app folder? But probably there is a case in Portable apps on all OSes. Not sure if it is worth supported. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes. The user will need to read the docs to understand what's happening, there's no way around that. But setting up
macOS doesn't, which surprised me a bit, honestly.
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private val MIN_JDK_VERSION = 19 | ||
override val generateJreClassesArchive: Boolean get() = true | ||
override fun runtimeJvmArgs() = | ||
listOf( | ||
"-XX:SharedArchiveFile=$ARCHIVE_FILE_ARGUMENT", | ||
"-XX:+AutoCreateSharedArchive" | ||
) | ||
override fun checkJdkCompatibility(jdkMajorVersion: Int) { | ||
if (jdkMajorVersion < MIN_JDK_VERSION) { | ||
error( | ||
"AppCdsMode '$this' is not supported on JDK earlier than" + | ||
" $MIN_JDK_VERSION; current is $jdkMajorVersion" | ||
) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* AppCDS is used via a dynamic shared archive created by executing | ||
* the app before packaging (using `-XX:ArchiveClassesAtExit`). | ||
* | ||
* When using this mode, the app will be run during the creation of | ||
* the distributable. In this run, a system property | ||
* `compose.appcds.create-archive` will be set to the value `true`. | ||
* The app should "exercise" itself and cause the loading of all | ||
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* the classes that should be written into the AppCDS archive. It | ||
* should then shut down in order to let the build process continue. | ||
* | ||
* Advantages: | ||
* - Can be used with JDKs earlier than 19. | ||
* - The first run of the distributed app is fast too. | ||
* | ||
* Drawbacks: | ||
* - Requires an additional step of running the app when building the | ||
* distributable. | ||
* - The distributable is larger because it includes the archive of | ||
* the app's classes. | ||
*/ | ||
@Suppress("unused") | ||
val Prebuild = object : AppCdsMode("Prebuild") { | ||
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I call Though, I see the cds logs, there was a run of the application, and there is Manually copying There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that first run - 2.5 sec (as in the case of Auto mode, when it is slow down the first execution) I expect it to be 0.4 sec, as I see in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have this error with additional
Log during collection:
It seems the collected classpath is with the absolute path There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Seems like it is not only logging, it actually tries accessing the hardcoded path. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I run it on JDK 19. It looks like it was fixed in https://bugs.openjdk.org/browse/JDK-8279366 in JDK 20 I checked on JBR 21, and it works. I suggest to revert the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still, the first start is 2 sec, only the second start is 0.5 sec, which indicates that cds works. The first start logging says that CDS should also work 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested multiple first runs of installed app:
Looks like there are some other factors that slow down the run of an installed app, comparing to just running There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It was an antivirus (the Windows built-in one). Disabling real-time protection makes the first run the same as the next ones. Worth to mention that in the docs
igordmn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
override val generateJreClassesArchive: Boolean get() = true | ||
override val generateAppClassesArchive: Boolean get() = true | ||
override fun appClassesArchiveCreationJvmArgs() = | ||
listOf( | ||
"-XX:ArchiveClassesAtExit=$ARCHIVE_FILE_ARGUMENT", | ||
"-Dcompose.appcds.create-archive=true", | ||
"-Xlog:cds" | ||
) | ||
override fun appClassesArchiveFile(packagedAppRootDir: Directory): RegularFile { | ||
val appDir = packagedAppJarFilesDir(packagedAppRootDir) | ||
return appDir.file(ARCHIVE_NAME) | ||
} | ||
override fun runtimeJvmArgs() = | ||
listOf( | ||
"-XX:SharedArchiveFile=$ARCHIVE_FILE_ARGUMENT", | ||
) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After merging the PR, please close the user PR #2080