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.
 
 
 
 

225 lines
9.4 KiB

package org.jetbrains.compose.desktop.application
import org.gradle.api.*
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import org.jetbrains.compose.ComposeExtension
import org.jetbrains.compose.desktop.DesktopBasePlugin
import org.jetbrains.compose.desktop.DesktopExtension
import org.jetbrains.compose.desktop.application.dsl.Application
import org.jetbrains.compose.desktop.application.dsl.ConfigurationSource
import org.jetbrains.compose.desktop.application.internal.OS
import org.jetbrains.compose.desktop.application.internal.currentOS
import org.jetbrains.compose.desktop.application.internal.provider
import org.jetbrains.compose.desktop.application.tasks.AbstractJPackageTask
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import java.io.File
import java.util.*
private const val PLUGIN_ID = "org.jetbrains.compose.desktop.application"
// todo: fix windows
// todo: multiple launchers
// todo: file associations
// todo: icon
// todo: use workers
@Suppress("unused") // Gradle plugin entry point
open class ApplicationPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.plugins.apply(DesktopBasePlugin::class.java)
val composeExt = project.extensions.getByType(ComposeExtension::class.java)
val desktopExt = composeExt.extensions.getByType(DesktopExtension::class.java)
val mainApplication = project.objects.newInstance(Application::class.java, "main")
desktopExt.extensions.add("application", mainApplication)
project.plugins.withId("org.jetbrains.kotlin.jvm") {
val mainSourceSet = project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets.getByName("main")
mainApplication.from(mainSourceSet)
}
project.plugins.withId("org.jetbrains.kotlin.multiplatform") {
project.configureFromMppPlugin(mainApplication)
}
project.afterEvaluate {
project.configurePackagingTasks(listOf(mainApplication))
}
}
}
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 '$PLUGIN_ID' 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) {
configureRunTask(app)
configurePackagingTasks(app)
}
}
internal fun Project.configurePackagingTasks(app: Application): TaskProvider<DefaultTask> {
val packageFormats = app.nativeExecutables.targetFormats.map { targetFormat ->
tasks.composeTask<AbstractJPackageTask>(
taskName("package", app, targetFormat.name),
args = listOf(targetFormat)
) {
configurePackagingTask(app)
}
}
return tasks.composeTask<DefaultTask>(taskName("package", app)) {
dependsOn(packageFormats)
}
}
internal fun AbstractJPackageTask.configurePackagingTask(app: Application) {
enabled = (currentOS == targetOS)
val targetPlatformSettings = when (targetOS) {
OS.Linux -> {
app.nativeExecutables.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 })
}
}
OS.Windows -> {
app.nativeExecutables.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 })
}
}
OS.MacOS -> {
app.nativeExecutables.macOS.also { mac ->
macPackageName.set(provider { mac.packageName })
macPackageIdentifier.set(provider { mac.packageIdentifier })
macSign.set(provider { mac.signing.sign })
macSigningKeyUserName.set(provider { mac.signing.keyUserName })
macSigningKeychain.set(project.layout.file(provider { mac.signing.keychain }))
macBundleSigningPrefix.set(provider { mac.signing.bundlePrefix })
}
}
}
app.nativeExecutables.let { executables ->
packageName.set(provider { executables.packageName ?: project.name })
packageDescription.set(provider { executables.description })
packageCopyright.set(provider { executables.copyright })
packageVendor.set(provider { executables.vendor })
packageVersion.set(provider {
targetPlatformSettings.version
?: executables.version
?: project.version.toString().takeIf { it != "unspecified" }
})
}
destinationDir.set(app.nativeExecutables.outputBaseDir.map { it.dir("${app.name}/${targetFormat.id}") })
javaHome.set(provider { app.javaHomeOrDefault() })
launcherMainJar.set(app.mainJar.orNull)
app._fromFiles.forEach { files.from(it) }
dependsOn(*app._dependenciesTaskNames.toTypedArray())
when (val configSource = app._configurationSource) {
is ConfigurationSource.None -> {}
is ConfigurationSource.GradleSourceSet -> {
val sourceSet = configSource.sourceSet
dependsOn(sourceSet.jarTaskName)
launcherMainJar.set(app.mainJar.orElse(jarFromJarTaskByName(sourceSet.jarTaskName)))
files.from(sourceSet.runtimeClasspath)
}
is ConfigurationSource.KotlinMppTarget -> {
val target = configSource.target
dependsOn(target.artifactsTaskName)
launcherMainJar.set(app.mainJar.orElse(jarFromJarTaskByName(target.artifactsTaskName)))
files.from(project.configurations.named(target.runtimeElementsConfigurationName))
}
}
modules.set(provider { app.nativeExecutables.modules })
launcherMainClass.set(provider { app.mainClass })
launcherJvmArgs.set(provider { app.jvmArgs })
launcherArgs.set(provider { app.args })
}
private fun AbstractJPackageTask.jarFromJarTaskByName(jarTaskName: String) =
project.tasks.named(jarTaskName).map { (it as Jar).archiveFile.get() }
private fun Project.configureRunTask(app: Application) {
project.tasks.composeTask<JavaExec>(taskName("run", app)) {
mainClass.set(provider { app.mainClass })
executable = javaExecutable(app.javaHomeOrDefault())
jvmArgs = app.jvmArgs
args = app.args
val cp = objects.fileCollection()
cp.from(app.mainJar.orNull)
cp.from(app._fromFiles)
dependsOn(*app._dependenciesTaskNames.toTypedArray())
when (val configSource = app._configurationSource) {
is ConfigurationSource.None -> {}
is ConfigurationSource.GradleSourceSet -> {
val sourceSet = configSource.sourceSet
dependsOn(sourceSet.jarTaskName)
cp.from(sourceSet.runtimeClasspath)
}
is ConfigurationSource.KotlinMppTarget -> {
val target = configSource.target
dependsOn(target.artifactsTaskName)
cp.from(configurations.named(target.runtimeElementsConfigurationName))
}
}
classpath = cp
}
}
private fun Application.javaHomeOrDefault(): String =
javaHome ?: System.getProperty("java.home")
private fun javaExecutable(javaHome: String): String {
val executableName = if (currentOS == OS.Windows) "java.exe" else "java"
return File(javaHome).resolve("bin/$executableName").absolutePath
}
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-application"
it.configureFn()
}
}
@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("")