From 3c1ae47b26a64e469440d2e1d31dcaf2213df3d1 Mon Sep 17 00:00:00 2001 From: Carlos Ballesteros Velasco Date: Fri, 13 Oct 2023 06:42:42 +0200 Subject: [PATCH] Supports packing macos app with a bundled ARM+x64 JRE (#1946) --- .../korlibs/korge/gradle/KorgeGradlePlugin.kt | 21 +++-- .../korge/gradle/targets/SourceSets.kt | 31 +++++++ .../gradle/targets/apple/InfoPlistBuilder.kt | 4 +- .../targets/desktop/DesktopJreBundler.kt | 80 +++++++++++++++++++ .../korlibs/korge/gradle/targets/ios/Ios.kt | 23 +++++- .../korlibs/korge/gradle/targets/jvm/Jvm.kt | 9 +++ .../kotlin/korlibs/root/RootKorlibsPlugin.kt | 18 ++--- .../com/soywiz/kproject/KProjectPlugin.kt | 5 +- 8 files changed, 164 insertions(+), 27 deletions(-) create mode 100644 buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/SourceSets.kt create mode 100644 buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/desktop/DesktopJreBundler.kt diff --git a/buildSrc/src/main/kotlin/korlibs/korge/gradle/KorgeGradlePlugin.kt b/buildSrc/src/main/kotlin/korlibs/korge/gradle/KorgeGradlePlugin.kt index 13a3dcbc0f..1275ec3d90 100644 --- a/buildSrc/src/main/kotlin/korlibs/korge/gradle/KorgeGradlePlugin.kt +++ b/buildSrc/src/main/kotlin/korlibs/korge/gradle/KorgeGradlePlugin.kt @@ -204,18 +204,17 @@ val Project.ext get() = extensions.getByType(ExtraPropertiesExtension::class.jav fun Project.korge(callback: KorgeExtension.() -> Unit) = korge.apply(callback).also { it.finish() } val Project.kotlin: KotlinMultiplatformExtension get() = this.extensions.getByType(KotlinMultiplatformExtension::class.java) -val Project.korge: KorgeExtension - get() { - val extension = project.extensions.findByName("korge") as? KorgeExtension? - return if (extension == null) { - //val newExtension = KorgeExtension(this, objectFactory = ) - val newExtension = project.extensions.create("korge", KorgeExtension::class.java) - //project.extensions.add("korge", newExtension) - newExtension - } else { - extension - } +val Project.korge: KorgeExtension get() = extensionGetOrCreate("korge") + +inline fun Project.extensionGetOrCreate(name: String): T { + val extension = project.extensions.findByName(name) as? T? + return if (extension == null) { + val newExtension = project.extensions.create(name, T::class.java) + newExtension + } else { + extension } +} open class JsWebCopy() : Copy() { @OutputDirectory diff --git a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/SourceSets.kt b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/SourceSets.kt new file mode 100644 index 0000000000..fb6fa95d0e --- /dev/null +++ b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/SourceSets.kt @@ -0,0 +1,31 @@ +package korlibs.korge.gradle.targets + +import korlibs.korge.gradle.* +import org.gradle.api.* + +val Project.exKotlinSourceSetContainer: ExKotlinSourceSetContainer get() = extensionGetOrCreate("exKotlinSourceSetContainer") + +open class ExKotlinSourceSetContainer(val project: Project) { + val kotlin = project.kotlin + val sourceSets = kotlin.sourceSets + + val common by lazy { sourceSets.createPairSourceSet("common") } + val nonJs by lazy { sourceSets.createPairSourceSet("nonJs", common) } + val concurrent by lazy { sourceSets.createPairSourceSet("concurrent", common) } + + // JS + val js by lazy { sourceSets.createPairSourceSet("js", common) } + + // JVM + val jvm by lazy { sourceSets.createPairSourceSet("jvm", concurrent, nonJs) } + + // Native + val native by lazy { sourceSets.createPairSourceSet("native", concurrent, nonJs) } + val posix by lazy { sourceSets.createPairSourceSet("posix", native) } + val darwin by lazy { sourceSets.createPairSourceSet("darwin", posix) } + val darwinMobile by lazy { sourceSets.createPairSourceSet("darwinMobile", darwin) } + val iosTvos by lazy { sourceSets.createPairSourceSet("iosTvos", darwinMobile/*, iosTvosMacos*/) } + val tvos by lazy { sourceSets.createPairSourceSet("tvos", iosTvos) } + val ios by lazy { sourceSets.createPairSourceSet("ios", iosTvos/*, iosMacos*/) } +} + diff --git a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/apple/InfoPlistBuilder.kt b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/apple/InfoPlistBuilder.kt index a5b419168f..ac372d2f55 100644 --- a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/apple/InfoPlistBuilder.kt +++ b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/apple/InfoPlistBuilder.kt @@ -1,7 +1,6 @@ package korlibs.korge.gradle.targets.apple -import korlibs.korge.gradle.GameCategory -import korlibs.korge.gradle.KorgeExtension +import korlibs.korge.gradle.* object InfoPlistBuilder { fun GameCategory?.toUTI(): String { @@ -62,6 +61,7 @@ object InfoPlistBuilder { appendLine(" CFBundleSignature????") appendLine(" LSMinimumSystemVersion10.9.0") appendLine(" NSHighResolutionCapable") + appendLine(" LSRequiresNativeExecution") } appendLine("""""") } diff --git a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/desktop/DesktopJreBundler.kt b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/desktop/DesktopJreBundler.kt new file mode 100644 index 0000000000..10c2d82d33 --- /dev/null +++ b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/desktop/DesktopJreBundler.kt @@ -0,0 +1,80 @@ +package korlibs.korge.gradle.targets.desktop + +import korlibs.korge.gradle.* +import korlibs.korge.gradle.targets.* +import korlibs.korge.gradle.targets.apple.* +import korlibs.korge.gradle.util.* +import org.gradle.api.* +import java.io.* +import java.net.* +import java.security.* + +// https://stackoverflow.com/questions/13017121/unpacking-tar-gz-into-root-dir-with-gradle +// https://github.com/korlibs/universal-jre/ +object DesktopJreBundler { + // https://github.com/adoptium/temurin21-binaries/releases/tag/jdk-21%2B35 + + data class UrlRef(val url: String, val sha256: String) + + val JRE_MACOS_LAUNCHER = UrlRef( + "https://github.com/korlibs/universal-jre/releases/download/0.0.1/app", + sha256 = "4123b08e24678885781b04125675aa2f7d2af87583a753d16737ad154934bf0b" + ) + + val JRE_MACOS_UNIVERSAL = UrlRef( + "https://github.com/korlibs/universal-jre/releases/download/0.0.1/macos-universal-jdk-21+35-jre.tar.gz", + sha256 = "6d2d0a2e35c649fc731f5d3f38d7d7828f7fad4b9b2ea55d4d05f0fd26cf93ca" + ) + + val JRE_DIR = File(korgeCacheDir, "jre") + val UNIVERSAL = File(JRE_DIR, "universal") + val UNIVERSAL_JRE = File(UNIVERSAL, "jdk-21+35-jre/Contents/jre") + + fun cachedFile(urlRef: UrlRef): File { + val downloadUrl = URL(urlRef.url) + val localFile = File(JRE_DIR, File(downloadUrl.file).name).ensureParents() + if (!localFile.isFile) { + println("Downloading $downloadUrl...") + val bytes = downloadUrl.readBytes() + val actualSha256 = MessageDigest.getInstance("SHA-256").digest(bytes).hex + val expectedSha256 = urlRef.sha256 + if (actualSha256 != expectedSha256) { + error("URL: ${urlRef.url} expected to have $expectedSha256 but was $actualSha256") + } + localFile.writeBytes(bytes) + } + return localFile + } + + fun createMacosApp(project: Project, fatJar: File) { + if (!UNIVERSAL_JRE.isDirectory) { + project.copy { + it.from(project.tarTree(cachedFile(JRE_MACOS_UNIVERSAL))) + it.into(UNIVERSAL) + } + } + + val gameApp = File(project.buildDir, "platforms/jvm-macos/game.app/Contents").ensureParents() + project.copy { + it.from(UNIVERSAL_JRE) + it.into(File(gameApp, "MacOS/jre")) + } + + val korge = project.korge + File(gameApp, "Resources/${korge.exeBaseName}.icns").ensureParents().writeBytes(IcnsBuilder.build(korge.getIconBytes())) + File(gameApp, "Info.plist").writeText(InfoPlistBuilder.build(korge)) + val exec = File(gameApp, "MacOS/${File(korge.exeBaseName).name}").ensureParents() + exec.writeBytes(cachedFile(JRE_MACOS_LAUNCHER).readBytes()) + exec.setExecutable(true) + project.copy { + it.from(fatJar) + it.into(File(gameApp, "MacOS")) + it.rename { "app.jar" } + it.filePermissions { + it.user.execute = true + it.group.execute = true + it.other.execute = true + } + } + } +} diff --git a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/ios/Ios.kt b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/ios/Ios.kt index d7dc732fe6..4bf7dc376c 100644 --- a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/ios/Ios.kt +++ b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/ios/Ios.kt @@ -16,6 +16,19 @@ import java.io.* fun Project.configureNativeIos(projectType: ProjectType) { configureNativeIosTvos(projectType, "ios") configureNativeIosTvos(projectType, "tvos") + + val exKotlinSourceSetContainer = this.project.exKotlinSourceSetContainer + this.project.kotlin.apply { + sourceSets.apply { + for (target in listOf(iosArm64(), iosX64(), iosSimulatorArm64(), tvosArm64(), tvosX64(), tvosSimulatorArm64())) { + val native = createPairSourceSet(target.name) + when { + target.isIos -> native.dependsOn(exKotlinSourceSetContainer.ios) + target.isTvos -> native.dependsOn(exKotlinSourceSetContainer.tvos) + } + } + } + } } val Project.xcframework by projectExtension() { @@ -191,8 +204,14 @@ fun Project.configureNativeIosTvosRun(targetName: String) { val simulatorSuffix = if (simulator) "Simulator" else "Device" //val arch = if (simulator) "X64" else "Arm64" //val arch2 = if (simulator) "x64" else "armv8" - val arch = if (simulator) "X64" else "Arm64" - val arch2 = if (simulator) "x86_64" else "arm64" + val arch = when { + simulator -> if (isArm) "SimulatorArm64" else "X64" + else -> "Arm64" + } + val arch2 = when { + simulator -> if (isArm) "arm64" else "x86_64" + else -> "arm64" + } val sdkName = if (simulator) "iphonesimulator" else "iphoneos" tasks.createThis("${targetName}Build$simulatorSuffix$debugSuffix") { //task.dependsOn(prepareKotlinNativeIosProject, "linkMain${debugSuffix}FrameworkIos$arch") diff --git a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/jvm/Jvm.kt b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/jvm/Jvm.kt index 77b0cf5a1d..6b7b01301a 100644 --- a/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/jvm/Jvm.kt +++ b/buildSrc/src/main/kotlin/korlibs/korge/gradle/targets/jvm/Jvm.kt @@ -5,6 +5,7 @@ import korlibs.korge.gradle.* import korlibs.korge.gradle.gkotlin import korlibs.korge.gradle.kotlin import korlibs.korge.gradle.targets.* +import korlibs.korge.gradle.targets.desktop.* import korlibs.korge.gradle.util.* import korlibs.root.* import org.gradle.api.* @@ -208,6 +209,14 @@ private fun Project.addProguard() { } } + project.tasks.createThis("packageJvmMacosApp") { + dependsOn(packageJvmFatJar) + group = GROUP_KORGE_PACKAGE + doLast { + DesktopJreBundler.createMacosApp(project, packageJvmFatJar.outputs.files.first()) + } + } + project.tasks.createThis("packageJvmFatJarProguard") { dependsOn(packageJvmFatJar) group = GROUP_KORGE_PACKAGE diff --git a/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt b/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt index 42f1b7b5b3..7c19a611c1 100644 --- a/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt +++ b/buildSrc/src/main/kotlin/korlibs/root/RootKorlibsPlugin.kt @@ -428,20 +428,20 @@ object RootKorlibsPlugin { } if (supportKotlinNative) { + //val iosTvosMacos by lazy { createPairSourceSet("iosTvosMacos", darwin) } + //val iosMacos by lazy { createPairSourceSet("iosMacos", iosTvosMacos) } + + //val linux by lazy { createPairSourceSet("linux", posix) } + //val macos by lazy { createPairSourceSet("macos", iosMacos) } + //val mingw by lazy { createPairSourceSet("mingw", native) } + val native by lazy { createPairSourceSet("native", concurrent) } val posix by lazy { createPairSourceSet("posix", native) } val darwin by lazy { createPairSourceSet("darwin", posix) } - val iosTvosMacos by lazy { createPairSourceSet("iosTvosMacos", darwin) } - val iosMacos by lazy { createPairSourceSet("iosMacos", iosTvosMacos) } - - val linux by lazy { createPairSourceSet("linux", posix) } - val macos by lazy { createPairSourceSet("macos", iosMacos) } - val mingw by lazy { createPairSourceSet("mingw", native) } - val darwinMobile by lazy { createPairSourceSet("darwinMobile", darwin) } - val iosTvos by lazy { createPairSourceSet("iosTvos", darwinMobile, iosTvosMacos) } + val iosTvos by lazy { createPairSourceSet("iosTvos", darwinMobile/*, iosTvosMacos*/) } val tvos by lazy { createPairSourceSet("tvos", iosTvos) } - val ios by lazy { createPairSourceSet("ios", iosTvos, iosMacos) } + val ios by lazy { createPairSourceSet("ios", iosTvos/*, iosMacos*/) } for (target in mobileTargets(project)) { val native = createPairSourceSet(target.name) diff --git a/korge-gradle-plugin/src/main/kotlin/com/soywiz/kproject/KProjectPlugin.kt b/korge-gradle-plugin/src/main/kotlin/com/soywiz/kproject/KProjectPlugin.kt index 28f41c2c8f..b838736202 100644 --- a/korge-gradle-plugin/src/main/kotlin/com/soywiz/kproject/KProjectPlugin.kt +++ b/korge-gradle-plugin/src/main/kotlin/com/soywiz/kproject/KProjectPlugin.kt @@ -4,13 +4,12 @@ import com.android.build.gradle.* import com.soywiz.kproject.internal.* import com.soywiz.kproject.model.* import com.soywiz.kproject.util.* -import korlibs.korge.gradle.targets.android.* import org.gradle.api.* import org.gradle.api.plugins.* import org.gradle.api.tasks.testing.logging.* import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* -import java.io.File +import java.io.* @Suppress("unused") class KProjectPlugin : Plugin { @@ -140,7 +139,7 @@ class KProjectPlugin : Plugin { val native = createPair("native").dependsOn(concurrent) val posix = createPair("posix").dependsOn(native) val apple = createPair("apple").dependsOn(posix) - val macos = createPair("macos").dependsOn(apple) + //val macos = createPair("macos").dependsOn(apple) //if (hasTarget(KProjectTarget.DESKTOP)) { // createPair("macosX64").dependsOn(macos) // createPair("macosArm64").dependsOn(macos)