From 8822eba9b25b5827a5eb3c156d951e05ab02e7bd Mon Sep 17 00:00:00 2001 From: CloudyDino Date: Thu, 28 May 2026 14:35:24 -0500 Subject: [PATCH] Add packagingResourcesRootDir to expose jpackage --resource-dir jpackage's --resource-dir is the documented override mechanism for packaging resources (DMG volume icon via -volume.icns, DMG background, Linux preinst/postinst scripts, WiX templates, ...), but Compose fully owns that directory: AbstractJPackageTask clears it and repopulates it with the generated Info.plist on every run, so users have no supported way to contribute their own files to it. Add a packagingResourcesRootDir DSL property, mirroring appResourcesRootDir (with common//- subdirectories). Its files are copied into the jpackage resource dir after it is cleared but before the generated Info.plist/product-def.plist are written, so the Compose-managed plists always take precedence over user-supplied files. This revives the approach a maintainer suggested on #1843 and supersedes the abandoned #3647. Unlike #1843 -- which repointed jpackageResources at a Sync output and so had its files removed by clearDirs -- the files are copied in after the clear, so nothing is wiped. Addresses CMP-2042, CMP-6411, CMP-1972, CMP-9837. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../application/dsl/AbstractDistributions.kt | 14 ++++++++ .../internal/configureJvmApplication.kt | 1 + .../application/tasks/AbstractJPackageTask.kt | 33 +++++++++++++++++++ .../integration/DesktopApplicationTest.kt | 13 ++++++++ .../packagingResources/build.gradle | 24 ++++++++++++++ .../common/common-packaging-resource.txt | 1 + .../linux/os-specific-packaging-resource.txt | 1 + .../macos/os-specific-packaging-resource.txt | 1 + .../os-specific-packaging-resource.txt | 1 + .../packagingResources/settings.gradle | 27 +++++++++++++++ .../src/main/kotlin/main.kt | 3 ++ 11 files changed, 119 insertions(+) create mode 100644 gradle-plugins/compose/src/test/test-projects/application/packagingResources/build.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/common/common-packaging-resource.txt create mode 100644 gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/linux/os-specific-packaging-resource.txt create mode 100644 gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/macos/os-specific-packaging-resource.txt create mode 100644 gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/windows/os-specific-packaging-resource.txt create mode 100644 gradle-plugins/compose/src/test/test-projects/application/packagingResources/settings.gradle create mode 100644 gradle-plugins/compose/src/test/test-projects/application/packagingResources/src/main/kotlin/main.kt 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..bde4123723d 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,20 @@ abstract class AbstractDistributions { var description: String? = null var vendor: String? = null val appResourcesRootDir: DirectoryProperty = objects.directoryProperty() + + /** + * Root directory for files passed to the underlying packaging tool (jpackage) + * via `--resource-dir`, used to override the tool's own packaging resources (e.g. + * a `-volume.icns` DMG volume icon, a DMG background, Linux + * `preinst`/`postinst` scripts, or WiX templates). See + * https://docs.oracle.com/en/java/javase/21/jpackage/override-jpackage-resources.html + * + * Files are resolved from `common`, `` and `-` subdirectories, the + * same layout as [appResourcesRootDir]. The generated `Info.plist` and + * `product-def.plist` are managed by Compose and always take precedence. + */ + val packagingResourcesRootDir: 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 35dec15a2fb..3e04ae13873 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 @@ -318,6 +318,7 @@ private fun JvmApplicationContext.configurePackageTask( packageTask.packageVendor.set(packageTask.provider { executables.vendor }) packageTask.packageVersion.set(packageVersionFor(packageTask.targetFormat)) packageTask.licenseFile.set(executables.licenseFile) + packageTask.packagingResourcesRootDir.set(executables.packagingResourcesRootDir) } packageTask.destinationDir.set(app.nativeDistributions.outputBaseDir.map { 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 8f466487267..24b426ed0c8 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 @@ -377,6 +377,11 @@ abstract class AbstractJPackageTask @Inject constructor( @get:Internal val appResourcesDir: DirectoryProperty = objects.directoryProperty() + @get:InputDirectory + @get:Optional + @get:PathSensitive(PathSensitivity.RELATIVE) + val packagingResourcesRootDir: DirectoryProperty = objects.directoryProperty() + @get:Internal private val libsMappingFile: Provider = workingDir.map { it.file("libs-mapping.txt") @@ -607,6 +612,7 @@ abstract class AbstractJPackageTask @Inject constructor( } fileOperations.clearDirs(jpackageResources) + copyUserPackagingResources() if (currentOS == OS.MacOS) { InfoPlistBuilder(macExtraPlistKeysRawXml.orNull) .also { setInfoPlistValues(it) } @@ -626,6 +632,33 @@ abstract class AbstractJPackageTask @Inject constructor( } } + /** + * Copies user-supplied packaging override resources into [jpackageResources] + * (the `--resource-dir` passed to jpackage). Files are taken from the `common`, + * `` and `-` subdirectories of [packagingResourcesRootDir], mirroring + * the layout of `appResourcesRootDir`. Called after [jpackageResources] is cleared + * but before the generated `Info.plist`/`product-def.plist` are written, so those + * Compose-managed files always win over anything the user drops in. + */ + private fun copyUserPackagingResources() { + val rootDir = packagingResourcesRootDir.ioFileOrNull ?: return + val destDir = jpackageResources.ioFile + for (subDirName in listOf("common", currentOS.id, currentTarget.id)) { + val sourceDir = rootDir.resolve(subDirName) + if (!sourceDir.isDirectory) continue + for (file in sourceDir.walk()) { + val relPath = file.relativeTo(sourceDir).path + if (relPath.isEmpty()) continue + val destFile = destDir.resolve(relPath) + if (file.isDirectory) { + fileOperations.mkdirs(destFile) + } else { + file.copyTo(destFile, overwrite = true) + } + } + } + } + override fun jvmToolEnvironment(): MutableMap = super.jvmToolEnvironment().apply { if (currentOS == OS.Windows) { diff --git a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt index f49f7ebaf50..d4ecf56af75 100644 --- a/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt +++ b/gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/DesktopApplicationTest.kt @@ -593,6 +593,19 @@ class DesktopApplicationTest : GradlePluginTestBase() { } } + @Test + fun packagingResources() = with(testProject("application/packagingResources")) { + gradle(":createDistributable").checks { + check.taskSuccessful(":createDistributable") + + // Files from packagingResourcesRootDir must be copied into the + // directory passed to jpackage via --resource-dir, for both the + // `common` and the current-OS subdirectories. + file("build/compose/tmp/resources/common-packaging-resource.txt").checkExists() + file("build/compose/tmp/resources/os-specific-packaging-resource.txt").checkExists() + } + } + @Test fun testWixUnzip() { Assumptions.assumeTrue(currentOS == OS.Windows) { "The test is only relevant for Windows" } diff --git a/gradle-plugins/compose/src/test/test-projects/application/packagingResources/build.gradle b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/build.gradle new file mode 100644 index 00000000000..d79c97386f3 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/build.gradle @@ -0,0 +1,24 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.plugin.compose" + id "org.jetbrains.compose" +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib" + implementation compose.desktop.currentOs +} + +compose.desktop { + application { + mainClass = "MainKt" + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageVersion = "1.0.0" + + packagingResourcesRootDir.set(project.layout.projectDirectory.dir("packaging-resources")) + } + } +} diff --git a/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/common/common-packaging-resource.txt b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/common/common-packaging-resource.txt new file mode 100644 index 00000000000..918a79b948c --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/common/common-packaging-resource.txt @@ -0,0 +1 @@ +common packaging resource diff --git a/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/linux/os-specific-packaging-resource.txt b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/linux/os-specific-packaging-resource.txt new file mode 100644 index 00000000000..a641fd658b8 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/linux/os-specific-packaging-resource.txt @@ -0,0 +1 @@ +linux only packaging resource diff --git a/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/macos/os-specific-packaging-resource.txt b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/macos/os-specific-packaging-resource.txt new file mode 100644 index 00000000000..722df330caa --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/macos/os-specific-packaging-resource.txt @@ -0,0 +1 @@ +macos only packaging resource diff --git a/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/windows/os-specific-packaging-resource.txt b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/windows/os-specific-packaging-resource.txt new file mode 100644 index 00000000000..a14ff486567 --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/packaging-resources/windows/os-specific-packaging-resource.txt @@ -0,0 +1 @@ +windows only packaging resource diff --git a/gradle-plugins/compose/src/test/test-projects/application/packagingResources/settings.gradle b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/settings.gradle new file mode 100644 index 00000000000..b2f66cf78ed --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/settings.gradle @@ -0,0 +1,27 @@ +pluginManagement { + plugins { + id 'org.jetbrains.kotlin.jvm' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER' + id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER' + } + repositories { + mavenLocal() + gradlePluginPortal() + mavenCentral() + google() + maven { + url 'https://packages.jetbrains.team/maven/p/cmp/dev' + } + } +} +dependencyResolutionManagement { + repositories { + mavenCentral() + google() + maven { + url 'https://packages.jetbrains.team/maven/p/cmp/dev' + } + mavenLocal() + } +} +rootProject.name = "packagingResources" diff --git a/gradle-plugins/compose/src/test/test-projects/application/packagingResources/src/main/kotlin/main.kt b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/src/main/kotlin/main.kt new file mode 100644 index 00000000000..b63534132cd --- /dev/null +++ b/gradle-plugins/compose/src/test/test-projects/application/packagingResources/src/main/kotlin/main.kt @@ -0,0 +1,3 @@ +fun main() { + println("Hello, world!") +}