Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for adding custom resources for jpackage #2331

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<TargetFormat> = EnumSet.noneOf(TargetFormat::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -45,6 +44,7 @@ internal class CommonJvmDesktopTasks(
val checkRuntime: TaskProvider<AbstractCheckNativeDistributionRuntime>,
val suggestRuntimeModules: TaskProvider<AbstractSuggestModulesTask>,
val prepareAppResources: TaskProvider<Sync>,
val prepareJPackageResources: TaskProvider<Sync>,
val createRuntimeImage: TaskProvider<AbstractJLinkTask>
)

Expand Down Expand Up @@ -89,6 +89,19 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes
into(jvmTmpDirForTask())
}

val prepareJPackageResources = tasks.register<Sync>(
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<AbstractJLinkTask>(
taskNameAction = "create",
taskNameObject = "runtimeImage"
Expand All @@ -106,6 +119,7 @@ private fun JvmApplicationContext.configureCommonJvmDesktopTasks(): CommonJvmDes
checkRuntime,
suggestRuntimeModules,
prepareAppResources,
prepareJPackageResources,
createRuntimeImage
)
}
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -217,7 +233,7 @@ private fun JvmApplicationContext.configurePackagingTasks(
)

val run = tasks.register<JavaExec>(taskNameAction = "run") {
configureRunTask(this, commonTasks.prepareAppResources)
configureRunTask(this, commonTasks.prepareAppResources, commonTasks.prepareJPackageResources)
}
}

Expand Down Expand Up @@ -248,6 +264,7 @@ private fun JvmApplicationContext.configurePackageTask(
createAppImage: TaskProvider<AbstractJPackageTask>? = null,
createRuntimeImage: TaskProvider<AbstractJLinkTask>? = null,
prepareAppResources: TaskProvider<Sync>? = null,
prepareJPackageResources: TaskProvider<Sync>? = null,
checkRuntime: TaskProvider<AbstractCheckNativeDistributionRuntime>? = null,
unpackDefaultResources: TaskProvider<AbstractUnpackDefaultComposeApplicationResourcesTask>,
runProguard: Provider<AbstractProguardTask>? = null
Expand All @@ -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 })
Expand Down Expand Up @@ -376,9 +399,11 @@ internal fun JvmApplicationContext.configurePlatformSettings(

private fun JvmApplicationContext.configureRunTask(
exec: JavaExec,
prepareAppResources: TaskProvider<Sync>
prepareAppResources: TaskProvider<Sync>,
prepareJPackageResources: TaskProvider<Sync>
) {
exec.dependsOn(prepareAppResources)
exec.dependsOn(prepareJPackageResources)

exec.mainClass.set(exec.provider { app.mainClass })
exec.executable(javaExecutable(app.javaHome))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,26 @@ abstract class AbstractJPackageTask @Inject constructor(
internal val appResourcesDirInputDirHackForVerification: Provider<Directory>
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<Directory>
get() = jpackageResourcesDir.map { it.takeIf { it.asFile.exists() } }

@get:Internal
private val libsMappingFile: Provider<RegularFile> = workingDir.map {
it.file("libs-mapping.txt")
Expand Down Expand Up @@ -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 = """
Expand All @@ -504,7 +514,20 @@ abstract class AbstractJPackageTask @Inject constructor(
</array>
""".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)
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions tutorials/Native_distributions_and_local_execution/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<PROJECT_DIR>/jpackage-resources`.

Compose Gradle plugin will include all files under the following subdirectories:
1. Files from `<RESOURCES_ROOT_DIR>/common` will be included into all packages.
2. Files from `<RESOURCES_ROOT_DIR>/<OS_NAME>` will be included only into packages for
a specific OS. Possible values for `<OS_NAME>` are: `windows`, `macos`, `linux`.
3. Files from `<RESOURCES_ROOT_DIR>/<OS_NAME>-<ARCH_NAME>` will be included only into packages for
a specific combination of OS and CPU architecture. Possible values for `<ARCH_NAME>` are: `x64` and `arm64`.
For example, files from `<RESOURCES_ROOT_DIR>/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.
Expand Down