Files
SleppyOwl/desktop/build.gradle.kts
StenFredd 3ef78bd49c
All checks were successful
release / release (push) Successful in 12m40s
Add CI
2026-10-27 17:38:55 +03:00

161 lines
8.0 KiB
Kotlin
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SleepyOwl for the Linux desktop (docs/desktop.md): windows, desktop screens, 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.
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 (gradle.properties, or `-Psleepyowl.versionName=…` from CI): the About screen, the log, the .deb. */
val appVersion = providers.gradleProperty("sleepyowl.versionName").get()
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"))
implementation(compose.desktop.currentOs)
implementation(libs.koin.core)
implementation(libs.koin.compose.viewmodel)
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.jna)
implementation(libs.dbus.java.core)
implementation(libs.dbus.java.unixsocket)
testImplementation(libs.junit)
testImplementation(libs.koin.test)
}
// XToolkit: the windows' WM_CLASS (sun.awt.X11.XToolkit.awtAppClassName), set before the first window.
val awtOpens = listOf("--add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED", "--add-opens=java.desktop/sun.awt=ALL-UNNAMED")
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.
tasks.register("desktopClasspath") {
val cp = sourceSets.main.get().runtimeClasspath
val out = layout.buildDirectory.file("desktop.classpath")
inputs.files(cp)
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")
}