Skip to content

Commit c815098

Browse files
committed
Implemented support for App CDS.
1 parent cf0ff4f commit c815098

File tree

10 files changed

+371
-45
lines changed

10 files changed

+371
-45
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package org.jetbrains.compose.desktop.application.dsl
2+
3+
import org.jetbrains.compose.internal.utils.packagedAppJarFilesDir
4+
import java.io.File
5+
import java.io.Serializable
6+
7+
/**
8+
* The configuration of AppCDS for the native distribution.
9+
*/
10+
abstract class AppCdsConfiguration {
11+
/**
12+
* The AppCDS mode to use.
13+
*/
14+
var mode: AppCdsMode = AppCdsMode.None
15+
16+
/**
17+
* Whether to ask the JVM to log AppCDS-related actions.
18+
*/
19+
@Suppress("MemberVisibilityCanBePrivate")
20+
var logging: Boolean = false
21+
22+
/**
23+
* Returns the AppCDS-related arguments to pass the JVM when running the app.
24+
*/
25+
internal fun runtimeJvmArgs() = buildList {
26+
addAll(mode.runtimeJvmArgs())
27+
if (logging) add("-Xlog:cds")
28+
}
29+
}
30+
31+
/**
32+
* The mode of use of AppCDS.
33+
*/
34+
abstract class AppCdsMode : Serializable {
35+
36+
/**
37+
* Whether to generate a classes.jsa archive for the JRE classes.
38+
*/
39+
internal abstract val generateJreClassesArchive: Boolean
40+
41+
/**
42+
* Returns whether this mode creates an archive of app classes at build time.
43+
*/
44+
internal open val generateAppClassesArchive: Boolean get() = false
45+
46+
/**
47+
* The arguments to pass to the JVM when running the app to create
48+
* the archive for the app's class files.
49+
*
50+
* This will only be called if [generateAppClassesArchive] is `true`.
51+
*/
52+
internal open fun appClassesArchiveCreationJvmArgs(): List<String> =
53+
error("AppCdsMode '$this' does not create an archive")
54+
55+
/**
56+
* Returns the app's classes archive file, given the root directory of
57+
* the packaged app.
58+
*/
59+
internal open fun appClassesArchiveFile(packagedAppRootDir: File): File =
60+
error("AppCdsMode '$this' does not create an archive")
61+
62+
/**
63+
* The arguments to pass to the JVM when running the final app.
64+
*/
65+
internal abstract fun runtimeJvmArgs(): List<String>
66+
67+
/**
68+
* Checks whether this mode is compatible with the given JDK major version.
69+
* Throws an exception if not.
70+
*/
71+
internal open fun checkJdkCompatibility(jdkMajorVersion: Int) = Unit
72+
73+
74+
companion object {
75+
76+
/**
77+
* The name of the AppCds archive file.
78+
*/
79+
private const val ARCHIVE_NAME = "app.jsa"
80+
81+
/**
82+
* AppCDS is not used.
83+
*/
84+
val None = object : AppCdsMode() {
85+
override val generateJreClassesArchive: Boolean get() = false
86+
override fun runtimeJvmArgs() = emptyList<String>()
87+
override fun toString() = "None"
88+
}
89+
90+
/**
91+
* AppCDS is used via a dynamic shared archive created automatically
92+
* when the app is run (using `-XX:+AutoCreateSharedArchive`).
93+
*
94+
* Pros:
95+
* - Simplest - no additional step is needed to build the archive.
96+
* - Creates a smaller distributable.
97+
*
98+
* Cons:
99+
* - Requires JDK 19 or later.
100+
* - The archive is not available at the first execution of the app,
101+
* so it is slower. The archive is created when at shutdown time
102+
* of the first execution, which also takes a little longer.
103+
*/
104+
@Suppress("unused")
105+
val Auto = object : AppCdsMode() {
106+
private val MIN_JDK_VERSION = 19
107+
override val generateJreClassesArchive: Boolean get() = true
108+
override fun runtimeJvmArgs() =
109+
listOf(
110+
"-XX:SharedArchiveFile=\$APPDIR/$ARCHIVE_NAME",
111+
"-XX:+AutoCreateSharedArchive"
112+
)
113+
override fun checkJdkCompatibility(jdkMajorVersion: Int) {
114+
if (jdkMajorVersion < MIN_JDK_VERSION) {
115+
error(
116+
"AppCdsMode '$this' is not supported on JDK earlier than" +
117+
" $MIN_JDK_VERSION; current is $jdkMajorVersion"
118+
)
119+
}
120+
}
121+
override fun toString() = "Auto"
122+
}
123+
124+
/**
125+
* AppCDS is used via a dynamic shared archive created by executing
126+
* the app before packaging (using `-XX:ArchiveClassesAtExit`).
127+
*
128+
* Pros:
129+
* - Can be used with JDKs earlier than 19.
130+
* - The first run of the distributed app is fast too.
131+
*
132+
* Cons:
133+
* - Requires an additional step of running the app when building the
134+
* distributable.
135+
* - The distributable is larger because it includes the archive of
136+
* the app's classes.
137+
*/
138+
@Suppress("unused")
139+
val Prebuild = object : AppCdsMode() {
140+
override val generateJreClassesArchive: Boolean get() = true
141+
override val generateAppClassesArchive: Boolean get() = true
142+
override fun appClassesArchiveCreationJvmArgs() =
143+
listOf(
144+
"-XX:ArchiveClassesAtExit=\$APPDIR/$ARCHIVE_NAME",
145+
"-Dcompose.cds.create-archive=true"
146+
)
147+
override fun appClassesArchiveFile(packagedAppRootDir: File): File {
148+
val appDir = packagedAppJarFilesDir(packagedAppRootDir)
149+
return appDir.resolve(ARCHIVE_NAME)
150+
}
151+
override fun runtimeJvmArgs() =
152+
listOf(
153+
"-XX:SharedArchiveFile=\$APPDIR/$ARCHIVE_NAME",
154+
)
155+
156+
override fun toString() = "Prebuild"
157+
}
158+
}
159+
}

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/JvmApplication.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@ abstract class JvmApplication {
3030
abstract fun nativeDistributions(fn: Action<JvmApplicationDistributions>)
3131
abstract val buildTypes: JvmApplicationBuildTypes
3232
abstract fun buildTypes(fn: Action<JvmApplicationBuildTypes>)
33+
abstract val appCds: AppCdsConfiguration
34+
abstract fun appCds(fn: Action<AppCdsConfiguration>)
3335
}
3436

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/JvmApplicationData.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.gradle.api.file.RegularFileProperty
1010
import org.gradle.api.model.ObjectFactory
1111
import org.gradle.api.provider.Provider
1212
import org.gradle.api.provider.ProviderFactory
13+
import org.jetbrains.compose.desktop.application.dsl.AppCdsConfiguration
1314
import org.jetbrains.compose.desktop.application.dsl.JvmApplicationDistributions
1415
import org.jetbrains.compose.desktop.application.dsl.JvmApplicationBuildTypes
1516
import org.jetbrains.compose.internal.utils.new
@@ -38,4 +39,5 @@ internal open class JvmApplicationData @Inject constructor(
3839
val jvmArgs: MutableList<String> = ArrayList()
3940
val nativeDistributions: JvmApplicationDistributions = objects.new()
4041
val buildTypes: JvmApplicationBuildTypes = objects.new()
42+
val appCds: AppCdsConfiguration = objects.new()
4143
}

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/JvmApplicationInternal.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.gradle.api.Task
1010
import org.gradle.api.file.RegularFileProperty
1111
import org.gradle.api.model.ObjectFactory
1212
import org.gradle.api.tasks.SourceSet
13+
import org.jetbrains.compose.desktop.application.dsl.AppCdsConfiguration
1314
import org.jetbrains.compose.desktop.application.dsl.JvmApplication
1415
import org.jetbrains.compose.desktop.application.dsl.JvmApplicationDistributions
1516
import org.jetbrains.compose.desktop.application.dsl.JvmApplicationBuildTypes
@@ -70,4 +71,10 @@ internal open class JvmApplicationInternal @Inject constructor(
7071
final override fun buildTypes(fn: Action<JvmApplicationBuildTypes>) {
7172
fn.execute(data.buildTypes)
7273
}
74+
75+
final override val appCds: AppCdsConfiguration by data::appCds
76+
final override fun appCds(fn: Action<AppCdsConfiguration>) {
77+
fn.execute(data.appCds)
78+
}
79+
7380
}

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ import org.jetbrains.compose.desktop.application.tasks.*
1818
import org.jetbrains.compose.desktop.tasks.AbstractJarsFlattenTask
1919
import org.jetbrains.compose.desktop.tasks.AbstractUnpackDefaultComposeApplicationResourcesTask
2020
import org.jetbrains.compose.internal.utils.*
21-
import org.jetbrains.compose.internal.utils.OS
22-
import org.jetbrains.compose.internal.utils.currentOS
23-
import org.jetbrains.compose.internal.utils.currentTarget
24-
import org.jetbrains.compose.internal.utils.dir
25-
import org.jetbrains.compose.internal.utils.ioFile
26-
import org.jetbrains.compose.internal.utils.ioFileOrNull
27-
import org.jetbrains.compose.internal.utils.javaExecutable
2821
import org.jetbrains.compose.internal.utils.provider
2922

3023
private val defaultJvmArgs = listOf("-D$CONFIGURE_SWING_GLOBALS=true")
@@ -66,6 +59,7 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes
6659
taskNameObject = "runtime"
6760
) {
6861
jdkHome.set(app.javaHomeProvider)
62+
appCdsMode.set(app.appCds.mode)
6963
checkJdkVendor.set(ComposeProperties.checkJdkVendor(project.providers))
7064
jdkVersionProbeJar.from(
7165
project.detachedComposeGradleDependency(
@@ -111,14 +105,15 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes
111105
includeAllModules.set(provider { app.nativeDistributions.includeAllModules })
112106
javaRuntimePropertiesFile.set(checkRuntime.flatMap { it.javaRuntimePropertiesFile })
113107
destinationDir.set(appTmpDir.dir("runtime"))
108+
generateCdsArchive.set(app.appCds.mode.generateJreClassesArchive)
114109
}
115110

116111
return CommonJvmDesktopTasks(
117-
unpackDefaultResources,
118-
checkRuntime,
119-
suggestRuntimeModules,
120-
prepareAppResources,
121-
createRuntimeImage
112+
unpackDefaultResources = unpackDefaultResources,
113+
checkRuntime = checkRuntime,
114+
suggestRuntimeModules = suggestRuntimeModules,
115+
prepareAppResources = prepareAppResources,
116+
createRuntimeImage = createRuntimeImage,
122117
)
123118
}
124119

@@ -149,6 +144,18 @@ private fun JvmApplicationContext.configurePackagingTasks(
149144
)
150145
}
151146

147+
val appCdsMode = app.appCds.mode
148+
val createAppCdsArchive = if (appCdsMode.generateAppClassesArchive) {
149+
tasks.register<AbstractCreateAppCdsArchiveTask>(
150+
taskNameAction = "create",
151+
taskNameObject = "appCdsArchive",
152+
args = listOf(createDistributable)
153+
) {
154+
dependsOn(createDistributable)
155+
this.appCdsMode.set(appCdsMode)
156+
}
157+
} else null
158+
152159
val packageFormats = app.nativeDistributions.targetFormats.map { targetFormat ->
153160
val packageFormat = tasks.register<AbstractJPackageTask>(
154161
taskNameAction = "package",
@@ -168,14 +175,16 @@ private fun JvmApplicationContext.configurePackagingTasks(
168175
prepareAppResources = commonTasks.prepareAppResources,
169176
checkRuntime = commonTasks.checkRuntime,
170177
unpackDefaultResources = commonTasks.unpackDefaultResources,
171-
runProguard = runProguard
178+
runProguard = runProguard,
179+
createAppCdsArchive = createAppCdsArchive
172180
)
173181
} else {
174182
configurePackageTask(
175183
this,
176184
createAppImage = createDistributable,
177185
checkRuntime = commonTasks.checkRuntime,
178-
unpackDefaultResources = commonTasks.unpackDefaultResources
186+
unpackDefaultResources = commonTasks.unpackDefaultResources,
187+
createAppCdsArchive = createAppCdsArchive
179188
)
180189
}
181190
}
@@ -233,7 +242,11 @@ private fun JvmApplicationContext.configurePackagingTasks(
233242
taskNameAction = "run",
234243
taskNameObject = "distributable",
235244
args = listOf(createDistributable)
236-
)
245+
) {
246+
if (createAppCdsArchive != null) {
247+
dependsOn(createAppCdsArchive)
248+
}
249+
}
237250

238251
val run = tasks.register<JavaExec>(taskNameAction = "run") {
239252
configureRunTask(this, commonTasks.prepareAppResources, runProguard)
@@ -284,7 +297,8 @@ private fun JvmApplicationContext.configurePackageTask(
284297
prepareAppResources: TaskProvider<Sync>? = null,
285298
checkRuntime: TaskProvider<AbstractCheckNativeDistributionRuntime>? = null,
286299
unpackDefaultResources: TaskProvider<AbstractUnpackDefaultComposeApplicationResourcesTask>,
287-
runProguard: Provider<AbstractProguardTask>? = null
300+
runProguard: Provider<AbstractProguardTask>? = null,
301+
createAppCdsArchive: TaskProvider<AbstractCreateAppCdsArchiveTask>? = null
288302
) {
289303
packageTask.enabled = packageTask.targetFormat.isCompatibleWithCurrentOS
290304

@@ -338,8 +352,14 @@ private fun JvmApplicationContext.configurePackageTask(
338352
}
339353
}
340354

355+
if (createAppCdsArchive != null) {
356+
packageTask.dependsOn(createAppCdsArchive)
357+
}
358+
341359
packageTask.launcherMainClass.set(provider { app.mainClass })
342-
packageTask.launcherJvmArgs.set(provider { defaultJvmArgs + app.jvmArgs })
360+
packageTask.launcherJvmArgs.set(
361+
provider { defaultJvmArgs + app.appCds.runtimeJvmArgs() + app.jvmArgs }
362+
)
343363
packageTask.launcherArgs.set(provider { app.args })
344364
}
345365

gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractCheckNativeDistributionRuntime.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.gradle.api.file.RegularFile
1010
import org.gradle.api.provider.Property
1111
import org.gradle.api.provider.Provider
1212
import org.gradle.api.tasks.*
13+
import org.jetbrains.compose.desktop.application.dsl.AppCdsMode
1314
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
1415
import org.jetbrains.compose.desktop.application.internal.JvmRuntimeProperties
1516
import org.jetbrains.compose.desktop.application.internal.ExternalToolRunner
@@ -39,6 +40,9 @@ abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTa
3940
@get:Input
4041
abstract val checkJdkVendor: Property<Boolean>
4142

43+
@get:Input
44+
val appCdsMode: Property<AppCdsMode> = objects.notNullProperty()
45+
4246
private val taskDir = project.layout.buildDirectory.dir("compose/tmp/$name")
4347

4448
@get:OutputFile
@@ -75,8 +79,8 @@ abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTa
7579
val jdkHome = jdkHomeFile
7680
val javaExecutable = jdkHome.getJdkTool("java")
7781
val jlinkExecutable = jdkHome.getJdkTool("jlink")
78-
val jpackageExecutabke = jdkHome.getJdkTool("jpackage")
79-
ensureToolsExist(javaExecutable, jlinkExecutable, jpackageExecutabke)
82+
val jpackageExecutable = jdkHome.getJdkTool("jpackage")
83+
ensureToolsExist(javaExecutable, jlinkExecutable, jpackageExecutable)
8084

8185
val jdkRuntimeProperties = getJDKRuntimeProperties(javaExecutable)
8286

@@ -109,6 +113,8 @@ abstract class AbstractCheckNativeDistributionRuntime : AbstractComposeDesktopTa
109113
}
110114
}
111115

116+
appCdsMode.get().checkJdkCompatibility(jdkMajorVersion)
117+
112118
val modules = arrayListOf<String>()
113119
runExternalTool(
114120
tool = javaExecutable,

0 commit comments

Comments
 (0)