diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/RuntimeCompressionLevel.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/RuntimeCompressionLevel.kt new file mode 100644 index 0000000000..d710e914a9 --- /dev/null +++ b/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) +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt index 920b59db06..fb694bdeee 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureApplication.kt +++ b/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) { configureRunTask(app) } + val createRuntimeImage = tasks.composeTask( + 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( taskName("package", app, targetFormat.name), args = listOf(targetFormat) ) { - configurePackagingTask(app) + configurePackagingTask(app, createRuntimeImage) } } @@ -77,7 +86,7 @@ internal fun Project.configurePackagingTasks(apps: Collection) { taskName("createDistributable", app), args = listOf(TargetFormat.AppImage) ) { - configurePackagingTask(app) + configurePackagingTask(app, createRuntimeImage) } val runDistributable = project.tasks.composeTask( @@ -88,10 +97,15 @@ internal fun Project.configurePackagingTasks(apps: Collection) { } internal fun AbstractJPackageTask.configurePackagingTask( - app: Application + app: Application, + createRuntimeImage: TaskProvider ) { 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}") } } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/dslUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/dslUtils.kt index b28787d57d..28d4b9cbd1 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/dslUtils.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/dslUtils.kt @@ -12,5 +12,8 @@ internal inline fun ObjectFactory.nullableProperty(): Property internal inline fun ObjectFactory.notNullProperty(): Property = property(T::class.java) +internal inline fun ObjectFactory.notNullProperty(defaultValue: T): Property = + property(T::class.java).value(defaultValue) + internal inline fun Task.provider(noinline fn: () -> T): Provider = project.provider(fn) \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/gradleUtils.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/gradleUtils.kt new file mode 100644 index 0000000000..2c15e193a5 --- /dev/null +++ b/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 Provider.ioFile: File + get() = get().asFile + +internal val Provider.ioFileOrNull: File? + get() = orNull?.asFile diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJLinkTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJLinkTask.kt new file mode 100644 index 0000000000..e4050fbcfe --- /dev/null +++ b/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 = objects.listProperty(String::class.java) + + @get:Input + internal val stripDebug: Property = objects.notNullProperty(true) + + @get:Input + internal val noHeaderFiles: Property = objects.notNullProperty(true) + + @get:Input + internal val noManPages: Property = objects.notNullProperty(true) + + @get:Input + internal val stripNativeCommands: Property = objects.notNullProperty(true) + + @get:Input + @get:Optional + internal val compressionLevel: Property = objects.nullableProperty() + + override fun makeArgs(tmpDir: File): MutableList = 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) + } +} \ No newline at end of file diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt index 2dd0fff996..e342427701 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJPackageTask.kt +++ b/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 = objects.nullableProperty() - @get:Input - val modules: ListProperty = objects.listProperty(String::class.java) + @get:InputDirectory + @get:Optional + val runtimeImage: DirectoryProperty = objects.directoryProperty() override fun makeArgs(tmpDir: File): MutableList = 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}") } diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt index 565ada51b8..e37a338159 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractJvmToolOperationTask.kt +++ b/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 = 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 = arrayListOf().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 -> diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractRunDistributableTask.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractRunDistributableTask.kt index 76d992c6a8..7938641c91 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/tasks/AbstractRunDistributableTask.kt +++ b/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) {