Desktop .deb package
This commit is contained in:
@@ -1,13 +1,37 @@
|
||||
// SleepyOwl for the Linux desktop (LINUX_PLAN.md): windows, desktop screens, later the mpv player and system integration.
|
||||
// Run: ./gradlew :desktop:run, or tools/linux/desktop.sh without Gradle around the process.
|
||||
// Package: ./gradlew :desktop:packageDeb → desktop/build/deb/sleepyowl_<version>_amd64.deb (LINUX_PLAN.md L10).
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.PosixFilePermissions
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.compose.multiplatform)
|
||||
}
|
||||
|
||||
/** The desktop app's version: the About screen, the log, the .deb. */
|
||||
val appVersion = "0.1.0"
|
||||
|
||||
kotlin { jvmToolchain(21) }
|
||||
|
||||
// BuildInfo.VERSION from appVersion: one place for the number.
|
||||
val buildInfo = tasks.register("buildInfo") {
|
||||
val version = appVersion
|
||||
val out = layout.buildDirectory.dir("generated/buildinfo")
|
||||
inputs.property("version", version)
|
||||
outputs.dir(out)
|
||||
doLast {
|
||||
out.get().file("BuildInfo.kt").asFile.apply { parentFile.mkdirs() }.writeText(
|
||||
"package ru.stenfredd.sleepyowl.desktop\n\n/** Generated by desktop/build.gradle.kts. */\nobject BuildInfo {\n const val VERSION = \"$version\"\n}\n",
|
||||
)
|
||||
}
|
||||
}
|
||||
sourceSets.main { kotlin.srcDir(buildInfo) }
|
||||
|
||||
dependencies {
|
||||
implementation(project(":ui:shared"))
|
||||
implementation(project(":playback:engine-mpv"))
|
||||
@@ -32,9 +56,26 @@ compose.desktop {
|
||||
application {
|
||||
mainClass = "ru.stenfredd.sleepyowl.desktop.MainKt"
|
||||
jvmArgs += awtOpens
|
||||
nativeDistributions {
|
||||
// jpackage of JDK 21 with a jlink'd JRE inside: createDistributable → build/compose/binaries/main/app/sleepyowl.
|
||||
// The .deb around it is ours (packageDeb below): jpackage's own one can't depend on libmpv2.
|
||||
javaHome = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(21) }.get()
|
||||
.metadata.installationPath.asFile.absolutePath
|
||||
packageName = "sleepyowl"
|
||||
packageVersion = appVersion
|
||||
description = "Jellyfin client"
|
||||
vendor = "StenFredd"
|
||||
// suggestRuntimeModules gives java.instrument, jdk.security.auth (dbus-java), jdk.unsupported; jdeps can't see
|
||||
// TLS (jdk.crypto.ec: the public address over https) or locale data (jdk.localedata: track languages in Russian).
|
||||
modules("java.instrument", "jdk.security.auth", "jdk.unsupported", "jdk.crypto.ec", "jdk.localedata")
|
||||
linux { iconFile = file("src/main/resources/icons/sleepyowl-256.png") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Licence texts of "О приложении": the TV's (app/src/main/assets/licenses) next to the desktop's own in resources/licenses.
|
||||
tasks.processResources { from(rootProject.file("app/src/main/assets/licenses")) { into("licenses") } }
|
||||
|
||||
tasks.withType<Test>().configureEach { jvmArgs(awtOpens) }
|
||||
|
||||
// Classpath into a file: tools/linux/desktop.sh starts the app without Gradle.
|
||||
@@ -45,3 +86,75 @@ tasks.register("desktopClasspath") {
|
||||
outputs.file(out)
|
||||
doLast { out.get().asFile.writeText(cp.joinToString(":")) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The .deb: jpackage's app image in /opt/sleepyowl, `sleepyowl` in /usr/bin, the menu entry and the icons. The Debian
|
||||
* version gets the build time (0.1.0-202610271530), so `sudo apt install ./sleepyowl_*.deb` always takes a new build as
|
||||
* an update. Menus and icon caches refresh by dpkg's triggers (desktop-file-utils, hicolor-icon-theme): no scripts.
|
||||
*/
|
||||
abstract class PackageDeb : DefaultTask() {
|
||||
@get:InputDirectory abstract val appImage: DirectoryProperty
|
||||
@get:InputFile abstract val desktopFile: RegularFileProperty
|
||||
@get:InputDirectory abstract val icons: DirectoryProperty
|
||||
@get:Input abstract val version: Property<String>
|
||||
@get:Input abstract val maintainer: Property<String>
|
||||
@get:Input abstract val depends: ListProperty<String>
|
||||
@get:OutputDirectory abstract val outDir: DirectoryProperty
|
||||
@get:Inject abstract val exec: ExecOperations
|
||||
@get:Inject abstract val fs: FileSystemOperations
|
||||
|
||||
@TaskAction fun build() {
|
||||
val debVersion = "${version.get()}-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"))
|
||||
val root = temporaryDir.resolve("root")
|
||||
fs.delete { delete(root) }
|
||||
fs.copy { from(appImage); into(root.resolve("opt/sleepyowl")) }
|
||||
root.resolve("usr/bin").mkdirs()
|
||||
Files.createSymbolicLink(root.resolve("usr/bin/sleepyowl").toPath(), Path.of("../../opt/sleepyowl/bin/sleepyowl"))
|
||||
fs.copy { from(desktopFile); into(root.resolve("usr/share/applications")) }
|
||||
icons.get().asFile.listFiles()!!.forEach { png ->
|
||||
val size = Regex("sleepyowl-(\\d+)\\.png").matchEntire(png.name)?.groupValues?.get(1) ?: return@forEach
|
||||
fs.copy { from(png); into(root.resolve("usr/share/icons/hicolor/${size}x$size/apps")); rename { "sleepyowl.png" } }
|
||||
}
|
||||
// Debian's modes whatever the umask: 755 for directories and programs, 644 for the rest.
|
||||
root.walkTopDown().filter { !Files.isSymbolicLink(it.toPath()) }.forEach {
|
||||
Files.setPosixFilePermissions(it.toPath(), PosixFilePermissions.fromString(if (it.isDirectory || it.canExecute()) "rwxr-xr-x" else "rw-r--r--"))
|
||||
}
|
||||
val kb = root.walkTopDown().filter { it.isFile }.sumOf { it.length() } / 1024
|
||||
root.resolve("DEBIAN").mkdirs()
|
||||
root.resolve("DEBIAN/control").writeText(
|
||||
"""
|
||||
Package: sleepyowl
|
||||
Version: $debVersion
|
||||
Architecture: amd64
|
||||
Maintainer: ${maintainer.get()}
|
||||
Installed-Size: $kb
|
||||
Depends: ${depends.get().joinToString(", ")}
|
||||
Section: video
|
||||
Priority: optional
|
||||
Description: Jellyfin client for movies and series
|
||||
SleepyOwl plays movies and series from a Jellyfin server with mpv, in a window, full screen or in a
|
||||
small always-on-top window.
|
||||
""".trimIndent() + "\n",
|
||||
)
|
||||
val out = outDir.get().asFile.apply { listFiles()?.forEach { it.delete() } }.resolve("sleepyowl_${debVersion}_amd64.deb")
|
||||
exec.exec { commandLine("dpkg-deb", "--root-owner-group", "-Zxz", "--build", root, out) }
|
||||
logger.lifecycle("Built $out")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<PackageDeb>("packageDeb") {
|
||||
group = "distribution"
|
||||
dependsOn("createDistributable")
|
||||
appImage = layout.buildDirectory.dir("compose/binaries/main/app/sleepyowl")
|
||||
desktopFile = layout.projectDirectory.file("packaging/sleepyowl.desktop")
|
||||
icons = layout.projectDirectory.dir("src/main/resources/icons")
|
||||
version = appVersion
|
||||
maintainer = "StenFredd <d.v.bakaev@yandex.ru>"
|
||||
// libmpv2, libEGL and libOpenGL are loaded through JNA, so nothing finds them on its own; the rest is what dpkg-shlibdeps gave
|
||||
// for the JRE's, Skiko's and JNA's libraries in L10 (Mint 22 / Ubuntu 24.04 names).
|
||||
depends = listOf(
|
||||
"libmpv2 (>= 0.37)", "libegl1", "libopengl0", "libgl1", "libc6 (>= 2.34)", "libstdc++6", "libfontconfig1 (>= 2.12.6)",
|
||||
"libx11-6", "libxext6", "libxi6", "libxrender1", "libxtst6", "libasound2t64 | libasound2",
|
||||
)
|
||||
outDir = layout.buildDirectory.dir("deb")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user