feat(desktop): version source-of-truth + RPM + AppImage packaging
Phase 1+2 of multi-platform distribution plan:
- gradle/libs.versions.toml: add `app = "1.08.0"` as single source of truth
- Root build.gradle: allprojects { version = libs.versions.app.get() }
- amethyst/build.gradle: versionName from catalog (versionCode stays local)
- desktopApp/build.gradle.kts: drop hardcoded "1.0.0"; inherit project.version;
add TargetFormat.Rpm; add linux DSL (menuGroup, appCategory, debMaintainer,
rpmLicenseType, rpmPackageVersion with dashes stripped)
- desktopApp/build.gradle.kts: new createReleaseAppImage task wrapping
createReleaseDistributable with linuxdeploy (TargetFormat.AppImage is
broken in Compose 1.10.x — CMP-7101)
- packaging/appimage/: AppRun launcher (sets LD_LIBRARY_PATH for bundled VLC),
amethyst.desktop XDG entry, 512x512 icon extracted from icon.icns
- scripts/asset-name.sh: single source for release asset naming contract
This commit is contained in:
@@ -55,7 +55,7 @@ android {
|
|||||||
minSdk = libs.versions.android.minSdk.get().toInteger()
|
minSdk = libs.versions.android.minSdk.get().toInteger()
|
||||||
targetSdk = libs.versions.android.targetSdk.get().toInteger()
|
targetSdk = libs.versions.android.targetSdk.get().toInteger()
|
||||||
versionCode = 442
|
versionCode = 442
|
||||||
versionName = generateVersionName("1.08.0")
|
versionName = generateVersionName(libs.versions.app.get())
|
||||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"be99e8c8d4df0f54b44eb6c96976ccb38baeea0192436a1c6fc8bc5e930da6b0\""
|
buildConfigField "String", "RELEASE_NOTES_ID", "\"be99e8c8d4df0f54b44eb6c96976ccb38baeea0192436a1c6fc8bc5e930da6b0\""
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ plugins {
|
|||||||
alias(libs.plugins.serialization)
|
alias(libs.plugins.serialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared app version for all subprojects — read from gradle/libs.versions.toml.
|
||||||
|
// Android versionCode stays local in amethyst/build.gradle (must be monotonic int).
|
||||||
|
// Desktop packageVersion inherits via project.version in desktopApp/build.gradle.kts.
|
||||||
allprojects {
|
allprojects {
|
||||||
|
version = libs.versions.app.get()
|
||||||
|
|
||||||
configurations.configureEach {
|
configurations.configureEach {
|
||||||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.jetbrainsKotlinJvm)
|
alias(libs.plugins.jetbrainsKotlinJvm)
|
||||||
@@ -7,6 +9,11 @@ plugins {
|
|||||||
id("ir.mahozad.vlc-setup") version "0.1.0"
|
id("ir.mahozad.vlc-setup") version "0.1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RPM rejects dashes in version strings — strip prerelease suffix for Linux RPM only.
|
||||||
|
// Other formats accept full semver (DEB uses ~rc1, DMG/MSI accept bare versions).
|
||||||
|
val appVersion: String = project.version.toString()
|
||||||
|
val appVersionRpm: String = appVersion.substringBefore("-")
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
kotlin.srcDir("src/jvmMain/kotlin")
|
kotlin.srcDir("src/jvmMain/kotlin")
|
||||||
@@ -88,11 +95,11 @@ compose.desktop {
|
|||||||
|
|
||||||
nativeDistributions {
|
nativeDistributions {
|
||||||
appResourcesRootDir.set(project.layout.projectDirectory.dir("src/jvmMain/appResources"))
|
appResourcesRootDir.set(project.layout.projectDirectory.dir("src/jvmMain/appResources"))
|
||||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.Rpm)
|
||||||
modules("java.management") // Required by kmp-tor TorRuntime
|
modules("java.management") // Required by kmp-tor TorRuntime
|
||||||
|
|
||||||
packageName = "Amethyst"
|
packageName = "Amethyst"
|
||||||
packageVersion = "1.0.0"
|
packageVersion = appVersion
|
||||||
description = "Nostr client for desktop"
|
description = "Nostr client for desktop"
|
||||||
vendor = "Amethyst Contributors"
|
vendor = "Amethyst Contributors"
|
||||||
|
|
||||||
@@ -109,6 +116,12 @@ compose.desktop {
|
|||||||
|
|
||||||
linux {
|
linux {
|
||||||
iconFile.set(project.file("src/jvmMain/resources/icon.png"))
|
iconFile.set(project.file("src/jvmMain/resources/icon.png"))
|
||||||
|
menuGroup = "Network"
|
||||||
|
appCategory = "Network"
|
||||||
|
debMaintainer = "vitor@vitorpamplona.com"
|
||||||
|
rpmLicenseType = "MIT"
|
||||||
|
// RPM version field rejects dashes; strip prerelease suffix for RPM builds.
|
||||||
|
rpmPackageVersion = appVersionRpm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,3 +139,68 @@ vlcSetup {
|
|||||||
tasks.named("spotlessKotlin") {
|
tasks.named("spotlessKotlin") {
|
||||||
mustRunAfter("vlcSetup")
|
mustRunAfter("vlcSetup")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- AppImage packaging (Linux) ---
|
||||||
|
//
|
||||||
|
// Compose Multiplatform's TargetFormat.AppImage is known-broken in 1.10.x (CMP-7101).
|
||||||
|
// Instead: wrap `createReleaseDistributable` output with `linuxdeploy` (which
|
||||||
|
// auto-bundles libraries, handles rpath, and calls appimagetool internally).
|
||||||
|
//
|
||||||
|
// Build inputs live in packaging/appimage/:
|
||||||
|
// - AppRun shell launcher (sets LD_LIBRARY_PATH including bundled VLC)
|
||||||
|
// - amethyst.desktop XDG desktop entry
|
||||||
|
// - amethyst.png 512x512 icon
|
||||||
|
//
|
||||||
|
// linuxdeploy binary is fetched by CI (SHA-verified) into packaging/appimage/
|
||||||
|
// as linuxdeploy-x86_64.AppImage. BUILDING.md documents local-dev fetch.
|
||||||
|
val createReleaseAppImage by tasks.registering(Exec::class) {
|
||||||
|
group = "compose desktop"
|
||||||
|
description = "Bundle createReleaseDistributable output into a Linux AppImage via linuxdeploy."
|
||||||
|
dependsOn("createReleaseDistributable")
|
||||||
|
|
||||||
|
val distDir = layout.buildDirectory.dir("compose/binaries/main-release/app/Amethyst")
|
||||||
|
val appDir = layout.buildDirectory.dir("appimage/Amethyst.AppDir")
|
||||||
|
val outFile = layout.buildDirectory.file("appimage/Amethyst-$appVersion-x86_64.AppImage")
|
||||||
|
val toolRoot = layout.projectDirectory.dir("../packaging/appimage")
|
||||||
|
val linuxdeployTool = toolRoot.file("linuxdeploy-x86_64.AppImage")
|
||||||
|
|
||||||
|
inputs.dir(distDir)
|
||||||
|
inputs.dir(toolRoot)
|
||||||
|
outputs.file(outFile)
|
||||||
|
|
||||||
|
doFirst {
|
||||||
|
val dir = appDir.get().asFile
|
||||||
|
dir.deleteRecursively()
|
||||||
|
dir.mkdirs()
|
||||||
|
copy {
|
||||||
|
from(distDir) { into("usr") }
|
||||||
|
from(toolRoot.file("AppRun")) {
|
||||||
|
rename { "AppRun" }
|
||||||
|
filePermissions { unix("0755") }
|
||||||
|
}
|
||||||
|
from(toolRoot.file("amethyst.desktop"))
|
||||||
|
from(toolRoot.file("amethyst.png"))
|
||||||
|
into(dir)
|
||||||
|
}
|
||||||
|
// DirIcon is used by desktop integrations (file managers, AppImageLauncher)
|
||||||
|
val dirIcon = File(dir, ".DirIcon")
|
||||||
|
if (dirIcon.exists()) dirIcon.delete()
|
||||||
|
Files.createSymbolicLink(dirIcon.toPath(), File("amethyst.png").toPath())
|
||||||
|
|
||||||
|
if (!linuxdeployTool.asFile.canExecute()) {
|
||||||
|
linuxdeployTool.asFile.setExecutable(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commandLine(
|
||||||
|
linuxdeployTool.asFile.absolutePath,
|
||||||
|
"--appdir", appDir.get().asFile.absolutePath,
|
||||||
|
"--output", "appimage",
|
||||||
|
"--desktop-file", "${appDir.get().asFile}/amethyst.desktop",
|
||||||
|
"--icon-file", "${appDir.get().asFile}/amethyst.png",
|
||||||
|
)
|
||||||
|
environment("OUTPUT", outFile.get().asFile.absolutePath)
|
||||||
|
environment("ARCH", "x86_64")
|
||||||
|
// Suppress linuxdeploy's verbose library-scanner output; keep errors.
|
||||||
|
environment("LINUXDEPLOY_OUTPUT_VERSION", appVersion)
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
# Amethyst app version — single source of truth consumed by both Android (amethyst/)
|
||||||
|
# and Desktop (desktopApp/). Android versionCode is bumped independently in
|
||||||
|
# amethyst/build.gradle because it must be a monotonic integer.
|
||||||
|
app = "1.08.0"
|
||||||
accompanistAdaptive = "0.37.3"
|
accompanistAdaptive = "0.37.3"
|
||||||
cachemapVersion = "0.2.4"
|
cachemapVersion = "0.2.4"
|
||||||
composeMultiplatform = "1.10.3"
|
composeMultiplatform = "1.10.3"
|
||||||
|
|||||||
Executable
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# AppImage launcher for Amethyst Desktop.
|
||||||
|
# Sets LD_LIBRARY_PATH to find bundled VLC natives (vlcj dlopens libvlc.so at runtime).
|
||||||
|
set -e
|
||||||
|
HERE="$(dirname "$(readlink -f "${0}")")"
|
||||||
|
export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/lib/vlc:${HERE}/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}"
|
||||||
|
export PATH="${HERE}/usr/bin:${PATH}"
|
||||||
|
export APPDIR="${HERE}"
|
||||||
|
exec "${HERE}/usr/bin/Amethyst" "$@"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Amethyst
|
||||||
|
GenericName=Nostr Client
|
||||||
|
Comment=Nostr client for desktop
|
||||||
|
Exec=Amethyst %u
|
||||||
|
Icon=amethyst
|
||||||
|
Categories=Network;InstantMessaging;
|
||||||
|
Terminal=false
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass=Amethyst
|
||||||
|
Keywords=nostr;client;social;chat;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Single source of truth for Amethyst Desktop release asset naming.
|
||||||
|
#
|
||||||
|
# Contract: amethyst-desktop-<version>-<family>-<arch>.<ext>
|
||||||
|
# <version> = tag stripped of leading 'v' (e.g. "1.08.0")
|
||||||
|
# <family> = macos | windows | linux
|
||||||
|
# <arch> = x64 | arm64
|
||||||
|
# <ext> = dmg | msi | zip | deb | rpm | AppImage | tar.gz
|
||||||
|
#
|
||||||
|
# Consumed by: .github/workflows/create-release.yml, .github/workflows/bump-*.yml,
|
||||||
|
# BUILDING.md, local release runbooks.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# source scripts/asset-name.sh
|
||||||
|
# collect_assets <family> <arch> <version> <dest_dir>
|
||||||
|
#
|
||||||
|
# Expected examples:
|
||||||
|
# amethyst-desktop-1.08.0-macos-x64.dmg
|
||||||
|
# amethyst-desktop-1.08.0-macos-arm64.dmg
|
||||||
|
# amethyst-desktop-1.08.0-windows-x64.msi
|
||||||
|
# amethyst-desktop-1.08.0-windows-x64.zip
|
||||||
|
# amethyst-desktop-1.08.0-linux-x64.deb
|
||||||
|
# amethyst-desktop-1.08.0-linux-x64.rpm
|
||||||
|
# amethyst-desktop-1.08.0-linux-x64.AppImage
|
||||||
|
# amethyst-desktop-1.08.0-linux-x64.tar.gz
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Print the canonical asset filename for a given family/arch/version/extension.
|
||||||
|
# Usage: asset_name <family> <arch> <version> <ext>
|
||||||
|
asset_name() {
|
||||||
|
local family="$1" arch="$2" version="$3" ext="$4"
|
||||||
|
printf 'amethyst-desktop-%s-%s-%s.%s' "$version" "$family" "$arch" "$ext"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy + rename build outputs into <dest_dir> using the canonical naming scheme.
|
||||||
|
# Usage: collect_assets <family> <arch> <version> <dest_dir>
|
||||||
|
# Expects build outputs under desktopApp/build/... (Compose binaries + custom tasks + portable archives).
|
||||||
|
collect_assets() {
|
||||||
|
local family="$1" arch="$2" version="$3" dest="$4"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
# Compose Desktop jpackage outputs (main-release/<format>/*.ext)
|
||||||
|
local src ext base dst
|
||||||
|
for src in \
|
||||||
|
desktopApp/build/compose/binaries/main-release/dmg/*.dmg \
|
||||||
|
desktopApp/build/compose/binaries/main-release/msi/*.msi \
|
||||||
|
desktopApp/build/compose/binaries/main-release/deb/*.deb \
|
||||||
|
desktopApp/build/compose/binaries/main-release/rpm/*.rpm \
|
||||||
|
desktopApp/build/appimage/*.AppImage \
|
||||||
|
desktopApp/build/portable/*.tar.gz \
|
||||||
|
desktopApp/build/portable/*.zip \
|
||||||
|
; do
|
||||||
|
[ -f "$src" ] || continue
|
||||||
|
base="$(basename "$src")"
|
||||||
|
case "$base" in
|
||||||
|
*.tar.gz) ext="tar.gz" ;;
|
||||||
|
*) ext="${base##*.}" ;;
|
||||||
|
esac
|
||||||
|
dst="$dest/$(asset_name "$family" "$arch" "$version" "$ext")"
|
||||||
|
cp "$src" "$dst"
|
||||||
|
echo "Collected: $dst"
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user