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()
|
||||
targetSdk = libs.versions.android.targetSdk.get().toInteger()
|
||||
versionCode = 442
|
||||
versionName = generateVersionName("1.08.0")
|
||||
versionName = generateVersionName(libs.versions.app.get())
|
||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"be99e8c8d4df0f54b44eb6c96976ccb38baeea0192436a1c6fc8bc5e930da6b0\""
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -12,7 +12,12 @@ plugins {
|
||||
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 {
|
||||
version = libs.versions.app.get()
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.jetbrainsKotlinJvm)
|
||||
@@ -7,6 +9,11 @@ plugins {
|
||||
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 {
|
||||
main {
|
||||
kotlin.srcDir("src/jvmMain/kotlin")
|
||||
@@ -88,11 +95,11 @@ compose.desktop {
|
||||
|
||||
nativeDistributions {
|
||||
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
|
||||
|
||||
packageName = "Amethyst"
|
||||
packageVersion = "1.0.0"
|
||||
packageVersion = appVersion
|
||||
description = "Nostr client for desktop"
|
||||
vendor = "Amethyst Contributors"
|
||||
|
||||
@@ -109,6 +116,12 @@ compose.desktop {
|
||||
|
||||
linux {
|
||||
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") {
|
||||
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]
|
||||
# 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"
|
||||
cachemapVersion = "0.2.4"
|
||||
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