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
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("")
|
|
|