From 7ee3c70c96426fb0df046361e0fb143a32bb498a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 19 Oct 2026 18:50:27 +0600 Subject: [PATCH] fix(macos): add GraalVM runtime dependency and initialize GraalVm in main function --- composeApp/build.gradle.kts | 8 ++++++++ composeApp/src/jvmMain/kotlin/org/spotube/spotube/main.kt | 4 ++++ gradle/libs.versions.toml | 1 + 3 files changed, 13 insertions(+) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 1852a641..25f7cc44 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -302,6 +302,7 @@ kotlin { implementation(libs.jna) implementation(libs.nucleus.core.runtime) + implementation(libs.nucleus.graalvm.runtime) implementation(libs.nucleus.nucleus.application) implementation(libs.nucleus.decorated.window.tao) implementation(libs.compose.native.tray) @@ -401,6 +402,9 @@ nucleus.application { nativeDistributions { packageName = bundleId + // User-facing name (.app bundle, DMG productName, Launchpad/Dock); packageName above is the reverse-DNS id. + // The GraalVM DMG task reads only appName ?: packageName, not macOS.packageName + appName = if (isNightly) "Spotube Nightly" else "Spotube" packageVersion = version licenseFile = project.file("../LICENSE") @@ -435,6 +439,8 @@ nucleus.application { macOS { iconFile.set(project.file("$desktopIconDir/icon.icns")) + bundleID = if (isNightly) "$bundleId.nightly" else bundleId + dockName = appName notarization { } @@ -527,6 +533,8 @@ tasks.withType().matching { it.name == "copyGraalvmAwtSoLibs" }.configureE // Issue with compose plugin // https://github.com/JetBrains/compose-hot-reload/blob/master/docs/Known_limitations.md#property-composeapplicationresourcesdir-is-null-when-running-hot-reload-tasks tasks.withType().configureEach { + // Populates the resources dir below with the VLC natives (appResourcesRootDir); hot run doesn't depend on it by default + dependsOn("prepareAppResources") jvmArgs( "--enable-native-access=ALL-UNNAMED" ) diff --git a/composeApp/src/jvmMain/kotlin/org/spotube/spotube/main.kt b/composeApp/src/jvmMain/kotlin/org/spotube/spotube/main.kt index e754c8d3..a4d02a13 100644 --- a/composeApp/src/jvmMain/kotlin/org/spotube/spotube/main.kt +++ b/composeApp/src/jvmMain/kotlin/org/spotube/spotube/main.kt @@ -38,6 +38,7 @@ import spotube.composeapp.generated.resources.spotube_logo import dev.nucleusframework.application.DecoratedWindow import dev.nucleusframework.application.NucleusBackend import dev.nucleusframework.application.nucleusApplication +import dev.nucleusframework.graalvm.GraalVmInitializer import dev.nucleusframework.window.tao.TaoScreenGeometry import io.github.vinceglb.filekit.FileKit import kotlinx.coroutines.CoroutineScope @@ -132,6 +133,9 @@ private fun restoreWindowState(saved: PersistedWindowState?): PersistedWindowSta @OptIn(ExperimentalComposeUiApi::class, FlowPreview::class) fun main() { + // Native image only (no-op on the JVM): sets java.home, java.library.path and compose.application.resources.dir + // to the executable's dir, which the VLC loader needs to find the bundled libvlc + GraalVmInitializer.initialize() FileKit.init(appId = "org.spotube.spotube") initKoin() NewPipeDownloader.init(KoinPathsProvider.paths) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c441d82a..41f2c6a3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -129,6 +129,7 @@ murmurhash = { module = "com.goncalossilva:murmurhash", version.ref = "murmurhas newpipe-extractor-kmp = { module = "io.github.yushosei:newpipe-extractor-kmp", version.ref = "newpipeExtractorKmp" } newpipeextractor = { module = "com.github.teamnewpipe:NewPipeExtractor", version.ref = "newpipeextractor" } nucleus-core-runtime = { module = "dev.nucleusframework:nucleus.core-runtime", version.ref = "nucleusNucleusApplication" } +nucleus-graalvm-runtime = { module = "dev.nucleusframework:nucleus.graalvm-runtime", version.ref = "nucleusNucleusApplication" } nucleus-decorated-window-tao = { module = "dev.nucleusframework:nucleus.decorated-window-tao", version.ref = "nucleusNucleusApplication" } nucleus-media-control = { module = "dev.nucleusframework:nucleus.media-control", version.ref = "nucleusNucleusApplication" } nucleus-nucleus-application = { module = "dev.nucleusframework:nucleus.nucleus-application", version.ref = "nucleusNucleusApplication" }