Browse Source

Extract runtime image creation into separate task

This change speeds up incremental build of
an application image.
For example, for one (relatively small) tested application
the overall time spent on creating binary images in incremental case
(after one file change) went from ~5 seconds down to 1.4 seconds
(non-rigorous benchmark using a relatively fast 8-core 2.3Ghz i9 MBP).
pull/224/head
Alexey Tsvetkov 4 years ago committed by Alexey Tsvetkov
parent
commit
7e243b5cbd
  1. 9
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/RuntimeCompressionLevel.kt
  2. 23
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt
  3. 3
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/dslUtils.kt
  4. 11
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/gradleUtils.kt
  5. 48
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJLinkTask.kt
  6. 37
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt
  7. 21
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt
  8. 5
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractRunDistributableTask.kt

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

@ -0,0 +1,9 @@
package org.jetbrains.compose.desktop.application.dsl
internal enum class RuntimeCompressionLevel(internal val id: Int) {
// For ID values see the docs on "--compress" https://docs.oracle.com/javase/9/tools/jlink.htm
NO_COMPRESSION(0),
CONSTANT_STRING_SHARING(1),
ZIP(2)
}

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

@ -9,6 +9,7 @@ import org.gradle.api.tasks.*
import org.gradle.jvm.tasks.Jar
import org.jetbrains.compose.desktop.application.dsl.Application
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.compose.desktop.application.tasks.AbstractJLinkTask
import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask
import org.jetbrains.compose.desktop.application.tasks.AbstractRunDistributableTask
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
@ -56,12 +57,20 @@ internal fun Project.configurePackagingTasks(apps: Collection<Application>) {
configureRunTask(app)
}
val createRuntimeImage = tasks.composeTask<AbstractJLinkTask>(
taskName("createRuntimeImage", app)
) {
javaHome.set(provider { app.javaHomeOrDefault() })
modules.set(provider { app.nativeDistributions.modules })
destinationDir.set(project.layout.buildDirectory.dir("compose/tmp/${app.name}/runtime"))
}
val packageFormats = app.nativeDistributions.targetFormats.map { targetFormat ->
tasks.composeTask<AbstractJPackageTask>(
taskName("package", app, targetFormat.name),
args = listOf(targetFormat)
) {
configurePackagingTask(app)
configurePackagingTask(app, createRuntimeImage)
}
}
@ -77,7 +86,7 @@ internal fun Project.configurePackagingTasks(apps: Collection<Application>) {
taskName("createDistributable", app),
args = listOf(TargetFormat.AppImage)
) {
configurePackagingTask(app)
configurePackagingTask(app, createRuntimeImage)
}
val runDistributable = project.tasks.composeTask<AbstractRunDistributableTask>(
@ -88,10 +97,15 @@ internal fun Project.configurePackagingTasks(apps: Collection<Application>) {
}
internal fun AbstractJPackageTask.configurePackagingTask(
app: Application
app: Application,
createRuntimeImage: TaskProvider<AbstractJLinkTask>
) {
enabled = targetFormat.isCompatibleWithCurrentOS
val runtimeImageDir = createRuntimeImage.flatMap { it.destinationDir }
dependsOn(createRuntimeImage)
runtimeImage.set(runtimeImageDir)
configurePlatformSettings(app)
app.nativeDistributions.let { executables ->
@ -115,7 +129,6 @@ internal fun AbstractJPackageTask.configurePackagingTask(
launcherMainJar.set(app.mainJar.orElse(configSource.jarTask(project).flatMap { it.archiveFile }))
}
modules.set(provider { app.nativeDistributions.modules })
launcherMainClass.set(provider { app.mainClass })
launcherJvmArgs.set(provider { app.jvmArgs })
launcherArgs.set(provider { app.args })
@ -205,7 +218,7 @@ private fun Jar.configurePackageUberJarForCurrentOS(app: Application) {
destinationDirectory.set(project.layout.buildDirectory.dir("compose/jars"))
doLast {
logger.lifecycle("The jar is written to ${archiveFile.get().asFile.canonicalPath}")
logger.lifecycle("The jar is written to ${archiveFile.ioFile.canonicalPath}")
}
}

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

@ -12,5 +12,8 @@ internal inline fun <reified T : Any> ObjectFactory.nullableProperty(): Property
internal inline fun <reified T : Any> ObjectFactory.notNullProperty(): Property<T> =
property(T::class.java)
internal inline fun <reified T : Any> ObjectFactory.notNullProperty(defaultValue: T): Property<T> =
property(T::class.java).value(defaultValue)
internal inline fun <reified T> Task.provider(noinline fn: () -> T): Provider<T> =
project.provider(fn)

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

@ -0,0 +1,11 @@
package org.jetbrains.compose.desktop.application.internal
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Provider
import java.io.File
internal val <T : FileSystemLocation> Provider<T>.ioFile: File
get() = get().asFile
internal val <T : FileSystemLocation> Provider<T>.ioFileOrNull: File?
get() = orNull?.asFile

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

@ -0,0 +1,48 @@
package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.jetbrains.compose.desktop.application.dsl.RuntimeCompressionLevel
import org.jetbrains.compose.desktop.application.internal.cliArg
import org.jetbrains.compose.desktop.application.internal.notNullProperty
import org.jetbrains.compose.desktop.application.internal.nullableProperty
import java.io.File
// todo: public DSL
// todo: deduplicate if multiple runtimes are created
abstract class AbstractJLinkTask : AbstractJvmToolOperationTask("jlink") {
@get:Input
val modules: ListProperty<String> = objects.listProperty(String::class.java)
@get:Input
internal val stripDebug: Property<Boolean> = objects.notNullProperty(true)
@get:Input
internal val noHeaderFiles: Property<Boolean> = objects.notNullProperty(true)
@get:Input
internal val noManPages: Property<Boolean> = objects.notNullProperty(true)
@get:Input
internal val stripNativeCommands: Property<Boolean> = objects.notNullProperty(true)
@get:Input
@get:Optional
internal val compressionLevel: Property<RuntimeCompressionLevel?> = objects.nullableProperty()
override fun makeArgs(tmpDir: File): MutableList<String> = super.makeArgs(tmpDir).apply {
modules.get().forEach { m ->
cliArg("--add-modules", m)
}
cliArg("--strip-debug", stripDebug)
cliArg("--no-header-files", noHeaderFiles)
cliArg("--no-man-pages", noManPages)
cliArg("--strip-native-commands", stripNativeCommands)
cliArg("--compress", compressionLevel.orNull?.id )
cliArg("--output", destinationDir)
}
}

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

@ -19,9 +19,6 @@ abstract class AbstractJPackageTask @Inject constructor(
@get:Input
val targetFormat: TargetFormat,
) : AbstractJvmToolOperationTask("jpackage") {
@get:OutputDirectory
val destinationDir: DirectoryProperty = objects.directoryProperty()
@get:InputFiles
val files: ConfigurableFileCollection = objects.fileCollection()
@ -158,19 +155,20 @@ abstract class AbstractJPackageTask @Inject constructor(
@get:Optional
val winUpgradeUuid: Property<String?> = objects.nullableProperty()
@get:Input
val modules: ListProperty<String> = objects.listProperty(String::class.java)
@get:InputDirectory
@get:Optional
val runtimeImage: DirectoryProperty = objects.directoryProperty()
override fun makeArgs(tmpDir: File): MutableList<String> = super.makeArgs(tmpDir).apply {
cliArg("--input", tmpDir)
cliArg("--type", targetFormat.id)
cliArg("--dest", destinationDir.asFile.get())
cliArg("--dest", destinationDir.ioFile)
cliArg("--verbose", verbose)
cliArg("--install-dir", installationPath)
cliArg("--license-file", licenseFile.asFile.orNull)
cliArg("--icon", iconFile.asFile.orNull)
cliArg("--license-file", licenseFile.ioFileOrNull)
cliArg("--icon", iconFile.ioFileOrNull)
cliArg("--name", packageName)
cliArg("--description", packageDescription)
@ -178,7 +176,7 @@ abstract class AbstractJPackageTask @Inject constructor(
cliArg("--app-version", packageVersion)
cliArg("--vendor", packageVendor)
cliArg("--main-jar", launcherMainJar.asFile.get().name)
cliArg("--main-jar", launcherMainJar.ioFile.name)
cliArg("--main-class", launcherMainClass)
launcherArgs.orNull?.forEach {
cliArg("--arguments", it)
@ -202,7 +200,7 @@ abstract class AbstractJPackageTask @Inject constructor(
cliArg("--mac-package-name", macPackageName)
cliArg("--mac-bundle-signing-prefix", macBundleSigningPrefix)
cliArg("--mac-sign", macSign)
cliArg("--mac-signing-keychain", macSigningKeychain.asFile.orNull)
cliArg("--mac-signing-keychain", macSigningKeychain.ioFileOrNull)
cliArg("--mac-signing-key-user-name", macSigningKeyUserName)
}
OS.Windows -> {
@ -216,13 +214,11 @@ abstract class AbstractJPackageTask @Inject constructor(
}
}
modules.get().forEach { m ->
cliArg("--add-modules", m)
}
cliArg("--runtime-image", runtimeImage)
}
override fun prepareWorkingDir(inputChanges: InputChanges) {
fileOperations.delete(destinationDir)
val workingDir = workingDir.ioFile
if (inputChanges.isIncremental) {
logger.debug("Updating working dir incrementally: $workingDir")
@ -243,7 +239,7 @@ abstract class AbstractJPackageTask @Inject constructor(
} else {
logger.debug("Updating working dir non-incrementally: $workingDir")
fileOperations.delete(workingDir)
workingDir.mkdirs()
fileOperations.mkdir(workingDir)
fileOperations.copy {
it.from(files)
it.from(launcherMainJar)
@ -259,7 +255,7 @@ abstract class AbstractJPackageTask @Inject constructor(
private fun configureWixPathIfNeeded(exec: ExecSpec) {
if (currentOS == OS.Windows) {
val wixDir = wixToolsetDir.asFile.orNull ?: return
val wixDir = wixToolsetDir.ioFileOrNull ?: return
val wixPath = wixDir.absolutePath
val path = System.getenv("PATH") ?: ""
exec.environment("PATH", "$wixPath;$path")
@ -269,10 +265,11 @@ abstract class AbstractJPackageTask @Inject constructor(
override fun checkResult(result: ExecResult) {
super.checkResult(result)
val destinationDirFile = destinationDir.asFile.get()
val finalLocation = when (targetFormat) {
TargetFormat.AppImage -> destinationDirFile
else -> destinationDirFile.walk().first { it.isFile && it.name.endsWith(targetFormat.fileExt) }
val finalLocation = destinationDir.ioFile.let { destinationDir ->
when (targetFormat) {
TargetFormat.AppImage -> destinationDir
else -> destinationDir.walk().first { it.isFile && it.name.endsWith(targetFormat.fileExt) }
}
}
logger.lifecycle("The distribution is written to ${finalLocation.canonicalPath}")
}

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

@ -1,16 +1,20 @@
package org.jetbrains.compose.desktop.application.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.internal.file.FileOperations
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
import org.gradle.process.ExecResult
import org.gradle.process.ExecSpec
import org.gradle.work.InputChanges
import org.jetbrains.compose.desktop.application.internal.*
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
import org.jetbrains.compose.desktop.application.internal.OS
import org.jetbrains.compose.desktop.application.internal.currentOS
@ -29,7 +33,10 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
protected abstract val fileOperations: FileOperations
@get:LocalState
protected val workingDir: File = project.buildDir.resolve("compose/tmp/$name")
protected val workingDir: Provider<Directory> = project.layout.buildDirectory.dir("compose/tmp/$name")
@get:OutputDirectory
val destinationDir: DirectoryProperty = objects.directoryProperty()
@get:Input
@get:Optional
@ -47,7 +54,7 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
protected open fun prepareWorkingDir(inputChanges: InputChanges) {
fileOperations.delete(workingDir)
workingDir.mkdirs()
fileOperations.mkdir(workingDir)
}
protected open fun makeArgs(tmpDir: File): MutableList<String> = arrayListOf<String>().apply {
@ -69,10 +76,14 @@ abstract class AbstractJvmToolOperationTask(private val toolName: String) : Defa
"Ensure JAVA_HOME or buildSettings.javaHome is set to JDK 14 or newer"
}
fileOperations.delete(destinationDir)
prepareWorkingDir(inputChanges)
val args = makeArgs(workingDir)
val argsFile = workingDir.parentFile.resolve("${name}.args.txt")
argsFile.writeText(args.joinToString("\n"))
val argsFile = workingDir.ioFile.let { dir ->
val args = makeArgs(dir)
dir.resolveSibling("${name}.args.txt").apply {
writeText(args.joinToString("\n"))
}
}
try {
execOperations.exec { exec ->

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

@ -29,9 +29,8 @@ abstract class AbstractRunDistributableTask @Inject constructor(
@TaskAction
fun run() {
val appDir = appImageRootDir.get().let { appImageRoot ->
val files = appImageRoot.asFile.listFiles()
val appDir = appImageRootDir.ioFile.let { appImageRoot ->
val files = appImageRoot.listFiles()
if (files == null || files.isEmpty()) {
error("Could not find application image: $appImageRoot is empty!")
} else if (files.size > 1) {

Loading…
Cancel
Save