mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
Amethyst v1.13.1 (and every prior release) shipped only linux-x64 desktop
binaries — .deb, .rpm, .AppImage, .flatpak, .tar.gz. Same for the amy
CLI and geode relay. Users on aarch64 hardware (Pinebook, Ampere
Altra, Raspberry Pi 4/5, AWS Graviton, arm64 servers, arm64 Chromebooks
running crostini, etc.) can't install any of them.
This teaches the release matrix about arm64:
- Add `ubuntu-24.04-arm` legs to build-desktop, build-cli, and
build-geode. This is a standard, free public-repo GitHub-hosted
runner (4 CPU / 16 GB / 14 GB SSD / arm64) since early 2025. No
cross-compilation: jpackage / jlink / Compose Multiplatform 1.11
all produce host-native artifacts.
- Fetch the matching `appimagetool-<arch>.AppImage` from the same
1.9.0 release with an arch-specific SHA256 pin. `APPIMAGETOOL_URL`
becomes `APPIMAGETOOL_VERSION` + per-arch SHA256 env vars.
- Parametrize the portable tarball/zip filename by `matrix.arch`
(`amethyst-desktop-<ver>-linux-arm64.tar.gz` is now produced).
- Parametrize the Flatpak bundle filename and rewrite the manifest's
`GST_PLUGIN_SYSTEM_PATH` from `x86_64-linux-gnu` to
`aarch64-linux-gnu` on the arm64 leg. The Flathub-submission manifest
(`desktopApp/packaging/flatpak/flathub/`) still gates on
`only-arches: x86_64` — flipping that to include aarch64 is a
follow-up once a Flathub aarch64 build has been validated end-to-end.
- Make the `createReleaseAppImage` gradle task pick its host arch from
`System.getProperty("os.arch")` (amd64/x86_64 → `x86_64`, aarch64/
arm64 → `aarch64`). Same task, same command, drives both legs.
- Fix `desktopApp/packaging/appimage/AppRun` to compute the multiarch
library path from `uname -m` at launch time instead of hard-coding
`x86_64-linux-gnu`. One script works in both AppImages on the target
machine.
- Extend the desktop smoke test to run the release .deb build + launch
probe on `ubuntu-24.04-arm` too, so arch-specific ProGuard/jlink
breakage (missing native lib, arch-specific reflection root) is
caught at PR time.
- Update BUILDING.md and scripts/asset-name.sh docs with the new
arm64 asset names.
Follow-up assets published for the next tag push (v1.13.2+):
- amethyst-desktop-<ver>-linux-arm64.{deb,rpm,AppImage,flatpak,tar.gz}
- amy-<ver>-linux-arm64.{deb,rpm,tar.gz}
- geode-<ver>-linux-arm64.{deb,rpm,tar.gz}
Verification (local, before submitting):
- `python3 -c 'import yaml; yaml.safe_load(open(".github/workflows/create-release.yml"))'` — parses clean
- `bash -n scripts/asset-name.sh desktopApp/packaging/appimage/AppRun` — parses clean
- `actionlint` — reports only pre-existing shellcheck style hints; no new errors
- Confirmed `linuxdeploy-aarch64.AppImage` and
`appimagetool-aarch64.AppImage` exist under the same pinned release
tags used for x86_64; SHA256 recorded from a fresh download.
Not addressed (out of scope for this PR):
- Homebrew / winget bump workflows (`bump-homebrew*.yml`,
`bump-winget.yml`) — those consume the assets by name; the new arm64
filenames don't change any x86_64 name they already reference.
- Android arm64 continues to ship as before (already had it).
421 lines
20 KiB
Kotlin
421 lines
20 KiB
Kotlin
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
|
import java.nio.file.Files
|
|
import java.util.zip.ZipFile
|
|
|
|
plugins {
|
|
alias(libs.plugins.jetbrainsKotlinJvm)
|
|
alias(libs.plugins.composeMultiplatform)
|
|
alias(libs.plugins.jetbrainsComposeCompiler)
|
|
}
|
|
|
|
// RPM rejects dashes in version strings — replace with tilde (~) which RPM uses
|
|
// for prerelease ordering: 1.08.0~rc1 < 1.08.0 per RPM version comparison rules.
|
|
val appVersion: String = project.version.toString()
|
|
|
|
sourceSets {
|
|
main {
|
|
kotlin.srcDir("src/jvmMain/kotlin")
|
|
resources.srcDir("src/jvmMain/resources")
|
|
}
|
|
test {
|
|
kotlin.srcDir("src/jvmTest/kotlin")
|
|
resources.srcDir("src/jvmTest/resources")
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
jvmToolchain(21)
|
|
}
|
|
|
|
dependencies {
|
|
implementation(compose.desktop.currentOs)
|
|
implementation(libs.jetbrains.compose.material3)
|
|
implementation(libs.jetbrains.compose.components.resources)
|
|
|
|
// Quartz Nostr library (will use JVM target)
|
|
implementation(project(":quartz"))
|
|
|
|
// Commons library
|
|
implementation(project(":commons"))
|
|
|
|
// Lifecycle ViewModel (needed to access ViewModel supertype from commons)
|
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
|
|
|
// Coroutines
|
|
implementation(libs.kotlinx.coroutines.core)
|
|
implementation(libs.kotlinx.coroutines.swing)
|
|
|
|
// Networking
|
|
implementation(libs.okhttp)
|
|
|
|
// JSON
|
|
implementation(libs.jackson.module.kotlin)
|
|
|
|
// Image loading (Coil3 — explicit because commons uses implementation, not api)
|
|
implementation(libs.coil.compose)
|
|
implementation(libs.coil.okhttp)
|
|
implementation(libs.coil.svg)
|
|
|
|
// Video / audio playback — MIT, OS-native backends (MF / AVFoundation / GStreamer)
|
|
// composemediaplayer 0.10.0 leaks kotlinx-coroutines-test as a *runtime* dependency
|
|
// in its published POM. That jar ships a META-INF/services registration for
|
|
// kotlinx.coroutines.CoroutineExceptionHandler -> ExceptionCollectorAsService; the
|
|
// release ProGuard pass strips the (unreferenced) provider class but keeps the
|
|
// services file, so the packaged dmg crashes at startup with a
|
|
// ServiceConfigurationError the first time the coroutine exception handler loads.
|
|
// It is test-only code that must never be on the production classpath — exclude it.
|
|
implementation(libs.composemediaplayer) {
|
|
exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-test")
|
|
}
|
|
|
|
// Thumbnail extraction — JCodec (pure-Java H.264). LGPL FFmpeg subprocess
|
|
// for non-H.264 / HLS fallback is invoked via plain ProcessBuilder; no
|
|
// wrapper library needed (see VideoThumbnailCache.runFfmpegToImage).
|
|
implementation(libs.jcodec)
|
|
implementation(libs.jcodec.javase)
|
|
|
|
// EXIF stripping (lossless)
|
|
implementation(libs.commons.imaging)
|
|
|
|
// Collections
|
|
implementation(libs.kotlinx.collections.immutable)
|
|
implementation(libs.androidx.collection)
|
|
|
|
// Tor daemon (desktop embedded via kmp-tor)
|
|
implementation(libs.kmp.tor.runtime)
|
|
implementation(libs.kmp.tor.resource.exec.tor)
|
|
|
|
// SLF4J no-op — silence "No SLF4J providers" warnings from transitive deps
|
|
implementation(libs.slf4j.nop)
|
|
|
|
// QR code generation (ZXing core)
|
|
implementation(libs.zxing)
|
|
|
|
// Testing
|
|
testImplementation(libs.kotlin.test)
|
|
testImplementation(libs.kotlinx.coroutines.test)
|
|
testImplementation(libs.mockk)
|
|
testImplementation(libs.okhttp)
|
|
|
|
// Compose UI testing (createComposeRule / onNodeWithText / etc.)
|
|
testImplementation(libs.jetbrains.compose.ui.test.junit4)
|
|
}
|
|
|
|
compose.desktop {
|
|
application {
|
|
mainClass = "com.vitorpamplona.amethyst.desktop.MainKt"
|
|
jvmArgs += "--add-opens=java.base/java.nio=ALL-UNNAMED"
|
|
|
|
jvmArgs += "-Xmx2g"
|
|
|
|
// Forward platform-preview overrides from the gradle invocation to the
|
|
// launched app's JVM so `./gradlew :desktopApp:run -Damethyst.platform=GNOME`
|
|
// works in addition to the env-var form (`AMETHYST_PLATFORM=GNOME`).
|
|
listOf("amethyst.platform", "amethyst.appearance", "amethyst.accent").forEach { key ->
|
|
System.getProperty(key)?.let { jvmArgs += "-D$key=$it" }
|
|
}
|
|
|
|
nativeDistributions {
|
|
appResourcesRootDir.set(project.layout.projectDirectory.dir("src/jvmMain/appResources"))
|
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb, TargetFormat.Rpm)
|
|
// Output of ./gradlew suggestRuntimeModules (+ java.management already present)
|
|
modules(
|
|
"java.instrument", // Runtime instrumentation (agent/profiler hooks)
|
|
"java.management", // Required by kmp-tor TorRuntime
|
|
"java.prefs", // java.util.prefs (desktop persistence)
|
|
"java.sql", // JDBC metadata (Jackson, SQLite driver)
|
|
"jdk.security.auth", // JAAS authentication callbacks
|
|
"jdk.unsupported", // sun.misc.Unsafe (secp256k1-kmp-jni-jvm, JNA)
|
|
)
|
|
|
|
packageName = "Amethyst"
|
|
packageVersion = appVersion
|
|
description = "Nostr client for desktop"
|
|
vendor = "Amethyst Contributors"
|
|
|
|
macOS {
|
|
bundleID = "com.vitorpamplona.amethyst.desktop"
|
|
iconFile.set(project.file("src/jvmMain/resources/icon.icns"))
|
|
|
|
// --- Developer ID code signing + notarization ---
|
|
// Required for Homebrew's main cask (unsigned casks rejected after
|
|
// 2026-09-01) and to clear macOS Gatekeeper without the right-click
|
|
// dance. Gated on the signing-identity env var so local dev builds
|
|
// and PR CI keep producing plain UNSIGNED DMGs exactly as before —
|
|
// signing only kicks in when the release workflow exports these
|
|
// (which it does only when the Apple secrets are present):
|
|
//
|
|
// AMETHYST_MAC_SIGN_IDENTITY "Developer ID Application: NAME (TEAMID)"
|
|
// AMETHYST_NOTARY_APPLE_ID Apple ID email of the notary account
|
|
// AMETHYST_NOTARY_PASSWORD app-specific password for that Apple ID
|
|
// AMETHYST_NOTARY_TEAM_ID 10-char Apple Developer Team ID
|
|
//
|
|
// The Developer ID Application certificate must already be in the
|
|
// build host's keychain (CI imports it from a base64 .p12 secret).
|
|
// Compose ships default hardened-runtime entitlements that permit
|
|
// the JVM's JIT, so no custom entitlements file is needed.
|
|
val macSignIdentity = System.getenv("AMETHYST_MAC_SIGN_IDENTITY")
|
|
if (!macSignIdentity.isNullOrBlank()) {
|
|
signing {
|
|
sign.set(true)
|
|
identity.set(macSignIdentity)
|
|
// Compose's MacSigner maps the identity to a certificate via
|
|
// `security find-certificate`, which doesn't reliably resolve
|
|
// the CI-imported cert through the keychain search list (fails
|
|
// with "Could not find certificate ... in keychain []"). When
|
|
// the workflow exports the throwaway keychain path, point the
|
|
// lookup straight at it. Empty/unset on local builds — Compose
|
|
// then falls back to the default search list as before.
|
|
System.getenv("AMETHYST_MAC_SIGN_KEYCHAIN")
|
|
?.takeIf { it.isNotBlank() }
|
|
?.let { keychain.set(it) }
|
|
}
|
|
notarization {
|
|
appleID.set(System.getenv("AMETHYST_NOTARY_APPLE_ID"))
|
|
password.set(System.getenv("AMETHYST_NOTARY_PASSWORD"))
|
|
teamID.set(System.getenv("AMETHYST_NOTARY_TEAM_ID"))
|
|
}
|
|
}
|
|
}
|
|
|
|
windows {
|
|
iconFile.set(project.file("src/jvmMain/resources/icon.ico"))
|
|
menuGroup = "Amethyst"
|
|
upgradeUuid = "A1B2C3D4-E5F6-7890-ABCD-EF1234567890"
|
|
// AUMID persistence for Windows Toast notifications — without a
|
|
// Start Menu shortcut the AUMID isn't registered and toasts
|
|
// disappear from Windows Settings → Notifications after reboot.
|
|
menu = true
|
|
shortcut = true
|
|
}
|
|
|
|
linux {
|
|
iconFile.set(project.file("src/jvmMain/resources/icon.png"))
|
|
menuGroup = "Network"
|
|
appCategory = "Network"
|
|
debMaintainer = "vitor@vitorpamplona.com"
|
|
// SPDX compound expression. Bundled components:
|
|
// MIT — Amethyst + kdroidFilter ComposeMediaPlayer
|
|
// LGPL-2.1-or-later — FFmpeg (LGPL build, bundled per OS for thumbnail fallback) +
|
|
// GStreamer (Linux runtime dep, system-installed)
|
|
// BSD-2-Clause — JCodec
|
|
// Apache-2.0 — Jaffree + many transitive Java libraries
|
|
rpmLicenseType = "MIT AND LGPL-2.1-or-later AND BSD-2-Clause AND Apache-2.0"
|
|
// RPM version: replace dashes with tilde (1.08.0~rc1 < 1.08.0 per RPM ordering).
|
|
rpmPackageVersion = appVersion.replace("-", "~")
|
|
}
|
|
}
|
|
|
|
// Compose Multiplatform 1.11.0 wired ProGuard 7.7.0 into the release
|
|
// build. The Android (mobile) module already solved the same class of
|
|
// problems with `-dontobfuscate` plus global `-keepnames` / `-keep enum`
|
|
// rules (see `amethyst/proguard-rules.pro`). We mirror that strategy in
|
|
// `compose-rules.pro` so the desktop release survives JNI callbacks
|
|
// (secp256k1-kmp, sqlite-bundled, jkeychain, kdroidFilter native)
|
|
// and reflection-heavy libraries (Jackson, JNA) without renaming.
|
|
//
|
|
// Shrink and optimize stay ON. One ProGuard optimize sub-pass is
|
|
// disabled in `compose-rules.pro` to avoid a generated okio bridge
|
|
// whose declared return type the JVM verifier rejects (R8 doesn't hit
|
|
// this — it generates bridges differently from ProGuard).
|
|
buildTypes.release.proguard {
|
|
version.set("7.9.1") // Kotlin 2.3 metadata support
|
|
configurationFiles.from(project.file("compose-rules.pro"))
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- AppImage packaging (Linux) ---
|
|
//
|
|
// Compose Multiplatform's TargetFormat.AppImage is known-broken in 1.10.x (CMP-7101).
|
|
// Instead: wrap `createReleaseDistributable` output with `appimagetool`, which
|
|
// just packages an AppDir as-is. We deliberately avoid `linuxdeploy` here —
|
|
// linuxdeploy auto-walks every binary in the AppDir with ldd to bundle deps,
|
|
// but jpackage already ships a self-contained tree we don't want it touching
|
|
// (the bundled JRE has libjvm.so under usr/lib/runtime/lib/server/ while sibling
|
|
// libs use $ORIGIN RPATH — ldd can't resolve without help).
|
|
// appimagetool sidesteps that — it only embeds the AppDir into a SquashFS,
|
|
// runtime-prepended, signed AppImage. AppRun handles LD_LIBRARY_PATH at launch.
|
|
//
|
|
// kdroidFilter (video/audio) links against system GStreamer at runtime — the
|
|
// AppImage does not bundle GStreamer; the host system must have it installed.
|
|
//
|
|
// Build inputs live in desktopApp/packaging/appimage/:
|
|
// - AppRun shell launcher
|
|
// - amethyst.desktop XDG desktop entry
|
|
// - amethyst.png 512x512 icon
|
|
//
|
|
// appimagetool binary is fetched by CI (SHA-verified) into
|
|
// desktopApp/packaging/appimage/ as appimagetool-<arch>.AppImage.
|
|
// The arch is selected at task-execution time from the host JVM's os.arch, so
|
|
// the same task builds the correct AppImage on both x86_64 and aarch64 hosts.
|
|
// BUILDING.md documents local-dev fetch.
|
|
val createReleaseAppImage by tasks.registering(Exec::class) {
|
|
group = "compose desktop"
|
|
description = "Package createReleaseDistributable output into a Linux AppImage via appimagetool."
|
|
dependsOn("createReleaseDistributable")
|
|
|
|
// AppImage's ARCH env accepts the Linux kernel arch names: x86_64 / aarch64
|
|
// / armhf / i686. jpackage produces host-native binaries, so mirror the
|
|
// host JVM arch. Do not read the property inside doFirst — it needs to be
|
|
// resolved at configuration time so outputs.file() below is stable.
|
|
val hostArch = when (val a = System.getProperty("os.arch").lowercase()) {
|
|
"amd64", "x86_64" -> "x86_64"
|
|
"aarch64", "arm64" -> "aarch64"
|
|
else -> a
|
|
}
|
|
|
|
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-$hostArch.AppImage")
|
|
val toolRoot = layout.projectDirectory.dir("packaging/appimage")
|
|
val appimagetool = toolRoot.file("appimagetool-$hostArch.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 (!appimagetool.asFile.canExecute()) {
|
|
appimagetool.asFile.setExecutable(true)
|
|
}
|
|
}
|
|
|
|
commandLine(
|
|
appimagetool.asFile.absolutePath,
|
|
appDir.get().asFile.absolutePath,
|
|
outFile.get().asFile.absolutePath,
|
|
)
|
|
environment("ARCH", hostArch)
|
|
// Bypass FUSE requirement on CI runners (ubuntu-latest lacks libfuse.so.2).
|
|
// AppImage standard env var: extracts + runs without mounting.
|
|
environment("APPIMAGE_EXTRACT_AND_RUN", "1")
|
|
}
|
|
|
|
// ============================================================================
|
|
// Native lib regression guard
|
|
// ============================================================================
|
|
// `pt.davidafsilva.apple:jkeychain` ships its `osxkeychain.so` native library
|
|
// as a resource at the JAR root, and `OSXKeychain.loadSharedObject()` reaches
|
|
// it with `getResourceAsStream("/osxkeychain.so")`. If a future ProGuard
|
|
// upgrade, dep swap, or build-config change strips this resource from the
|
|
// release distributable, every macOS user is forced back to the login screen
|
|
// on every cold boot (the Keychain backend can't load, `SecureKeyStorage`
|
|
// falls back to its password-prompted encrypted file, which never prompts
|
|
// in a GUI cold boot → keys silently null → bunker / nsec / NWC unrecoverable).
|
|
//
|
|
// As of the current build the resource DOES survive ProGuard (it ships in the
|
|
// proguarded jkeychain-1.1.0-*.jar with all 117 KB intact), so this task is
|
|
// a regression guard, not a workaround. It's wired onto every release task so
|
|
// it fails the build immediately if the .so disappears.
|
|
val verifyJkeychainNativeSurvivesProguard by tasks.registering {
|
|
description = "Fail the release build if osxkeychain.so is stripped from proguarded output (would break macOS Keychain at runtime)"
|
|
group = "verification"
|
|
dependsOn("proguardReleaseJars")
|
|
val proguardDir = layout.buildDirectory.dir("compose/tmp/main-release/proguard")
|
|
inputs.dir(proguardDir).withPropertyName("proguardOutput").withPathSensitivity(PathSensitivity.RELATIVE)
|
|
val marker = layout.buildDirectory.file("verify-jkeychain-native.ok")
|
|
outputs.file(marker)
|
|
doLast {
|
|
val dir = proguardDir.get().asFile
|
|
require(dir.isDirectory) { "ProGuard output dir missing: $dir" }
|
|
val jars = dir.listFiles { f -> f.extension == "jar" } ?: emptyArray()
|
|
require(jars.isNotEmpty()) { "No proguarded jars under $dir" }
|
|
val foundIn = jars.firstOrNull { jar ->
|
|
val zip = ZipFile(jar)
|
|
try {
|
|
val entries = zip.entries()
|
|
var found = false
|
|
while (entries.hasMoreElements()) {
|
|
if (entries.nextElement().name == "osxkeychain.so") {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
found
|
|
} finally {
|
|
zip.close()
|
|
}
|
|
}
|
|
require(foundIn != null) {
|
|
"REGRESSION: osxkeychain.so missing from every proguarded jar in $dir.\n" +
|
|
"OSXKeychain.loadSharedObject() calls getResourceAsStream(\"/osxkeychain.so\") — if this\n" +
|
|
"resource is not at classpath root in the release distributable, every macOS user is\n" +
|
|
"forced to re-log-in on every cold boot of the release DMG."
|
|
}
|
|
marker.get().asFile.writeText("verified osxkeychain.so survived ProGuard in ${foundIn.name}\n")
|
|
println("verifyJkeychainNativeSurvivesProguard: osxkeychain.so present in ${foundIn.name}")
|
|
}
|
|
}
|
|
|
|
// Gate every release-packaging task on the verification so a stripped .so
|
|
// never reaches a release artifact.
|
|
listOf(
|
|
"packageReleaseDmg",
|
|
"packageReleaseMsi",
|
|
"packageReleaseDeb",
|
|
"packageReleaseRpm",
|
|
"packageReleaseDistributionForCurrentOS",
|
|
"packageReleaseUberJarForCurrentOS",
|
|
"createReleaseDistributable",
|
|
"runReleaseDistributable",
|
|
).forEach { name ->
|
|
tasks.matching { it.name == name }.configureEach {
|
|
dependsOn(verifyJkeychainNativeSurvivesProguard)
|
|
}
|
|
}
|
|
|
|
// macOS notarization: the bundled jars (secp256k1, jna, sqlite-bundled, skiko,
|
|
// jkeychain, kdroidFilter mediaplayer) carry Mach-O natives that Compose's
|
|
// .app codesign never reaches — codesign doesn't descend into jars, but Apple's
|
|
// notary service does and rejects any unsigned Mach-O ("Invalid"). Sign those
|
|
// natives inside the *proguarded* jars (the exact artifacts Compose then copies
|
|
// into the .app) so the subsequent bundle signing seals already-signed code.
|
|
// Runs only on macOS with the Developer ID identity exported — a no-op on every
|
|
// other leg and on unsigned local/PR builds.
|
|
val signMacJarNatives by tasks.registering {
|
|
description = "Codesign macOS Mach-O natives embedded in bundled jars before the .app is sealed + notarized"
|
|
group = "build"
|
|
dependsOn("proguardReleaseJars")
|
|
val proguardDir = layout.buildDirectory.dir("compose/tmp/main-release/proguard")
|
|
val script = rootProject.file("scripts/sign-macos-jar-natives.sh")
|
|
val identity = providers.environmentVariable("AMETHYST_MAC_SIGN_IDENTITY")
|
|
val isMac = System.getProperty("os.name").lowercase().contains("mac")
|
|
onlyIf { isMac && !identity.orNull.isNullOrBlank() }
|
|
doLast {
|
|
val proc =
|
|
ProcessBuilder("bash", script.absolutePath, proguardDir.get().asFile.absolutePath)
|
|
.redirectErrorStream(true)
|
|
.also { it.environment()["SIGN_IDENTITY"] = identity.get() }
|
|
.start()
|
|
proc.inputStream.bufferedReader().forEachLine { println(it) }
|
|
val code = proc.waitFor()
|
|
if (code != 0) throw GradleException("sign-macos-jar-natives.sh failed with exit code $code")
|
|
}
|
|
}
|
|
|
|
// Must run after ProGuard writes the jars and before Compose assembles + signs
|
|
// the .app, so wire it onto createReleaseDistributable (which the DMG packaging
|
|
// depends on transitively).
|
|
tasks.matching { it.name == "createReleaseDistributable" }.configureEach {
|
|
dependsOn(signMacJarNatives)
|
|
}
|