From d6836d12823a44fbb75fda6bcf65e9398c962007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCrten?= Date: Tue, 20 Sep 2022 09:41:36 +0200 Subject: [PATCH] Add support for adding custom resources for jpackage --- .../application/dsl/AbstractDistributions.kt | 1 + .../internal/configureJvmApplication.kt | 37 +++++++++++--- .../application/tasks/AbstractJPackageTask.kt | 51 ++++++++++++++----- .../README.md | 32 ++++++++++++ 4 files changed, 101 insertions(+), 20 deletions(-) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/AbstractDistributions.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/AbstractDistributions.kt index e3db8d7d70c..7a935ac0c68 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/AbstractDistributions.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/dsl/AbstractDistributions.kt @@ -30,6 +30,7 @@ abstract class AbstractDistributions { var description: String? = null var vendor: String? = null val appResourcesRootDir: DirectoryProperty = objects.directoryProperty() + val jpackageResourcesRootDir: DirectoryProperty = objects.directoryProperty() val licenseFile: RegularFileProperty = objects.fileProperty() var targetFormats: Set = EnumSet.noneOf(TargetFormat::class.java) diff --git a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt index 32f3d335096..2609c4ce3f7 100644 --- a/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt +++ b/gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/desktop/application/internal/configureJvmApplication.kt @@ -17,7 +17,6 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.compose.desktop.application.internal.validation.validatePackageVersions import org.jetbrains.compose.desktop.application.tasks.* import org.jetbrains.compose.desktop.tasks.AbstractUnpackDefaultComposeApplicationResourcesTask -import org.jetbrains.compose.internal.joinDashLowercaseNonEmpty import java.io.File private val defaultJvmArgs = listOf("-D$CONFIGURE_SWING_GLOBALS=true") @@ -45,6 +44,7 @@ internal class CommonJvmDesktopTasks( val checkRuntime: TaskProvider, val suggestRuntimeModules: TaskProvider, val prepareAppResources: TaskProvider, + val prepareJPackageResources: TaskProvider, val createRuntimeImage: TaskProvider ) @@ -89,6 +89,19 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes into(jvmTmpDirForTask()) } + val prepareJPackageResources = tasks.register( + taskNameAction = "prepare", + taskNameObject = "JPackageResources" + ) { + val jpackageResourcesRootDir = app.nativeDistributions.jpackageResourcesRootDir + if (jpackageResourcesRootDir.isPresent) { + from(jpackageResourcesRootDir.dir("common")) + from(jpackageResourcesRootDir.dir(currentOS.id)) + from(jpackageResourcesRootDir.dir(currentTarget.id)) + } + into(jvmTmpDirForTask()) + } + val createRuntimeImage = tasks.register( taskNameAction = "create", taskNameObject = "runtimeImage" @@ -106,6 +119,7 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes checkRuntime, suggestRuntimeModules, prepareAppResources, + prepareJPackageResources, createRuntimeImage ) } @@ -131,6 +145,7 @@ private fun JvmApplicationContext.configurePackagingTasks( this, createRuntimeImage = commonTasks.createRuntimeImage, prepareAppResources = commonTasks.prepareAppResources, + prepareJPackageResources = commonTasks.prepareJPackageResources, checkRuntime = commonTasks.checkRuntime, unpackDefaultResources = commonTasks.unpackDefaultResources, runProguard = runProguard @@ -154,6 +169,7 @@ private fun JvmApplicationContext.configurePackagingTasks( this, createRuntimeImage = commonTasks.createRuntimeImage, prepareAppResources = commonTasks.prepareAppResources, + prepareJPackageResources = commonTasks.prepareJPackageResources, checkRuntime = commonTasks.checkRuntime, unpackDefaultResources = commonTasks.unpackDefaultResources, runProguard = runProguard @@ -217,7 +233,7 @@ private fun JvmApplicationContext.configurePackagingTasks( ) val run = tasks.register(taskNameAction = "run") { - configureRunTask(this, commonTasks.prepareAppResources) + configureRunTask(this, commonTasks.prepareAppResources, commonTasks.prepareJPackageResources) } } @@ -248,6 +264,7 @@ private fun JvmApplicationContext.configurePackageTask( createAppImage: TaskProvider? = null, createRuntimeImage: TaskProvider? = null, prepareAppResources: TaskProvider? = null, + prepareJPackageResources: TaskProvider? = null, checkRuntime: TaskProvider? = null, unpackDefaultResources: TaskProvider, runProguard: Provider? = null @@ -264,12 +281,18 @@ private fun JvmApplicationContext.configurePackageTask( packageTask.runtimeImage.set(createRuntimeImage.flatMap { it.destinationDir }) } - prepareAppResources?.let { prepareResources -> - packageTask.dependsOn(prepareResources) - val resourcesDir = packageTask.project.layout.dir(prepareResources.map { it.destinationDir }) + prepareAppResources?.let { prepareAppResources -> + packageTask.dependsOn(prepareAppResources) + val resourcesDir = packageTask.project.layout.dir(prepareAppResources.map { it.destinationDir }) packageTask.appResourcesDir.set(resourcesDir) } + prepareJPackageResources?.let { prepareJPackageResources -> + packageTask.dependsOn(prepareJPackageResources) + val resourcesDir = packageTask.project.layout.dir(prepareJPackageResources.map { it.destinationDir }) + packageTask.jpackageResourcesDir.set(resourcesDir) + } + checkRuntime?.let { checkRuntime -> packageTask.dependsOn(checkRuntime) packageTask.javaRuntimePropertiesFile.set(checkRuntime.flatMap { it.javaRuntimePropertiesFile }) @@ -376,9 +399,11 @@ internal fun JvmApplicationContext.configurePlatformSettings( private fun JvmApplicationContext.configureRunTask( exec: JavaExec, - prepareAppResources: TaskProvider + prepareAppResources: TaskProvider, + prepareJPackageResources: TaskProvider ) { exec.dependsOn(prepareAppResources) + exec.dependsOn(prepareJPackageResources) exec.mainClass.set(exec.provider { app.mainClass }) exec.executable(javaExecutable(app.javaHome)) 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 b26cf12d05a..a84963e78a2 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 @@ -283,6 +283,26 @@ abstract class AbstractJPackageTask @Inject constructor( internal val appResourcesDirInputDirHackForVerification: Provider get() = appResourcesDir.map { it.takeIf { it.asFile.exists() } } + @get:Internal + val jpackageResourcesDir: DirectoryProperty = objects.directoryProperty() + + /** + * Gradle runtime verification fails, + * if InputDirectory is not null, but a directory does not exist. + * The directory might not exist, because prepareJPackageResources task + * does not create output directory if there are no resources. + * + * To work around this, jpackageResourcesDir is used as a real property, + * but it is annotated as @Internal, so it ignored during inputs checking. + * This property is used only for inputs checking. + * It returns appResourcesDir value if the underlying directory exists. + */ + @Suppress("unused") + @get:InputDirectory + @get:Optional + internal val jpackageResourcesDirInputDirHackForVerification: Provider + get() = jpackageResourcesDir.map { it.takeIf { it.asFile.exists() } } + @get:Internal private val libsMappingFile: Provider = workingDir.map { it.file("libs-mapping.txt") @@ -477,24 +497,14 @@ abstract class AbstractJPackageTask @Inject constructor( // todo: incremental copy cleanDirs(packagedResourcesDir) val destResourcesDir = packagedResourcesDir.ioFile - val appResourcesDir = appResourcesDir.ioFileOrNull - if (appResourcesDir != null) { - for (file in appResourcesDir.walk()) { - val relPath = file.relativeTo(appResourcesDir).path - val destFile = destResourcesDir.resolve(relPath) - if (file.isDirectory) { - fileOperations.mkdir(destFile) - } else { - file.copyTo(destFile) - } - } - } + appResourcesDir.ioFileOrNull?.copyContentsTo(destResourcesDir) cleanDirs(jpackageResources) + val jpackageResources = jpackageResources.ioFile if (currentOS == OS.MacOS) { InfoPlistBuilder(macExtraPlistKeysRawXml.orNull) .also { setInfoPlistValues(it) } - .writeToFile(jpackageResources.ioFile.resolve("Info.plist")) + .writeToFile(jpackageResources.resolve("Info.plist")) if (macAppStore.orNull == true) { val productDefPlistXml = """ @@ -504,7 +514,20 @@ abstract class AbstractJPackageTask @Inject constructor( """.trimIndent() InfoPlistBuilder(productDefPlistXml) - .writeToFile(jpackageResources.ioFile.resolve("product-def.plist")) + .writeToFile(jpackageResources.resolve("product-def.plist")) + } + } + jpackageResourcesDir.ioFileOrNull?.copyContentsTo(jpackageResources) + } + + private fun File.copyContentsTo(destination: File) { + for (file in walk()) { + val relPath = file.relativeTo(this).path + val destFile = destination.resolve(relPath) + if (file.isDirectory) { + fileOperations.mkdir(destFile) + } else { + file.copyTo(destFile) } } } diff --git a/tutorials/Native_distributions_and_local_execution/README.md b/tutorials/Native_distributions_and_local_execution/README.md index 0b71644ce20..96cec8ebed2 100755 --- a/tutorials/Native_distributions_and_local_execution/README.md +++ b/tutorials/Native_distributions_and_local_execution/README.md @@ -580,6 +580,38 @@ fun main() { 3. Run `./gradlew runDistributable`. 4. Links like `compose://foo/bar` are now redirected from a browser to your application. +## Customizing data passed to `jpackage` via `--resource-dir` + +In some cases it can be useful to pass files to `jpackage` via the `--resource-dir` +option. For example on Windows, it is possible to customize the generated install +wizard that way as the Wix toolchain used to build the installer uses that directory +for looking for custom configuration files. + +To do so, specify a root resource directory via DSL: +``` +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageVersion = "1.0.0" + + jpackageResourcesRootDir.set(project.layout.projectDirectory.dir("jpackage-resources")) + } + } +} +``` +In the example above a root resource directory is set to `/jpackage-resources`. + +Compose Gradle plugin will include all files under the following subdirectories: +1. Files from `/common` will be included into all packages. +2. Files from `/` will be included only into packages for +a specific OS. Possible values for `` are: `windows`, `macos`, `linux`. +3. Files from `/-` will be included only into packages for + a specific combination of OS and CPU architecture. Possible values for `` are: `x64` and `arm64`. +For example, files from `/macos-arm64` will be included only into packages built for Apple Silicon +Macs. + ## Obfuscation To obfuscate Compose Multiplatform JVM applications the standard approach for JVM applications works.