mirror of
https://github.com/Darkrock-Studios/hammer-editor.git
synced 2026-08-04 23:29:46 +00:00
Port :desktop packaging to Nucleus framework
Replaces Compose Desktop's bespoke nativeDistributions + the hand-rolled packageMsix, snapcraft, flatpak-builder, and appimagetool tasks with the Nucleus plugin's unified DSL. Nucleus is a fork of compose-desktop- application that adds first-class AppX/Portable/Snap/Flatpak/AppImage targets, so org.jetbrains.compose can no longer be applied alongside it (task name collisions); compose.desktop.currentOs and compose.components.uiToolingPreview are now resolved via explicit Maven coordinates instead. The hand-edited msix/AppxManifest.xml, snap/snapcraft.yaml, and flatpak/*.yaml are now generated from the DSL; the source files remain in-tree for now and will be deleted once CI validates the nucleus output across all targets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2ba59f7910
commit
4af31163b2
3 changed files with 122 additions and 195 deletions
|
|
@ -69,7 +69,6 @@ plugins {
|
|||
alias(libs.plugins.aboutlibraries.plugin.android) apply false
|
||||
alias(libs.plugins.jetbrains.kover)
|
||||
alias(libs.plugins.kotlinx.atomicfu)
|
||||
alias(libs.plugins.flatpak.gradle.generator) apply false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -87,7 +86,6 @@ kover {
|
|||
}
|
||||
|
||||
registerPublishTasks()
|
||||
registerLinuxDistributionTasks(libs.versions.app.get())
|
||||
|
||||
val releasePreFlightChecks = tasks.register("releasePreFlightChecks") {
|
||||
doLast {
|
||||
|
|
@ -121,15 +119,8 @@ val releasePreFlightChecks = tasks.register("releasePreFlightChecks") {
|
|||
}
|
||||
}
|
||||
|
||||
// Ensure flatpak generator runs after pre-flight checks
|
||||
project(":desktop").tasks.configureEach {
|
||||
if (name == "flatpakGradleGenerator") {
|
||||
mustRunAfter(releasePreFlightChecks)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("prepareForRelease") {
|
||||
dependsOn(releasePreFlightChecks, ":desktop:flatpakGradleGenerator")
|
||||
dependsOn(releasePreFlightChecks)
|
||||
doLast {
|
||||
val releaseInfo =
|
||||
configureRelease(libs.versions.app.get()) ?: error("Failed to configure new release")
|
||||
|
|
@ -162,19 +153,6 @@ tasks.register("prepareForRelease") {
|
|||
val globalChangelogFile = File("${project.rootDir}/CHANGELOG.md")
|
||||
writeChangelogMarkdown(releaseInfo, globalChangelogFile)
|
||||
|
||||
// Update snapcraft.yaml with new version and JVM version
|
||||
val snapcraftPath = "snap/snapcraft.yaml".replace("/", File.separator)
|
||||
val snapcraftFile = project.rootDir.resolve(snapcraftPath)
|
||||
val jvmVersion = libs.versions.jvm.get()
|
||||
updateSnapcraftYaml(releaseInfo.semVar, jvmVersion, snapcraftFile)
|
||||
|
||||
// Update Flatpak manifest and metainfo with new version and JVM version
|
||||
val flatpakManifestPath = "flatpak/studio.darkrock.hammer.yaml".replace("/", File.separator)
|
||||
val flatpakManifestFile = project.rootDir.resolve(flatpakManifestPath)
|
||||
val flatpakMetainfoPath = "flatpak/studio.darkrock.hammer.metainfo.xml".replace("/", File.separator)
|
||||
val flatpakMetainfoFile = project.rootDir.resolve(flatpakMetainfoPath)
|
||||
updateFlatpakFiles(releaseInfo.semVar, jvmVersion, flatpakManifestFile, flatpakMetainfoFile, releaseInfo.changeLog)
|
||||
|
||||
fun git(vararg args: String) {
|
||||
val cmd = listOf("git") + args.toList()
|
||||
println("> ${cmd.joinToString(" ")}")
|
||||
|
|
@ -193,12 +171,6 @@ tasks.register("prepareForRelease") {
|
|||
git("add", changeLogFile.absolutePath)
|
||||
git("add", versionsFile.absolutePath)
|
||||
git("add", globalChangelogFile.absolutePath)
|
||||
git("add", snapcraftFile.absolutePath)
|
||||
git("add", flatpakManifestFile.absolutePath)
|
||||
git("add", flatpakMetainfoFile.absolutePath)
|
||||
val flatpakSourcesPath = "flatpak/flatpak-sources.json".replace("/", File.separator)
|
||||
val flatpakSourcesFile = project.rootDir.resolve(flatpakSourcesPath)
|
||||
git("add", flatpakSourcesFile.absolutePath)
|
||||
git("commit", "-m", "Prepared for release: v${releaseInfo.semVar}")
|
||||
|
||||
// Switch to release and reset to origin/release HEAD
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import com.darkrockstudios.build.registerLinuxDistributionTasks
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import io.github.kdroidfilter.nucleus.desktop.application.dsl.AppImageCategory
|
||||
import io.github.kdroidfilter.nucleus.desktop.application.dsl.SnapCompression
|
||||
import io.github.kdroidfilter.nucleus.desktop.application.dsl.SnapConfinement
|
||||
import io.github.kdroidfilter.nucleus.desktop.application.dsl.SnapGrade
|
||||
import io.github.kdroidfilter.nucleus.desktop.application.dsl.SnapPlug
|
||||
import io.github.kdroidfilter.nucleus.desktop.application.dsl.TargetFormat
|
||||
|
||||
val data_version: String by extra
|
||||
|
||||
|
|
@ -7,10 +11,11 @@ plugins {
|
|||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.compose.compiler)
|
||||
alias(libs.plugins.jetbrains.compose)
|
||||
// Nucleus brings its own forked compose-desktop application tasks;
|
||||
// applying org.jetbrains.compose alongside it collides on task names.
|
||||
alias(libs.plugins.nucleus)
|
||||
alias(libs.plugins.jetbrains.kover)
|
||||
alias(libs.plugins.aboutlibraries.plugin)
|
||||
alias(libs.plugins.flatpak.gradle.generator)
|
||||
}
|
||||
|
||||
group = "com.darkrockstudios.apps.hammer.desktop"
|
||||
|
|
@ -40,8 +45,10 @@ kotlin {
|
|||
implementation(project(":base"))
|
||||
implementation(project(":common"))
|
||||
implementation(project(":composeUi"))
|
||||
implementation(compose.components.uiToolingPreview)
|
||||
implementation(compose.desktop.currentOs)
|
||||
// Explicit coords — org.jetbrains.compose plugin isn't applied here
|
||||
// because nucleus brings its own forked desktop-application tasks.
|
||||
implementation(libs.jetbrains.compose.ui.tooling.preview)
|
||||
implementation(libs.jetbrains.compose.desktop)
|
||||
implementation(libs.darklaf.core)
|
||||
implementation(libs.kotlinx.cli)
|
||||
}
|
||||
|
|
@ -55,59 +62,117 @@ kotlin {
|
|||
}
|
||||
}
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "com.darkrockstudios.apps.hammer.desktop.MainKt"
|
||||
nativeDistributions {
|
||||
targetFormats(
|
||||
TargetFormat.Dmg,
|
||||
TargetFormat.Pkg,
|
||||
TargetFormat.AppImage,
|
||||
TargetFormat.Msi,
|
||||
TargetFormat.Exe,
|
||||
TargetFormat.Deb,
|
||||
TargetFormat.Rpm
|
||||
)
|
||||
modules = arrayListOf(":base", ":common", ":composeUi", ":desktop")
|
||||
includeAllModules = true
|
||||
packageName = "hammer"
|
||||
packageVersion = libs.versions.app.get()
|
||||
description = "A simple tool for building stories."
|
||||
copyright = "© 2025 Adam W. Brown, All rights reserved."
|
||||
licenseFile.set(project.file("../LICENSE"))
|
||||
outputBaseDir.set(project.layout.buildDirectory.dir("installers"))
|
||||
nucleus.application {
|
||||
mainClass = "com.darkrockstudios.apps.hammer.desktop.MainKt"
|
||||
jvmArgs += listOf("-Dcompose.application.configure.swing.globals=false")
|
||||
|
||||
windows {
|
||||
menuGroup = "Hammer"
|
||||
shortcut = true
|
||||
console = false
|
||||
buildTypes.release.proguard {
|
||||
version.set("7.6.0")
|
||||
isEnabled.set(true)
|
||||
obfuscate.set(false)
|
||||
optimize.set(false)
|
||||
configurationFiles.from("proguard-rules.pro")
|
||||
}
|
||||
|
||||
iconFile.set(project.file("icons/windows.ico"))
|
||||
}
|
||||
nativeDistributions {
|
||||
targetFormats(
|
||||
// macOS
|
||||
TargetFormat.Dmg,
|
||||
TargetFormat.Pkg,
|
||||
// Windows
|
||||
TargetFormat.Msi,
|
||||
TargetFormat.Exe, // alias for Nsis
|
||||
TargetFormat.Portable,
|
||||
TargetFormat.AppX, // replaces bespoke packageMsix
|
||||
// Linux
|
||||
TargetFormat.Deb,
|
||||
TargetFormat.Rpm,
|
||||
TargetFormat.AppImage,
|
||||
TargetFormat.Snap, // replaces bespoke snapcraft build
|
||||
TargetFormat.Flatpak, // replaces bespoke flatpak-builder
|
||||
)
|
||||
includeAllModules = true
|
||||
packageName = "hammer"
|
||||
packageVersion = libs.versions.app.get()
|
||||
description = "A simple tool for building stories."
|
||||
copyright = "© 2025 Adam W. Brown, All rights reserved."
|
||||
vendor = "Dark Rock Studios"
|
||||
licenseFile.set(project.file("../LICENSE"))
|
||||
|
||||
linux {
|
||||
rpmLicenseType = "MIT"
|
||||
shortcut = true
|
||||
windows {
|
||||
menuGroup = "Hammer"
|
||||
console = false
|
||||
iconFile.set(project.file("icons/windows.ico"))
|
||||
|
||||
iconFile.set(project.file("icons/linux.png"))
|
||||
}
|
||||
|
||||
macOS {
|
||||
dockName = "Hammer"
|
||||
appStore = false
|
||||
|
||||
iconFile.set(project.file("icons/macos.icns"))
|
||||
appx {
|
||||
// Mirrors the previous msix/AppxManifest.xml.
|
||||
applicationId = "Hammer"
|
||||
identityName = "DarkRockStudios.HammerEditor"
|
||||
publisher = "CN=1CB419E3-12E3-4F8B-B2CC-2C4F16D8E686"
|
||||
displayName = "Hammer Editor"
|
||||
publisherDisplayName = "Dark Rock Studios"
|
||||
languages = listOf(
|
||||
"en-US", "de-DE", "fr-FR", "es-ES", "it-IT", "pt-BR", "uk-UA"
|
||||
)
|
||||
storeLogo.set(project.file("../msix/Assets/StoreLogo.png"))
|
||||
square44x44Logo.set(project.file("../msix/Assets/Square44x44Logo.png"))
|
||||
square150x150Logo.set(project.file("../msix/Assets/Square150x150Logo.png"))
|
||||
wide310x150Logo.set(project.file("../msix/Assets/Wide310x150Logo.png"))
|
||||
}
|
||||
}
|
||||
jvmArgs("-Dcompose.application.configure.swing.globals=false")
|
||||
|
||||
buildTypes.release.proguard {
|
||||
version.set("7.6.0")
|
||||
isEnabled.set(true)
|
||||
obfuscate.set(false)
|
||||
optimize.set(false)
|
||||
//joinOutputJars.set(true)
|
||||
configurationFiles.from("proguard-rules.pro")
|
||||
linux {
|
||||
shortcut = true
|
||||
rpmLicenseType = "MIT"
|
||||
iconFile.set(project.file("icons/linux.png"))
|
||||
|
||||
appImage {
|
||||
category = AppImageCategory.Office
|
||||
genericName = "Story Editor"
|
||||
synopsis = "A simple tool for building stories."
|
||||
desktopEntries = mapOf("Keywords" to "writing;editor;story;novel;")
|
||||
}
|
||||
|
||||
snap {
|
||||
confinement = SnapConfinement.Strict
|
||||
grade = SnapGrade.Stable
|
||||
summary = "A simple tool for building stories"
|
||||
base = "core22"
|
||||
compression = SnapCompression.Xz
|
||||
plugs = listOf(
|
||||
SnapPlug.Home,
|
||||
SnapPlug.Network,
|
||||
SnapPlug.NetworkBind,
|
||||
SnapPlug.Desktop,
|
||||
SnapPlug.DesktopLegacy,
|
||||
SnapPlug.Wayland,
|
||||
SnapPlug.X11,
|
||||
SnapPlug.Opengl,
|
||||
SnapPlug.RemovableMedia,
|
||||
)
|
||||
}
|
||||
|
||||
flatpak {
|
||||
runtime = "org.freedesktop.Platform"
|
||||
runtimeVersion = "24.08"
|
||||
sdk = "org.freedesktop.Sdk"
|
||||
finishArgs = listOf(
|
||||
"--socket=x11",
|
||||
"--socket=wayland",
|
||||
"--share=ipc",
|
||||
"--share=network",
|
||||
"--device=dri",
|
||||
"--filesystem=home",
|
||||
"--filesystem=xdg-documents",
|
||||
)
|
||||
license.set(project.file("../LICENSE"))
|
||||
}
|
||||
}
|
||||
|
||||
macOS {
|
||||
dockName = "Hammer"
|
||||
bundleID = "studio.darkrock.hammer"
|
||||
iconFile.set(project.file("icons/macos.icns"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,117 +183,3 @@ aboutLibraries {
|
|||
excludeFields.addAll("generated")
|
||||
}
|
||||
}
|
||||
|
||||
registerLinuxDistributionTasks(libs.versions.app.get())
|
||||
|
||||
// MSIX packaging task for Windows Store
|
||||
tasks.register("packageMsix") {
|
||||
group = "distribution"
|
||||
description = "Creates an MSIX package for Microsoft Store submission"
|
||||
|
||||
// Only run on Windows
|
||||
onlyIf {
|
||||
org.gradle.internal.os.OperatingSystem.current().isWindows
|
||||
}
|
||||
|
||||
// Depend on the distributable being created first
|
||||
dependsOn("createDistributable")
|
||||
|
||||
doLast {
|
||||
val appVersion = libs.versions.app.get()
|
||||
val msixVersion = "$appVersion.0" // MSIX requires 4-part version
|
||||
|
||||
val buildDir = project.layout.buildDirectory.get().asFile
|
||||
val distributableDir = buildDir.resolve("installers/main/app/hammer")
|
||||
val msixDir = project.rootDir.resolve("msix")
|
||||
val outputMsix = buildDir.resolve("installers/Hammer-${appVersion}.msix")
|
||||
|
||||
// Copy manifest
|
||||
println("Copying AppxManifest.xml...")
|
||||
val manifestSrc = msixDir.resolve("AppxManifest.xml")
|
||||
val manifestDst = distributableDir.resolve("AppxManifest.xml")
|
||||
manifestSrc.copyTo(manifestDst, overwrite = true)
|
||||
|
||||
// Update version in manifest (only in Identity element)
|
||||
println("Updating version to $msixVersion...")
|
||||
val manifestContent = manifestDst.readText()
|
||||
val updatedManifest = manifestContent.replace(
|
||||
Regex("""(<Identity[^>]*Version=")[\d\.]+""""),
|
||||
"$1$msixVersion\""
|
||||
)
|
||||
manifestDst.writeText(updatedManifest)
|
||||
|
||||
// Copy assets
|
||||
println("Copying Assets...")
|
||||
val assetsSrc = msixDir.resolve("Assets")
|
||||
val assetsDst = distributableDir.resolve("Assets")
|
||||
assetsSrc.copyRecursively(assetsDst, overwrite = true)
|
||||
|
||||
// Find makeappx.exe
|
||||
val makeappxPath = findMakeAppx()
|
||||
if (makeappxPath == null) {
|
||||
throw GradleException(
|
||||
"makeappx.exe not found. Please install Windows SDK.\n" +
|
||||
"Download from: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/"
|
||||
)
|
||||
}
|
||||
|
||||
println("Packaging MSIX with makeappx...")
|
||||
val result = project.providers.exec {
|
||||
commandLine(
|
||||
makeappxPath,
|
||||
"pack",
|
||||
"/d", distributableDir.absolutePath,
|
||||
"/p", outputMsix.absolutePath,
|
||||
"/o"
|
||||
)
|
||||
isIgnoreExitValue = true
|
||||
}.result.get()
|
||||
|
||||
if (result.exitValue == 0) {
|
||||
println("✓ MSIX package created successfully!")
|
||||
println(" Location: ${outputMsix.absolutePath}")
|
||||
println(" Size: ${String.format("%.2f MB", outputMsix.length() / 1024.0 / 1024.0)}")
|
||||
println("\nReady for Microsoft Store submission!")
|
||||
} else {
|
||||
throw GradleException("Failed to create MSIX package")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun findMakeAppx(): String? {
|
||||
// Common Windows SDK locations
|
||||
val searchPaths = listOf(
|
||||
"C:\\Program Files (x86)\\Windows Kits\\10\\bin",
|
||||
"C:\\Program Files (x86)\\Windows Kits\\10\\App Certification Kit"
|
||||
)
|
||||
|
||||
for (basePath in searchPaths) {
|
||||
val baseDir = file(basePath)
|
||||
if (baseDir.exists()) {
|
||||
// Search in bin subdirectories (e.g., 10.0.22621.0/x64/)
|
||||
baseDir.walk().forEach { file ->
|
||||
if (file.name == "makeappx.exe") {
|
||||
return file.absolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
tasks.named("flatpakGradleGenerator") {
|
||||
setProperty("outputFile", rootProject.file("flatpak/flatpak-sources.json"))
|
||||
|
||||
// Exclude test configurations to reduce the dependency list
|
||||
setProperty("excludeConfigurations", listOf(
|
||||
"testCompileClasspath",
|
||||
"testRuntimeClasspath",
|
||||
"jvmTestCompileClasspath",
|
||||
"jvmTestRuntimeClasspath"
|
||||
))
|
||||
|
||||
// Set architecture (x86_64 is the default, but can specify for clarity)
|
||||
// setProperty("onlyArches", "x86_64")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ kotlinx-io-okio = "0.9.0"
|
|||
compose-texteditor = "2.0.0"
|
||||
platform-spellcheckerkt = "1.1.0"
|
||||
flatpak-gradle-generator = "1.7.0"
|
||||
nucleus = "1.15.4"
|
||||
colorpicker-compose = "1.1.4"
|
||||
material-kolor = "4.1.1"
|
||||
|
||||
|
|
@ -116,6 +117,8 @@ cryptography-provider-jdk = { module = "dev.whyoleg.cryptography:cryptography-pr
|
|||
jetbrains_compose_ui_util = { module = "org.jetbrains.compose.ui:ui-util", version.ref = "jetbrains_compose" }
|
||||
jetbrains_compose_ui_text = { module = "org.jetbrains.compose.ui:ui-text", version.ref = "jetbrains_compose" }
|
||||
jetbrains_compose_ui_backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "jetbrains_compose" }
|
||||
jetbrains_compose_ui_tooling_preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "jetbrains_compose" }
|
||||
jetbrains_compose_desktop = { module = "org.jetbrains.compose.desktop:desktop", version.ref = "jetbrains_compose" }
|
||||
|
||||
coil_compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
|
||||
coil_network_ktor = { group = "io.coil-kt.coil3", name = "coil-network-ktor3", version.ref = "coil" }
|
||||
|
|
@ -296,6 +299,7 @@ buildconfig = { id = "com.github.gmazzo.buildconfig", version.ref = "buildconfig
|
|||
aboutlibraries_plugin = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
|
||||
aboutlibraries_plugin_android = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutlibraries" }
|
||||
flatpak_gradle_generator = { id = "io.github.jwharm.flatpak-gradle-generator", version.ref = "flatpak-gradle-generator" }
|
||||
nucleus = { id = "io.github.kdroidfilter.nucleus", version.ref = "nucleus" }
|
||||
|
||||
[bundles]
|
||||
koin_server = ["koin_core", "koin_core_coroutines", "koin_sl4j", "koin_ktor"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue