You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
13 KiB
326 lines
13 KiB
package org.jetbrains.compose.desktop.application.internal |
|
|
|
import org.gradle.api.* |
|
import org.gradle.api.file.Directory |
|
import org.gradle.api.file.DuplicatesStrategy |
|
import org.gradle.api.file.FileCollection |
|
import org.gradle.api.plugins.JavaPluginConvention |
|
import org.gradle.api.provider.Provider |
|
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.internal.validation.validatePackageVersions |
|
import org.jetbrains.compose.desktop.application.tasks.* |
|
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension |
|
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType |
|
import java.io.File |
|
import java.util.* |
|
|
|
// todo: multiple launchers |
|
// todo: file associations |
|
// todo: use workers |
|
fun configureApplicationImpl(project: Project, app: Application) { |
|
if (app._isDefaultConfigurationEnabled) { |
|
if (project.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform")) { |
|
project.configureFromMppPlugin(app) |
|
} else if (project.plugins.hasPlugin("org.jetbrains.kotlin.jvm")) { |
|
val mainSourceSet = project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.getByName("main") |
|
app.from(mainSourceSet) |
|
} |
|
} |
|
project.validatePackageVersions(app) |
|
project.configurePackagingTasks(listOf(app)) |
|
project.configureWix() |
|
} |
|
|
|
internal fun Project.configureFromMppPlugin(mainApplication: Application) { |
|
val kotlinExt = extensions.getByType(KotlinMultiplatformExtension::class.java) |
|
var isJvmTargetConfigured = false |
|
kotlinExt.targets.all { target -> |
|
if (target.platformType == KotlinPlatformType.jvm) { |
|
if (!isJvmTargetConfigured) { |
|
mainApplication.from(target) |
|
isJvmTargetConfigured = true |
|
} else { |
|
logger.error("w: Default configuration for Compose Desktop Application is disabled: " + |
|
"multiple Kotlin JVM targets definitions are detected. " + |
|
"Specify, which target to use by using `compose.desktop.application.from(kotlinMppTarget)`") |
|
mainApplication.disableDefaultConfiguration() |
|
} |
|
} |
|
} |
|
} |
|
|
|
internal fun Project.configurePackagingTasks(apps: Collection<Application>) { |
|
for (app in apps) { |
|
val checkRuntime = tasks.composeTask<AbstractCheckNativeDistributionRuntime>( |
|
taskName("checkRuntime", app) |
|
) { |
|
javaHome.set(provider { app.javaHomeOrDefault() }) |
|
javaRuntimePropertiesFile.set( |
|
project.layout.buildDirectory.file("compose/tmp/${app.name}/runtime-properties/properties.bin") |
|
) |
|
} |
|
|
|
val createRuntimeImage = tasks.composeTask<AbstractJLinkTask>( |
|
taskName("createRuntimeImage", app) |
|
) { |
|
dependsOn(checkRuntime) |
|
javaHome.set(provider { app.javaHomeOrDefault() }) |
|
modules.set(provider { app.nativeDistributions.modules }) |
|
includeAllModules.set(provider { app.nativeDistributions.includeAllModules }) |
|
javaRuntimePropertiesFile.set(checkRuntime.flatMap { it.javaRuntimePropertiesFile }) |
|
destinationDir.set(project.layout.buildDirectory.dir("compose/tmp/${app.name}/runtime")) |
|
} |
|
|
|
val packageFormats = app.nativeDistributions.targetFormats.map { targetFormat -> |
|
val packageFormat = tasks.composeTask<AbstractJPackageTask>( |
|
taskName("package", app, targetFormat.name), |
|
args = listOf(targetFormat) |
|
) { |
|
configurePackagingTask(app, createRuntimeImage = createRuntimeImage) |
|
} |
|
|
|
if (targetFormat.isCompatibleWith(OS.MacOS)) { |
|
check(targetFormat == TargetFormat.Dmg || targetFormat == TargetFormat.Pkg) { |
|
"Unexpected target format for MacOS: $targetFormat" |
|
} |
|
|
|
val notarizationRequestsDir = project.layout.buildDirectory.dir("compose/notarization/${app.name}") |
|
val upload = tasks.composeTask<AbstractUploadAppForNotarizationTask>( |
|
taskName("notarize", app, targetFormat.name), |
|
args = listOf(targetFormat) |
|
) { |
|
configureUploadForNotarizationTask(app, packageFormat, notarizationRequestsDir) |
|
} |
|
|
|
tasks.composeTask<AbstractCheckNotarizationStatusTask>( |
|
taskName("checkNotarizationStatus", app) |
|
) { |
|
configureCheckNotarizationStatusTask(app, notarizationRequestsDir) |
|
} |
|
} |
|
|
|
packageFormat |
|
} |
|
|
|
val packageAll = tasks.composeTask<DefaultTask>(taskName("package", app)) { |
|
dependsOn(packageFormats) |
|
} |
|
|
|
val packageUberJarForCurrentOS = project.tasks.composeTask<Jar>(taskName("package", app, "uberJarForCurrentOS")) { |
|
configurePackageUberJarForCurrentOS(app) |
|
} |
|
|
|
val createDistributable = tasks.composeTask<AbstractJPackageTask>( |
|
taskName("createDistributable", app), |
|
args = listOf(TargetFormat.AppImage) |
|
) { |
|
configurePackagingTask(app, createRuntimeImage = createRuntimeImage) |
|
} |
|
|
|
val runDistributable = project.tasks.composeTask<AbstractRunDistributableTask>( |
|
taskName("runDistributable", app), |
|
args = listOf(createDistributable) |
|
) |
|
|
|
val run = project.tasks.composeTask<JavaExec>(taskName("run", app)) { |
|
configureRunTask(app) |
|
} |
|
} |
|
} |
|
|
|
internal fun AbstractJPackageTask.configurePackagingTask( |
|
app: Application, |
|
createAppImage: TaskProvider<AbstractJPackageTask>? = null, |
|
createRuntimeImage: TaskProvider<AbstractJLinkTask>? = null |
|
) { |
|
enabled = targetFormat.isCompatibleWithCurrentOS |
|
|
|
createAppImage?.let { createAppImage -> |
|
dependsOn(createAppImage) |
|
appImage.set(createAppImage.flatMap { it.destinationDir }) |
|
} |
|
|
|
createRuntimeImage?.let { createRuntimeImage -> |
|
dependsOn(createRuntimeImage) |
|
runtimeImage.set(createRuntimeImage.flatMap { it.destinationDir }) |
|
} |
|
|
|
configurePlatformSettings(app) |
|
|
|
app.nativeDistributions.let { executables -> |
|
packageName.set(app._packageNameProvider(project)) |
|
packageDescription.set(provider { executables.description }) |
|
packageCopyright.set(provider { executables.copyright }) |
|
packageVendor.set(provider { executables.vendor }) |
|
packageVersion.set(packageVersionFor(project, app, targetFormat)) |
|
} |
|
|
|
destinationDir.set(app.nativeDistributions.outputBaseDir.map { it.dir("${app.name}/${targetFormat.outputDirName}") }) |
|
javaHome.set(provider { app.javaHomeOrDefault() }) |
|
|
|
launcherMainJar.set(app.mainJar.orNull) |
|
app._fromFiles.forEach { files.from(it) } |
|
dependsOn(*app._dependenciesTaskNames.toTypedArray()) |
|
|
|
app._configurationSource?.let { configSource -> |
|
dependsOn(configSource.jarTaskName) |
|
files.from(configSource.runtimeClasspath(project)) |
|
launcherMainJar.set(app.mainJar.orElse(configSource.jarTask(project).flatMap { it.archiveFile })) |
|
} |
|
|
|
launcherMainClass.set(provider { app.mainClass }) |
|
launcherJvmArgs.set(provider { app.jvmArgs }) |
|
launcherArgs.set(provider { app.args }) |
|
} |
|
|
|
internal fun AbstractUploadAppForNotarizationTask.configureUploadForNotarizationTask( |
|
app: Application, |
|
packageFormat: TaskProvider<AbstractJPackageTask>, |
|
requestsDir: Provider<Directory> |
|
) { |
|
dependsOn(packageFormat) |
|
inputDir.set(packageFormat.flatMap { it.destinationDir }) |
|
this.requestsDir.set(requestsDir) |
|
configureCommonNotarizationSettings(app) |
|
} |
|
|
|
internal fun AbstractCheckNotarizationStatusTask.configureCheckNotarizationStatusTask( |
|
app: Application, |
|
requestsDir: Provider<Directory> |
|
) { |
|
requestDir.set(requestsDir) |
|
configureCommonNotarizationSettings(app) |
|
} |
|
|
|
internal fun AbstractNotarizationTask.configureCommonNotarizationSettings( |
|
app: Application |
|
) { |
|
nonValidatedBundleID.set(app.nativeDistributions.macOS.bundleID) |
|
nonValidatedNotarizationSettings = app.nativeDistributions.macOS.notarization |
|
} |
|
|
|
internal fun AbstractJPackageTask.configurePlatformSettings(app: Application) { |
|
when (currentOS) { |
|
OS.Linux -> { |
|
app.nativeDistributions.linux.also { linux -> |
|
linuxShortcut.set(provider { linux.shortcut }) |
|
linuxAppCategory.set(provider { linux.appCategory }) |
|
linuxAppRelease.set(provider { linux.appRelease }) |
|
linuxDebMaintainer.set(provider { linux.debMaintainer }) |
|
linuxMenuGroup.set(provider { linux.menuGroup }) |
|
linuxPackageName.set(provider { linux.packageName }) |
|
linuxRpmLicenseType.set(provider { linux.rpmLicenseType }) |
|
iconFile.set(linux.iconFile.orElse(DefaultIcons.forLinux(project))) |
|
installationPath.set(linux.installationPath) |
|
} |
|
} |
|
OS.Windows -> { |
|
app.nativeDistributions.windows.also { win -> |
|
winConsole.set(provider { win.console }) |
|
winDirChooser.set(provider { win.dirChooser }) |
|
winPerUserInstall.set(provider { win.perUserInstall }) |
|
winShortcut.set(provider { win.shortcut }) |
|
winMenu.set(provider { win.menu }) |
|
winMenuGroup.set(provider { win.menuGroup }) |
|
winUpgradeUuid.set(provider { win.upgradeUuid }) |
|
iconFile.set(win.iconFile.orElse(DefaultIcons.forWindows(project))) |
|
installationPath.set(win.installationPath) |
|
} |
|
} |
|
OS.MacOS -> { |
|
app.nativeDistributions.macOS.also { mac -> |
|
macPackageName.set(provider { mac.packageName }) |
|
macDockName.set( |
|
if (mac.setDockNameSameAsPackageName) |
|
provider { mac.dockName }.orElse(macPackageName).orElse(packageName) |
|
else |
|
provider { mac.dockName } |
|
) |
|
nonValidatedMacBundleID.set(provider { mac.bundleID }) |
|
nonValidatedMacSigningSettings = app.nativeDistributions.macOS.signing |
|
iconFile.set(mac.iconFile.orElse(DefaultIcons.forMac(project))) |
|
installationPath.set(mac.installationPath) |
|
} |
|
} |
|
} |
|
} |
|
|
|
private fun JavaExec.configureRunTask(app: Application) { |
|
mainClass.set(provider { app.mainClass }) |
|
executable(javaExecutable(app.javaHomeOrDefault())) |
|
jvmArgs = app.jvmArgs |
|
args = app.args |
|
|
|
val cp = project.objects.fileCollection() |
|
// adding a null value will cause future invocations of `from` to throw an NPE |
|
app.mainJar.orNull?.let { cp.from(it) } |
|
cp.from(app._fromFiles) |
|
dependsOn(*app._dependenciesTaskNames.toTypedArray()) |
|
|
|
app._configurationSource?.let { configSource -> |
|
dependsOn(configSource.jarTaskName) |
|
cp.from(configSource.runtimeClasspath(project)) |
|
} |
|
|
|
classpath = cp |
|
} |
|
|
|
private fun Jar.configurePackageUberJarForCurrentOS(app: Application) { |
|
fun flattenJars(files: FileCollection): FileCollection = |
|
project.files({ |
|
files.map { if (it.isZipOrJar()) project.zipTree(it) else it } |
|
}) |
|
|
|
// adding a null value will cause future invocations of `from` to throw an NPE |
|
app.mainJar.orNull?.let { from(it) } |
|
from(flattenJars(app._fromFiles)) |
|
dependsOn(*app._dependenciesTaskNames.toTypedArray()) |
|
|
|
app._configurationSource?.let { configSource -> |
|
dependsOn(configSource.jarTaskName) |
|
from(flattenJars(configSource.runtimeClasspath(project))) |
|
} |
|
|
|
app.mainClass?.let { manifest.attributes["Main-Class"] = it } |
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE |
|
archiveAppendix.set(currentTarget.id) |
|
archiveBaseName.set(app._packageNameProvider(project)) |
|
archiveVersion.set(packageVersionFor(project, app, TargetFormat.AppImage)) |
|
destinationDirectory.set(project.layout.buildDirectory.dir("compose/jars")) |
|
|
|
doLast { |
|
logger.lifecycle("The jar is written to ${archiveFile.ioFile.canonicalPath}") |
|
} |
|
} |
|
|
|
private fun File.isZipOrJar() = |
|
name.endsWith(".jar", ignoreCase = true) |
|
|| name.endsWith(".zip", ignoreCase = true) |
|
|
|
private fun Application.javaHomeOrDefault(): String = |
|
javaHome ?: System.getProperty("java.home") |
|
|
|
private inline fun <reified T : Task> TaskContainer.composeTask( |
|
name: String, |
|
args: List<Any> = emptyList(), |
|
noinline configureFn: T.() -> Unit = {} |
|
) = register(name, T::class.java, *args.toTypedArray()).apply { |
|
configure { |
|
it.group = "compose desktop" |
|
it.configureFn() |
|
} |
|
} |
|
|
|
internal fun Application._packageNameProvider(project: Project): Provider<String> = |
|
project.provider { nativeDistributions.packageName ?: project.name } |
|
|
|
@OptIn(ExperimentalStdlibApi::class) |
|
private fun taskName(action: String, app: Application, suffix: String? = null): String = |
|
listOf( |
|
action, |
|
app.name.takeIf { it != "main" }?.capitalize(Locale.ROOT), |
|
suffix?.capitalize(Locale.ROOT) |
|
).filterNotNull().joinToString("")
|
|
|