* Add desktop release CI and support for Arch Linux packaging * Make Gradle wrapper executable in desktop-release workflow * Make Gradle wrapper executable in desktop-release workflow * Make Gradle wrapper executable in desktop-release workflow * Configure Gradle and update Java environment in desktop-release workflow * Update Java setup and AUR packaging in desktop release workflow * Update Java setup and AUR packaging in desktop release workflow * Add MSIX packaging support for Windows desktop distribution * Update AUR packaging metadata and validation * Use spine toc attribute for NCX resolution * crash fixes * Implement automatic discovery and injection of EPUB font face siblings * Enhance custom font support with family grouping and variable font handling * Optimize metadata loading and improve TTS highlighting * Add keyboard navigation support for EPUB reader * Refine PDF spread page sizing to respect aspect ratios * Implement responsive maximum height for reader popups and sheets * Handle TTS generation failures by skipping problematic chunks * Refactor PDF tile rendering logic and zoom indicator behavior * Prefer block and offset locators over page index in native vertical flow * Implement save and share actions for original book files * Add Estonian language support * Implement temporary viewing mode for external files * Implement direct opening for temporary external files without library persistence * fix failing tests * Import SharedFileCapabilities in DesktopLibraryUi * Improve native vertical reader progress, persistence, and image support * Center target in viewport for native vertical reader and support animated scrolling
1621 lines
60 KiB
Kotlin
1621 lines
60 KiB
Kotlin
import org.gradle.api.GradleException
|
|
import org.gradle.api.DefaultTask
|
|
import org.gradle.api.file.DirectoryProperty
|
|
import org.gradle.api.file.RegularFileProperty
|
|
import org.gradle.api.provider.ListProperty
|
|
import org.gradle.api.provider.MapProperty
|
|
import org.gradle.api.provider.Property
|
|
import org.gradle.api.tasks.JavaExec
|
|
import org.gradle.api.tasks.Exec
|
|
import org.gradle.api.tasks.Input
|
|
import org.gradle.api.tasks.InputDirectory
|
|
import org.gradle.api.tasks.InputFile
|
|
import org.gradle.api.tasks.OutputDirectory
|
|
import org.gradle.api.tasks.OutputFile
|
|
import org.gradle.api.tasks.PathSensitive
|
|
import org.gradle.api.tasks.PathSensitivity
|
|
import org.gradle.api.tasks.Sync
|
|
import org.gradle.api.tasks.TaskAction
|
|
import org.gradle.api.tasks.bundling.Compression
|
|
import org.gradle.api.tasks.bundling.Tar
|
|
import org.gradle.jvm.tasks.Jar
|
|
import org.gradle.process.ExecOperations
|
|
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
|
import org.gradle.work.DisableCachingByDefault
|
|
import java.io.File
|
|
import java.security.MessageDigest
|
|
import java.awt.RenderingHints
|
|
import java.awt.image.BufferedImage
|
|
import java.nio.file.AtomicMoveNotSupportedException
|
|
import java.nio.file.Files
|
|
import java.nio.file.StandardCopyOption
|
|
import java.util.Properties
|
|
import java.util.zip.ZipEntry
|
|
import java.util.zip.ZipFile
|
|
import java.util.zip.ZipOutputStream
|
|
import javax.imageio.ImageIO
|
|
import javax.inject.Inject
|
|
|
|
plugins {
|
|
alias(libs.plugins.kotlin.multiplatform)
|
|
alias(libs.plugins.kotlin.compose)
|
|
alias(libs.plugins.compose.multiplatform)
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Verification task has no outputs.")
|
|
abstract class CheckBundledPdfiumRuntimeTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val bundleRootPath: Property<String>
|
|
|
|
@get:Input
|
|
abstract val libraryPath: Property<String>
|
|
|
|
@TaskAction
|
|
fun checkRuntime() {
|
|
val bundleRoot = File(bundleRootPath.get())
|
|
val library = bundleRoot.resolve(libraryPath.get())
|
|
if (!library.isFile) {
|
|
throw GradleException(
|
|
"Missing bundled Pdfium runtime at ${library.absolutePath}. " +
|
|
"Expected ${libraryPath.get()} inside ${bundleRoot.absolutePath}."
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Renames package output produced by jpackage.")
|
|
abstract class RenameDesktopMsiOutputTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val msiDirectoryPath: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageVersion: Property<String>
|
|
|
|
@get:Input
|
|
abstract val architecture: Property<String>
|
|
|
|
@TaskAction
|
|
fun renameOutput() {
|
|
val msiDirectory = File(msiDirectoryPath.get())
|
|
val outputPackageName = packageName.get()
|
|
val outputPackageVersion = packageVersion.get()
|
|
val source = msiDirectory.resolve("$outputPackageName-$outputPackageVersion.msi")
|
|
if (!source.isFile) return
|
|
|
|
val target = msiDirectory.resolve("$outputPackageName-$outputPackageVersion-${architecture.get()}.msi")
|
|
if (target.exists() && !target.delete()) {
|
|
throw GradleException("Could not replace existing MSI at ${target.absolutePath}.")
|
|
}
|
|
if (!source.renameTo(target)) {
|
|
throw GradleException("Could not rename MSI from ${source.absolutePath} to ${target.absolutePath}.")
|
|
}
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Generates an MSIX manifest from package metadata.")
|
|
abstract class GenerateDesktopMsixManifestTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val identityName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val publisher: Property<String>
|
|
|
|
@get:Input
|
|
abstract val publisherDisplayName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageDescription: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageVersion: Property<String>
|
|
|
|
@get:Input
|
|
abstract val architecture: Property<String>
|
|
|
|
@get:Input
|
|
abstract val executablePath: Property<String>
|
|
|
|
@get:OutputFile
|
|
abstract val outputFile: RegularFileProperty
|
|
|
|
@TaskAction
|
|
fun generate() {
|
|
fun xmlEscaped(value: String): String {
|
|
return value.replace("&", "&")
|
|
.replace("\"", """)
|
|
.replace("'", "'")
|
|
.replace("<", "<")
|
|
.replace(">", ">")
|
|
}
|
|
|
|
val file = outputFile.get().asFile
|
|
file.parentFile.mkdirs()
|
|
file.writeText(
|
|
"""
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<Package
|
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
|
IgnorableNamespaces="uap rescap">
|
|
<Identity
|
|
Name="${xmlEscaped(identityName.get())}"
|
|
Publisher="${xmlEscaped(publisher.get())}"
|
|
Version="${xmlEscaped(packageVersion.get())}"
|
|
ProcessorArchitecture="${xmlEscaped(architecture.get())}" />
|
|
<Properties>
|
|
<DisplayName>${xmlEscaped(packageName.get())}</DisplayName>
|
|
<PublisherDisplayName>${xmlEscaped(publisherDisplayName.get())}</PublisherDisplayName>
|
|
<Logo>Assets\StoreLogo.png</Logo>
|
|
</Properties>
|
|
<Resources>
|
|
<Resource Language="en-us" />
|
|
</Resources>
|
|
<Dependencies>
|
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.22621.0" />
|
|
</Dependencies>
|
|
<Applications>
|
|
<Application Id="Episteme" Executable="${xmlEscaped(executablePath.get())}" EntryPoint="Windows.FullTrustApplication">
|
|
<uap:VisualElements
|
|
DisplayName="${xmlEscaped(packageName.get())}"
|
|
Description="${xmlEscaped(packageDescription.get())}"
|
|
BackgroundColor="transparent"
|
|
Square44x44Logo="Assets\Square44x44Logo.png"
|
|
Square150x150Logo="Assets\Square150x150Logo.png" />
|
|
</Application>
|
|
</Applications>
|
|
<Capabilities>
|
|
<rescap:Capability Name="runFullTrust" />
|
|
</Capabilities>
|
|
</Package>
|
|
""".trimIndent() + "\n",
|
|
Charsets.UTF_8
|
|
)
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Generates fixed-size MSIX logo assets from the desktop icon.")
|
|
abstract class GenerateDesktopMsixAssetsTask : DefaultTask() {
|
|
@get:InputFile
|
|
@get:PathSensitive(PathSensitivity.NONE)
|
|
abstract val sourceIconFile: RegularFileProperty
|
|
|
|
@get:OutputDirectory
|
|
abstract val outputDirectory: DirectoryProperty
|
|
|
|
@TaskAction
|
|
fun generate() {
|
|
val source = ImageIO.read(sourceIconFile.get().asFile)
|
|
?: throw GradleException("Could not read MSIX source icon ${sourceIconFile.get().asFile.absolutePath}.")
|
|
val output = outputDirectory.get().asFile
|
|
output.mkdirs()
|
|
writePng(source, output.resolve("Square44x44Logo.png"), 44)
|
|
writePng(source, output.resolve("Square150x150Logo.png"), 150)
|
|
writePng(source, output.resolve("StoreLogo.png"), 50)
|
|
}
|
|
|
|
private fun writePng(source: BufferedImage, target: File, size: Int) {
|
|
val image = BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB)
|
|
val graphics = image.createGraphics()
|
|
try {
|
|
graphics.setRenderingHint(
|
|
RenderingHints.KEY_INTERPOLATION,
|
|
RenderingHints.VALUE_INTERPOLATION_BICUBIC
|
|
)
|
|
graphics.setRenderingHint(
|
|
RenderingHints.KEY_RENDERING,
|
|
RenderingHints.VALUE_RENDER_QUALITY
|
|
)
|
|
graphics.drawImage(source, 0, 0, size, size, null)
|
|
} finally {
|
|
graphics.dispose()
|
|
}
|
|
ImageIO.write(image, "png", target)
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Packages the staged MSIX app image with Windows SDK makeappx.")
|
|
abstract class PackageDesktopMsixTask @Inject constructor(
|
|
private val execOperations: ExecOperations
|
|
) : DefaultTask() {
|
|
@get:InputDirectory
|
|
@get:PathSensitive(PathSensitivity.RELATIVE)
|
|
abstract val packageRootDirectory: DirectoryProperty
|
|
|
|
@get:OutputFile
|
|
abstract val outputFile: RegularFileProperty
|
|
|
|
@get:Input
|
|
abstract val makeAppxPath: Property<String>
|
|
|
|
@get:Input
|
|
abstract val hostOsId: Property<String>
|
|
|
|
@get:Input
|
|
abstract val hostArchId: Property<String>
|
|
|
|
@TaskAction
|
|
fun packageMsix() {
|
|
if (hostOsId.get() != "windows" || hostArchId.get() != "x64") {
|
|
throw GradleException(
|
|
"MSIX packaging requires a Windows x64 packaging host. " +
|
|
"Current host: ${hostOsId.get()} ${hostArchId.get()}."
|
|
)
|
|
}
|
|
|
|
val makeAppx = File(makeAppxPath.get())
|
|
if (!makeAppx.isFile) {
|
|
throw GradleException(
|
|
"Windows SDK makeappx.exe was not found at ${makeAppx.absolutePath}. " +
|
|
"Install the Windows SDK MSIX packaging tools or set " +
|
|
"-PdesktopMakeAppxPath=<path-to-makeappx.exe>."
|
|
)
|
|
}
|
|
|
|
val output = outputFile.get().asFile
|
|
output.parentFile.mkdirs()
|
|
if (output.exists() && !output.delete()) {
|
|
throw GradleException("Could not replace existing MSIX at ${output.absolutePath}.")
|
|
}
|
|
|
|
execOperations.exec {
|
|
executable = makeAppx.absolutePath
|
|
args(
|
|
"pack",
|
|
"/d",
|
|
packageRootDirectory.get().asFile.absolutePath,
|
|
"/p",
|
|
output.absolutePath,
|
|
"/o"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Signs the MSIX package with Windows SDK signtool.")
|
|
abstract class SignDesktopMsixTask @Inject constructor(
|
|
private val execOperations: ExecOperations
|
|
) : DefaultTask() {
|
|
@get:InputFile
|
|
@get:PathSensitive(PathSensitivity.NONE)
|
|
abstract val unsignedMsixFile: RegularFileProperty
|
|
|
|
@get:InputFile
|
|
@get:PathSensitive(PathSensitivity.NONE)
|
|
abstract val certificateFile: RegularFileProperty
|
|
|
|
@get:Input
|
|
abstract val signToolPath: Property<String>
|
|
|
|
@get:Input
|
|
abstract val certificatePassword: Property<String>
|
|
|
|
@get:Input
|
|
abstract val timestampUrl: Property<String>
|
|
|
|
@TaskAction
|
|
fun signMsix() {
|
|
val signTool = File(signToolPath.get())
|
|
if (!signTool.isFile) {
|
|
throw GradleException(
|
|
"Windows SDK signtool.exe was not found at ${signTool.absolutePath}. " +
|
|
"Install the Windows SDK or set -PdesktopSignToolPath=<path-to-signtool.exe>."
|
|
)
|
|
}
|
|
|
|
val signArgs = mutableListOf(
|
|
"sign",
|
|
"/fd",
|
|
"SHA256",
|
|
"/f",
|
|
certificateFile.get().asFile.absolutePath
|
|
)
|
|
val password = certificatePassword.get().trim()
|
|
if (password.isNotEmpty()) {
|
|
signArgs += listOf("/p", password)
|
|
}
|
|
val timestamp = timestampUrl.get().trim()
|
|
if (timestamp.isNotEmpty()) {
|
|
signArgs += listOf("/tr", timestamp, "/td", "SHA256")
|
|
}
|
|
signArgs += unsignedMsixFile.get().asFile.absolutePath
|
|
|
|
execOperations.exec {
|
|
executable = signTool.absolutePath
|
|
args(signArgs)
|
|
}
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Generates local desktop service config for native packages.")
|
|
abstract class GenerateDesktopCloudConfigTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val configValues: MapProperty<String, String>
|
|
|
|
@get:OutputFile
|
|
abstract val outputFile: RegularFileProperty
|
|
|
|
@TaskAction
|
|
fun generate() {
|
|
val file = outputFile.get().asFile
|
|
file.parentFile.mkdirs()
|
|
file.writeText(
|
|
configValues.get().entries.joinToString(separator = "\n", postfix = "\n") { (key, value) ->
|
|
"$key=${value.replace("\\", "\\\\").replace("\n", "")}"
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Verification task has no outputs.")
|
|
abstract class VerifyDesktopNativePackagingTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val supportedHost: Property<Boolean>
|
|
|
|
@get:Input
|
|
abstract val hostOsId: Property<String>
|
|
|
|
@get:Input
|
|
abstract val hostArchId: Property<String>
|
|
|
|
@get:Input
|
|
abstract val missingStandardServiceConfig: ListProperty<String>
|
|
|
|
@TaskAction
|
|
fun verify() {
|
|
if (!supportedHost.get()) {
|
|
throw GradleException(
|
|
"Desktop native packaging is currently release-supported only on Windows x64 and Linux x64. " +
|
|
"Current host: ${hostOsId.get()} ${hostArchId.get()}."
|
|
)
|
|
}
|
|
val missing = missingStandardServiceConfig.get()
|
|
if (missing.isNotEmpty()) {
|
|
throw GradleException(
|
|
"Standard desktop packages require account/sync service config. Missing: " +
|
|
missing.joinToString(", ") + ". " +
|
|
"Set DESKTOP_FIREBASE_WEB_API_KEY and DESKTOP_GOOGLE_OAUTH_CLIENT_ID, " +
|
|
"use -PdesktopFlavor=oss for the offline build, or set " +
|
|
"-PdesktopAllowUnconfiguredStandardServices=true for a local non-GA package."
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Generates AUR package metadata from the local Linux distributable.")
|
|
abstract class PrepareDesktopAurPackageTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val aurPackageName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val providedPackageName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageVersion: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageRelease: Property<String>
|
|
|
|
@get:Input
|
|
abstract val packageDescription: Property<String>
|
|
|
|
@get:Input
|
|
abstract val appDisplayName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val installDirectoryName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val launcherName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val executableName: Property<String>
|
|
|
|
@get:Input
|
|
abstract val sourceUrl: Property<String>
|
|
|
|
@get:Input
|
|
abstract val projectUrl: Property<String>
|
|
|
|
@get:InputFile
|
|
@get:PathSensitive(PathSensitivity.NONE)
|
|
abstract val linuxTarFile: RegularFileProperty
|
|
|
|
@get:OutputDirectory
|
|
abstract val outputDirectory: DirectoryProperty
|
|
|
|
@TaskAction
|
|
fun prepare() {
|
|
val output = outputDirectory.get().asFile
|
|
val sourceTar = linuxTarFile.get().asFile
|
|
if (!sourceTar.isFile) {
|
|
throw GradleException("Missing Linux tarball for AUR packaging: ${sourceTar.absolutePath}")
|
|
}
|
|
|
|
output.deleteRecursively()
|
|
output.mkdirs()
|
|
|
|
val stagedTar = output.resolve(sourceTar.name)
|
|
sourceTar.copyTo(stagedTar, overwrite = true)
|
|
val sha256 = stagedTar.sha256()
|
|
val configuredSourceUrl = sourceUrl.get().trim()
|
|
val sourceEntry = if (configuredSourceUrl.isBlank()) {
|
|
stagedTar.name
|
|
} else {
|
|
"${stagedTar.name}::$configuredSourceUrl"
|
|
}
|
|
|
|
output.resolve("PKGBUILD").writeText(
|
|
aurPkgbuild(
|
|
pkgname = aurPackageName.get(),
|
|
providedPackage = providedPackageName.get(),
|
|
pkgver = packageVersion.get(),
|
|
pkgrel = packageRelease.get(),
|
|
pkgdesc = packageDescription.get(),
|
|
appName = appDisplayName.get(),
|
|
installDir = installDirectoryName.get(),
|
|
launcher = launcherName.get(),
|
|
executable = executableName.get(),
|
|
source = sourceEntry,
|
|
sha256 = sha256,
|
|
projectUrl = projectUrl.get()
|
|
)
|
|
)
|
|
output.resolve(".SRCINFO").writeText(
|
|
aurSrcInfo(
|
|
pkgname = aurPackageName.get(),
|
|
providedPackage = providedPackageName.get(),
|
|
pkgver = packageVersion.get(),
|
|
pkgrel = packageRelease.get(),
|
|
pkgdesc = packageDescription.get(),
|
|
source = sourceEntry,
|
|
sha256 = sha256,
|
|
projectUrl = projectUrl.get()
|
|
)
|
|
)
|
|
}
|
|
|
|
private fun File.sha256(): String {
|
|
val digest = MessageDigest.getInstance("SHA-256")
|
|
inputStream().use { input ->
|
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
|
while (true) {
|
|
val read = input.read(buffer)
|
|
if (read < 0) break
|
|
digest.update(buffer, 0, read)
|
|
}
|
|
}
|
|
return digest.digest().joinToString("") { "%02x".format(it) }
|
|
}
|
|
|
|
private fun shellSingleQuoted(value: String): String {
|
|
return "'" + value.replace("'", "'\"'\"'") + "'"
|
|
}
|
|
|
|
private fun archRuntimeDependencies(): List<String> {
|
|
return listOf(
|
|
"alsa-lib",
|
|
"atk",
|
|
"cairo",
|
|
"dbus",
|
|
"expat",
|
|
"fontconfig",
|
|
"freetype2",
|
|
"gcc-libs",
|
|
"gdk-pixbuf2",
|
|
"glib2",
|
|
"glibc",
|
|
"gtk3",
|
|
"libcups",
|
|
"libarchive",
|
|
"libsecret",
|
|
"libx11",
|
|
"libxcomposite",
|
|
"libxdamage",
|
|
"libxext",
|
|
"libxi",
|
|
"libxrandr",
|
|
"libxrender",
|
|
"libxtst",
|
|
"nss",
|
|
"pango",
|
|
"zlib"
|
|
)
|
|
}
|
|
|
|
private fun aurPkgbuild(
|
|
pkgname: String,
|
|
providedPackage: String,
|
|
pkgver: String,
|
|
pkgrel: String,
|
|
pkgdesc: String,
|
|
appName: String,
|
|
installDir: String,
|
|
launcher: String,
|
|
executable: String,
|
|
source: String,
|
|
sha256: String,
|
|
projectUrl: String
|
|
): String {
|
|
val desktopFile = "$providedPackage.desktop"
|
|
val iconName = providedPackage
|
|
val depends = archRuntimeDependencies()
|
|
val mimeTypes = archDesktopMimeTypes()
|
|
return """
|
|
pkgname=${shellSingleQuoted(pkgname)}
|
|
pkgver=${shellSingleQuoted(pkgver)}
|
|
pkgrel=${shellSingleQuoted(pkgrel)}
|
|
pkgdesc=${shellSingleQuoted(pkgdesc)}
|
|
arch=('x86_64')
|
|
url=${shellSingleQuoted(projectUrl)}
|
|
license=('AGPL-3.0-only')
|
|
depends=(${depends.joinToString(" ") { shellSingleQuoted(it) }})
|
|
provides=(${shellSingleQuoted(providedPackage)})
|
|
conflicts=(${shellSingleQuoted(providedPackage)})
|
|
source=(${shellSingleQuoted(source)})
|
|
sha256sums=(${shellSingleQuoted(sha256)})
|
|
options=('!debug')
|
|
|
|
package() {
|
|
install -dm755 "${'$'}pkgdir/opt/$installDir"
|
|
cp -a "$installDir/." "${'$'}pkgdir/opt/$installDir/"
|
|
chmod 755 "${'$'}pkgdir/opt/$installDir/bin/$executable"
|
|
|
|
install -dm755 "${'$'}pkgdir/usr/bin"
|
|
ln -sf "/opt/$installDir/bin/$executable" "${'$'}pkgdir/usr/bin/$launcher"
|
|
|
|
install -Dm644 "${'$'}pkgdir/opt/$installDir/share/licenses/LICENSE" "${'$'}pkgdir/usr/share/licenses/${'$'}pkgname/LICENSE"
|
|
|
|
local icon_path
|
|
icon_path="${'$'}(find "${'$'}pkgdir/opt/$installDir" -name 'episteme_icon.png' -print -quit)"
|
|
if [[ -n "${'$'}icon_path" ]]; then
|
|
install -Dm644 "${'$'}icon_path" "${'$'}pkgdir/usr/share/icons/hicolor/512x512/apps/$iconName.png"
|
|
install -Dm644 "${'$'}icon_path" "${'$'}pkgdir/usr/share/pixmaps/$iconName.png"
|
|
fi
|
|
|
|
install -Dm644 /dev/stdin "${'$'}pkgdir/usr/share/applications/$desktopFile" <<'EOF'
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=$appName
|
|
Comment=$pkgdesc
|
|
Exec=$launcher %F
|
|
Icon=$iconName
|
|
Terminal=false
|
|
Categories=Office;Viewer;
|
|
MimeType=${mimeTypes.joinToString(";")};
|
|
EOF
|
|
}
|
|
""".trimIndent() + "\n"
|
|
}
|
|
|
|
private fun aurSrcInfo(
|
|
pkgname: String,
|
|
providedPackage: String,
|
|
pkgver: String,
|
|
pkgrel: String,
|
|
pkgdesc: String,
|
|
source: String,
|
|
sha256: String,
|
|
projectUrl: String
|
|
): String {
|
|
val depends = archRuntimeDependencies()
|
|
return """
|
|
pkgbase = $pkgname
|
|
pkgdesc = $pkgdesc
|
|
pkgver = $pkgver
|
|
pkgrel = $pkgrel
|
|
url = $projectUrl
|
|
arch = x86_64
|
|
license = AGPL-3.0-only
|
|
${depends.joinToString("\n") { "\tdepends = $it" }}
|
|
provides = $providedPackage
|
|
conflicts = $providedPackage
|
|
source = $source
|
|
sha256sums = $sha256
|
|
|
|
pkgname = $pkgname
|
|
""".trimIndent() + "\n"
|
|
}
|
|
|
|
private fun archDesktopMimeTypes(): List<String> {
|
|
return listOf(
|
|
"application/pdf",
|
|
"application/epub+zip",
|
|
"application/x-mobipocket-ebook",
|
|
"application/vnd.amazon.ebook",
|
|
"application/vnd.amazon.mobi8-ebook",
|
|
"text/markdown",
|
|
"text/x-markdown",
|
|
"text/plain",
|
|
"text/html",
|
|
"application/xhtml+xml",
|
|
"application/x-fictionbook+xml",
|
|
"application/x-zip-compressed-fb2",
|
|
"application/zip",
|
|
"application/vnd.comicbook+zip",
|
|
"application/x-cbz",
|
|
"application/vnd.comicbook-rar",
|
|
"application/x-cbr",
|
|
"application/x-rar-compressed",
|
|
"application/x-cb7",
|
|
"application/x-7z-compressed",
|
|
"application/vnd.comicbook+tar",
|
|
"application/x-cbt",
|
|
"application/x-tar",
|
|
"application/tar",
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
"application/vnd.oasis.opendocument.text",
|
|
"application/x-vnd.oasis.opendocument.text-flat-xml"
|
|
)
|
|
}
|
|
}
|
|
|
|
@DisableCachingByDefault(because = "Strips stale jar signatures in-place after ProGuard rewrites signed dependencies.")
|
|
abstract class StripInvalidJarSignaturesTask : DefaultTask() {
|
|
@get:Input
|
|
abstract val jarDirectoryPath: Property<String>
|
|
|
|
@TaskAction
|
|
fun stripSignatures() {
|
|
val jarDirectory = File(jarDirectoryPath.get())
|
|
if (!jarDirectory.isDirectory) return
|
|
|
|
var strippedJarCount = 0
|
|
jarDirectory.walkTopDown()
|
|
.filter { it.isFile && it.extension.equals("jar", ignoreCase = true) }
|
|
.forEach { jar ->
|
|
val strippedEntries = stripInvalidJarSignatures(jar)
|
|
if (strippedEntries > 0) {
|
|
strippedJarCount += 1
|
|
logger.lifecycle("Stripped $strippedEntries stale jar signature entr${if (strippedEntries == 1) "y" else "ies"} from ${jar.name}")
|
|
}
|
|
}
|
|
|
|
if (strippedJarCount > 0) {
|
|
logger.lifecycle("Stripped stale jar signatures from $strippedJarCount ProGuard output jar${if (strippedJarCount == 1) "" else "s"}.")
|
|
}
|
|
}
|
|
|
|
private fun stripInvalidJarSignatures(jar: File): Int {
|
|
val temp = Files.createTempFile(jar.parentFile.toPath(), "${jar.nameWithoutExtension}-unsigned-", ".jar")
|
|
var strippedEntries = 0
|
|
|
|
ZipFile(jar).use { source ->
|
|
ZipOutputStream(Files.newOutputStream(temp)).use { target ->
|
|
val seenEntries = mutableSetOf<String>()
|
|
val entries = source.entries()
|
|
while (entries.hasMoreElements()) {
|
|
val sourceEntry = entries.nextElement()
|
|
val entryName = sourceEntry.name
|
|
if (!seenEntries.add(entryName)) continue
|
|
if (isJarSignatureResource(entryName)) {
|
|
strippedEntries += 1
|
|
continue
|
|
}
|
|
|
|
val targetEntry = ZipEntry(entryName)
|
|
if (sourceEntry.time >= 0) {
|
|
targetEntry.time = sourceEntry.time
|
|
}
|
|
target.putNextEntry(targetEntry)
|
|
if (!sourceEntry.isDirectory) {
|
|
source.getInputStream(sourceEntry).use { input ->
|
|
input.copyTo(target)
|
|
}
|
|
}
|
|
target.closeEntry()
|
|
}
|
|
}
|
|
}
|
|
|
|
if (strippedEntries > 0) {
|
|
try {
|
|
Files.move(temp, jar.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)
|
|
} catch (_: AtomicMoveNotSupportedException) {
|
|
Files.move(temp, jar.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
|
}
|
|
} else {
|
|
Files.deleteIfExists(temp)
|
|
}
|
|
|
|
return strippedEntries
|
|
}
|
|
|
|
private fun isJarSignatureResource(entryName: String): Boolean {
|
|
val normalized = entryName.replace('\\', '/').uppercase()
|
|
if (!normalized.startsWith("META-INF/")) return false
|
|
|
|
val metaInfName = normalized.removePrefix("META-INF/")
|
|
if (metaInfName.contains("/")) return false
|
|
|
|
return metaInfName.startsWith("SIG-") ||
|
|
metaInfName.endsWith(".SF") ||
|
|
metaInfName.endsWith(".DSA") ||
|
|
metaInfName.endsWith(".RSA") ||
|
|
metaInfName.endsWith(".EC")
|
|
}
|
|
}
|
|
|
|
fun desktopOsId(osName: String = System.getProperty("os.name")): String {
|
|
val normalized = osName.lowercase()
|
|
return when {
|
|
normalized.startsWith("windows") -> "windows"
|
|
normalized == "linux" || normalized.contains("linux") -> "linux"
|
|
normalized.startsWith("mac") || normalized.contains("darwin") -> "macos"
|
|
else -> "other"
|
|
}
|
|
}
|
|
|
|
fun desktopArchId(osArch: String = System.getProperty("os.arch")): String {
|
|
return when (osArch.lowercase()) {
|
|
"amd64", "x86_64", "x64" -> "x64"
|
|
"aarch64", "arm64" -> "arm64"
|
|
"x86", "i386", "i686" -> "x86"
|
|
else -> "unknown"
|
|
}
|
|
}
|
|
|
|
fun desktopSwtArtifactId(
|
|
osName: String = System.getProperty("os.name"),
|
|
osArch: String = System.getProperty("os.arch")
|
|
): String? {
|
|
return when (desktopOsId(osName)) {
|
|
"windows" -> when (desktopArchId(osArch)) {
|
|
"arm64" -> "org.eclipse.swt.win32.win32.aarch64"
|
|
else -> "org.eclipse.swt.win32.win32.x86_64"
|
|
}
|
|
|
|
"linux" -> when (desktopArchId(osArch)) {
|
|
"arm64" -> "org.eclipse.swt.gtk.linux.aarch64"
|
|
else -> "org.eclipse.swt.gtk.linux.x86_64"
|
|
}
|
|
|
|
"macos" -> when (desktopArchId(osArch)) {
|
|
"arm64" -> "org.eclipse.swt.cocoa.macosx.aarch64"
|
|
else -> "org.eclipse.swt.cocoa.macosx.x86_64"
|
|
}
|
|
|
|
else -> null
|
|
}
|
|
}
|
|
|
|
fun desktopPdfiumDirectoryName(
|
|
osName: String = System.getProperty("os.name"),
|
|
osArch: String = System.getProperty("os.arch")
|
|
): String {
|
|
return when (desktopOsId(osName)) {
|
|
"windows" -> "win-${desktopArchId(osArch)}-v8"
|
|
"linux" -> "linux-${desktopArchId(osArch)}-v8"
|
|
"macos" -> "mac-${desktopArchId(osArch)}-v8"
|
|
else -> "${desktopArchId(osArch)}-v8"
|
|
}
|
|
}
|
|
|
|
fun desktopPdfiumLibraryPath(
|
|
osName: String = System.getProperty("os.name"),
|
|
osArch: String = System.getProperty("os.arch")
|
|
): String {
|
|
return when (desktopOsId(osName)) {
|
|
"windows" -> "bin/pdfium.dll"
|
|
"linux" -> "lib/libpdfium.so"
|
|
"macos" -> "lib/libpdfium.dylib"
|
|
else -> "lib/pdfium"
|
|
}
|
|
}
|
|
|
|
fun desktopJdkToolName(toolName: String, osName: String = System.getProperty("os.name")): String {
|
|
return if (desktopOsId(osName) == "windows") "$toolName.exe" else toolName
|
|
}
|
|
|
|
fun File.asDesktopJdkHome(): File {
|
|
val absolute = absoluteFile
|
|
return when {
|
|
absolute.isFile && absolute.parentFile?.name?.equals("bin", ignoreCase = true) == true ->
|
|
absolute.parentFile?.parentFile ?: absolute
|
|
|
|
absolute.isDirectory && absolute.name.equals("bin", ignoreCase = true) ->
|
|
absolute.parentFile ?: absolute
|
|
|
|
absolute.resolve("Contents/Home/bin").isDirectory ->
|
|
absolute.resolve("Contents/Home")
|
|
|
|
else -> absolute
|
|
}
|
|
}
|
|
|
|
fun File.desktopJdkTool(toolName: String, osName: String = System.getProperty("os.name")): File {
|
|
return resolve("bin/${desktopJdkToolName(toolName, osName)}")
|
|
}
|
|
|
|
fun File.isDesktopPackagingJdk(osName: String = System.getProperty("os.name")): Boolean {
|
|
return desktopJdkTool("java", osName).isFile &&
|
|
desktopJdkTool("jlink", osName).isFile &&
|
|
desktopJdkTool("jpackage", osName).isFile
|
|
}
|
|
|
|
fun File.desktopJdkMajorVersion(): Int? {
|
|
val releaseFile = resolve("release")
|
|
if (!releaseFile.isFile) return null
|
|
|
|
val version = runCatching {
|
|
releaseFile.useLines { lines ->
|
|
lines.firstOrNull { it.startsWith("JAVA_VERSION=") }
|
|
?.substringAfter("=")
|
|
?.trim()
|
|
?.trim('"')
|
|
}
|
|
}.getOrNull() ?: return null
|
|
|
|
return version.removePrefix("1.").substringBefore(".").toIntOrNull()
|
|
}
|
|
|
|
fun safeChildDirectories(root: File): List<File> {
|
|
return runCatching {
|
|
root.listFiles()?.filter { it.isDirectory }.orEmpty()
|
|
}.getOrDefault(emptyList())
|
|
}
|
|
|
|
fun stableDesktopJdkCandidates(candidates: List<File>, preferredMajorVersion: Int = 21): List<File> {
|
|
return candidates
|
|
.map { it.asDesktopJdkHome() }
|
|
.distinctBy { runCatching { it.canonicalPath }.getOrElse { _ -> it.absolutePath }.lowercase() }
|
|
.sortedWith(
|
|
compareBy<File> { if (it.desktopJdkMajorVersion() == preferredMajorVersion) 0 else 1 }
|
|
.thenBy { it.desktopJdkMajorVersion() ?: Int.MAX_VALUE }
|
|
.thenBy { it.absolutePath.lowercase() }
|
|
)
|
|
}
|
|
|
|
fun desktopPathJdkCandidates(osName: String = System.getProperty("os.name")): List<File> {
|
|
return System.getenv("PATH")
|
|
?.split(File.pathSeparator)
|
|
.orEmpty()
|
|
.asSequence()
|
|
.map { it.trim() }
|
|
.filter { it.isNotEmpty() }
|
|
.map { File(it).resolve(desktopJdkToolName("jpackage", osName)) }
|
|
.filter { it.isFile }
|
|
.mapNotNull { it.parentFile?.parentFile }
|
|
.toList()
|
|
}
|
|
|
|
fun desktopGradleJdkCandidates(): List<File> {
|
|
val userHome = System.getProperty("user.home")?.let(::File) ?: return emptyList()
|
|
return stableDesktopJdkCandidates(safeChildDirectories(userHome.resolve(".gradle/jdks")))
|
|
}
|
|
|
|
fun desktopPlatformJdkCandidates(osName: String = System.getProperty("os.name")): List<File> {
|
|
val roots = when (desktopOsId(osName)) {
|
|
"windows" -> listOfNotNull(
|
|
System.getenv("ProgramFiles")?.let { File(it, "Java") },
|
|
System.getenv("ProgramFiles")?.let { File(it, "Eclipse Adoptium") },
|
|
System.getenv("ProgramFiles")?.let { File(it, "Microsoft") },
|
|
System.getenv("ProgramFiles(x86)")?.let { File(it, "Java") }
|
|
)
|
|
|
|
"linux" -> listOf(
|
|
File("/usr/lib/jvm"),
|
|
File("/usr/java"),
|
|
File("/opt/java"),
|
|
File("/opt/jdk")
|
|
)
|
|
|
|
"macos" -> listOfNotNull(
|
|
File("/Library/Java/JavaVirtualMachines"),
|
|
System.getProperty("user.home")?.let { File(it, "Library/Java/JavaVirtualMachines") }
|
|
)
|
|
|
|
else -> emptyList()
|
|
}
|
|
|
|
return stableDesktopJdkCandidates(roots + roots.flatMap(::safeChildDirectories))
|
|
}
|
|
|
|
fun findDesktopPackagingJavaHome(
|
|
explicitCandidates: List<String>,
|
|
implicitCandidates: List<File>,
|
|
osName: String = System.getProperty("os.name")
|
|
): File? {
|
|
val explicitJavaHome = explicitCandidates.firstOrNull { it.isNotBlank() }
|
|
if (explicitJavaHome != null) {
|
|
val candidate = File(explicitJavaHome).asDesktopJdkHome()
|
|
if (!candidate.isDesktopPackagingJdk(osName)) {
|
|
throw GradleException(
|
|
"Desktop packaging JDK must include java, jlink, and jpackage under " +
|
|
"${candidate.resolve("bin").absolutePath}. " +
|
|
"Set -PdesktopPackagingJavaHome=<jdk-home> or DESKTOP_PACKAGING_JAVA_HOME to a full JDK."
|
|
)
|
|
}
|
|
return candidate
|
|
}
|
|
|
|
return implicitCandidates
|
|
.map { it.asDesktopJdkHome() }
|
|
.distinctBy { runCatching { it.canonicalPath }.getOrElse { _ -> it.absolutePath }.lowercase() }
|
|
.firstOrNull { it.isDesktopPackagingJdk(osName) }
|
|
}
|
|
|
|
fun normalizeDesktopPackageVersion(rawVersion: String): String {
|
|
val coreVersion = rawVersion.trim()
|
|
.substringBefore("-")
|
|
.substringBefore("+")
|
|
.takeIf { it.isNotBlank() }
|
|
?: "1.0.0"
|
|
val parts = coreVersion.split(".")
|
|
val numericParts = parts.map { it.toIntOrNull() }
|
|
val normalizedParts = when {
|
|
parts.size in 1..3 && numericParts.all { it != null } ->
|
|
numericParts.map { it ?: 0 } + List(3 - parts.size) { 0 }
|
|
|
|
else -> throw GradleException(
|
|
"desktopPackageVersion must be numeric MAJOR[.MINOR[.BUILD]], but was '$rawVersion'."
|
|
)
|
|
}
|
|
val (major, minor, build) = normalizedParts
|
|
if (major !in 0..255 || minor !in 0..255 || build !in 0..65535) {
|
|
throw GradleException(
|
|
"desktopPackageVersion '$rawVersion' is outside the Windows package version range. " +
|
|
"Expected MAJOR 0..255, MINOR 0..255, BUILD 0..65535."
|
|
)
|
|
}
|
|
return "$major.$minor.$build"
|
|
}
|
|
|
|
fun normalizeDesktopVersionName(rawVersion: String): String {
|
|
return rawVersion.trim().takeIf { it.isNotBlank() } ?: "1.0.0"
|
|
}
|
|
|
|
fun normalizeDesktopFlavor(rawFlavor: String): String {
|
|
val flavor = rawFlavor.trim().lowercase()
|
|
return when (flavor) {
|
|
"oss", "oss-offline", "episteme-oss" -> "oss-offline"
|
|
else -> "standard"
|
|
}
|
|
}
|
|
|
|
fun normalizeDesktopPackageArchitecture(osArch: String): String {
|
|
val normalizedArch = desktopArchId(osArch)
|
|
return if (normalizedArch != "unknown") {
|
|
normalizedArch
|
|
} else {
|
|
osArch.lowercase()
|
|
.replace(Regex("[^a-z0-9]+"), "-")
|
|
.trim('-')
|
|
.ifBlank { "unknown" }
|
|
}
|
|
}
|
|
|
|
fun desktopDefaultPackageFormats(osName: String = System.getProperty("os.name")): String {
|
|
return when (desktopOsId(osName)) {
|
|
"windows" -> "msi"
|
|
"linux" -> "deb,rpm"
|
|
"macos" -> "dmg"
|
|
else -> ""
|
|
}
|
|
}
|
|
|
|
fun desktopTargetFormatForId(format: String): TargetFormat {
|
|
return when (format.lowercase()) {
|
|
"exe" -> TargetFormat.Exe
|
|
"msi" -> TargetFormat.Msi
|
|
"deb" -> TargetFormat.Deb
|
|
"rpm" -> TargetFormat.Rpm
|
|
"dmg" -> TargetFormat.Dmg
|
|
"pkg" -> TargetFormat.Pkg
|
|
else -> throw GradleException(
|
|
"Unsupported desktopPackageFormats entry '$format'. " +
|
|
"Use one or more of: msi, exe, deb, rpm, dmg, pkg."
|
|
)
|
|
}
|
|
}
|
|
|
|
fun desktopPackageFormatId(format: TargetFormat): String {
|
|
return when (format) {
|
|
TargetFormat.Exe -> "exe"
|
|
TargetFormat.Msi -> "msi"
|
|
TargetFormat.Deb -> "deb"
|
|
TargetFormat.Rpm -> "rpm"
|
|
TargetFormat.Dmg -> "dmg"
|
|
TargetFormat.Pkg -> "pkg"
|
|
else -> format.name.lowercase()
|
|
}
|
|
}
|
|
|
|
fun desktopPackageFormatSupportedOnHost(
|
|
format: TargetFormat,
|
|
osName: String = System.getProperty("os.name")
|
|
): Boolean {
|
|
return when (desktopOsId(osName)) {
|
|
"windows" -> format == TargetFormat.Msi || format == TargetFormat.Exe
|
|
"linux" -> format == TargetFormat.Deb || format == TargetFormat.Rpm
|
|
"macos" -> format == TargetFormat.Dmg || format == TargetFormat.Pkg
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
fun normalizeDesktopPackageFormats(
|
|
rawFormats: String,
|
|
osName: String = System.getProperty("os.name")
|
|
): List<TargetFormat> {
|
|
val formats = rawFormats
|
|
.split(',', ';', ' ', '\n', '\t')
|
|
.map { it.trim() }
|
|
.filter { it.isNotBlank() }
|
|
.map(::desktopTargetFormatForId)
|
|
.distinct()
|
|
if (formats.isEmpty()) {
|
|
throw GradleException(
|
|
"desktopPackageFormats resolved to no package formats for ${desktopOsId(osName)}. " +
|
|
"Set -PdesktopPackageFormats=msi on Windows or -PdesktopPackageFormats=deb,rpm on Linux."
|
|
)
|
|
}
|
|
val unsupported = formats.filterNot { desktopPackageFormatSupportedOnHost(it, osName) }
|
|
if (unsupported.isNotEmpty()) {
|
|
throw GradleException(
|
|
"desktopPackageFormats=${formats.joinToString(",") { desktopPackageFormatId(it) }} does not match " +
|
|
"the current packaging host ${desktopOsId(osName)}. Unsupported here: " +
|
|
unsupported.joinToString(",") { desktopPackageFormatId(it) } + "."
|
|
)
|
|
}
|
|
return formats
|
|
}
|
|
|
|
fun normalizeDesktopMsixVersion(rawVersion: String): String {
|
|
val parts = rawVersion.trim().split('.')
|
|
if (parts.size !in 3..4 || parts.any { it.isBlank() || it.all(Char::isDigit).not() }) {
|
|
throw GradleException(
|
|
"desktopMsixVersion must be a numeric Windows package version with three or four parts, " +
|
|
"for example 1.0.1 or 1.0.1.0."
|
|
)
|
|
}
|
|
val normalized = if (parts.size == 3) parts + "0" else parts
|
|
normalized.forEach { part ->
|
|
val value = part.toIntOrNull()
|
|
if (value == null || value !in 0..65535) {
|
|
throw GradleException("desktopMsixVersion part '$part' is outside the MSIX range 0..65535.")
|
|
}
|
|
}
|
|
return normalized.joinToString(".")
|
|
}
|
|
|
|
fun normalizeDesktopMsixIdentityName(rawName: String): String {
|
|
val normalized = rawName.trim()
|
|
if (!Regex("[A-Za-z0-9][A-Za-z0-9.-]{2,49}").matches(normalized)) {
|
|
throw GradleException(
|
|
"desktopMsixIdentityName must be 3-50 characters using letters, numbers, dots, or hyphens."
|
|
)
|
|
}
|
|
return normalized
|
|
}
|
|
|
|
fun desktopMsixArchitecture(osArch: String = System.getProperty("os.arch")): String {
|
|
return when (desktopArchId(osArch)) {
|
|
"x64" -> "x64"
|
|
"arm64" -> "arm64"
|
|
"x86" -> "x86"
|
|
else -> "neutral"
|
|
}
|
|
}
|
|
|
|
fun latestExistingFile(candidates: List<File>): File? {
|
|
return candidates.filter { it.isFile }.maxByOrNull { it.absolutePath }
|
|
}
|
|
|
|
fun windowsSdkToolCandidates(toolName: String): List<File> {
|
|
val roots = listOfNotNull(
|
|
System.getenv("WindowsSdkDir")?.let(::File),
|
|
File("C:/Program Files (x86)/Windows Kits/10"),
|
|
File("C:/Program Files/Windows Kits/10"),
|
|
File("C:/Program Files (x86)/Windows Kits/10/App Certification Kit"),
|
|
File("C:/Program Files/Windows Kits/10/App Certification Kit")
|
|
).distinctBy { it.absolutePath.lowercase() }
|
|
val sdkBins = roots.flatMap { root ->
|
|
safeChildDirectories(root.resolve("bin")).flatMap { versionDir ->
|
|
listOf(
|
|
versionDir.resolve("x64/$toolName.exe"),
|
|
versionDir.resolve("x86/$toolName.exe"),
|
|
versionDir.resolve(toolName)
|
|
)
|
|
}
|
|
}
|
|
val directBins = roots.map { root -> root.resolve("$toolName.exe") }
|
|
val pathBins = (System.getenv("PATH") ?: "")
|
|
.split(File.pathSeparator)
|
|
.filter { it.isNotBlank() }
|
|
.map { File(it).resolve("$toolName.exe") }
|
|
return sdkBins + directBins + pathBins
|
|
}
|
|
|
|
fun findWindowsSdkTool(toolName: String, explicitPath: String?): File {
|
|
val explicit = explicitPath?.trim()?.takeIf { it.isNotEmpty() }?.let(::File)
|
|
if (explicit != null) return explicit
|
|
return latestExistingFile(windowsSdkToolCandidates(toolName))
|
|
?: File(rootProject.projectDir, "__missing_windows_sdk_tool__/$toolName.exe")
|
|
}
|
|
|
|
val desktopVersionName = "1.0.1"
|
|
val desktopFlavor = providers.gradleProperty("desktopFlavor")
|
|
.orElse("standard")
|
|
.map(::normalizeDesktopFlavor)
|
|
.get()
|
|
val isOssOfflineDesktop = desktopFlavor == "oss-offline"
|
|
val desktopDiagnostics = providers.gradleProperty("desktopDiagnostics")
|
|
.map { it.equals("true", ignoreCase = true) }
|
|
.orElse(false)
|
|
val desktopDiagnosticTags = providers.gradleProperty("desktopDiagnosticTags")
|
|
.orElse("")
|
|
val desktopResolvedVersionName = providers.gradleProperty("desktopVersionName")
|
|
.orElse(providers.gradleProperty("desktopVersion"))
|
|
.orElse(desktopVersionName)
|
|
.map(::normalizeDesktopVersionName)
|
|
val desktopPackageVersion = providers.gradleProperty("desktopPackageVersion")
|
|
.orElse(desktopResolvedVersionName)
|
|
.map(::normalizeDesktopPackageVersion)
|
|
val desktopPackageName = if (isOssOfflineDesktop) "Episteme oss" else "Episteme"
|
|
val desktopLinuxPackageName = if (isOssOfflineDesktop) "episteme-oss" else "episteme"
|
|
val desktopPackageDescription = if (isOssOfflineDesktop) {
|
|
"Episteme oss offline desktop reader"
|
|
} else {
|
|
"Episteme desktop reader"
|
|
}
|
|
val desktopVendor = providers.gradleProperty("desktopVendor").orElse("Aryan")
|
|
val desktopVendorName = desktopVendor.get()
|
|
val desktopProjectUrl = providers.gradleProperty("desktopProjectUrl")
|
|
.orElse("https://github.com/Aryan-Raj3112/episteme")
|
|
val desktopOsName = System.getProperty("os.name")
|
|
val desktopOsArch = System.getProperty("os.arch")
|
|
val desktopPackageArchitecture = normalizeDesktopPackageArchitecture(desktopOsArch)
|
|
val desktopAurPackageName = providers.gradleProperty("desktopAurPackageName")
|
|
.orElse(if (isOssOfflineDesktop) "episteme-oss-bin" else "episteme-bin")
|
|
val desktopAurPackageRelease = providers.gradleProperty("desktopAurPackageRelease")
|
|
.orElse("1")
|
|
val desktopAurSourceUrl = providers.gradleProperty("desktopAurSourceUrl")
|
|
.orElse("")
|
|
val desktopPackageTargetFormats = providers.gradleProperty("desktopPackageFormats")
|
|
.orElse(desktopDefaultPackageFormats(desktopOsName))
|
|
.map { normalizeDesktopPackageFormats(it, desktopOsName) }
|
|
.get()
|
|
val desktopMsixIdentityName = providers.gradleProperty("desktopMsixIdentityName")
|
|
.orElse(if (isOssOfflineDesktop) "Aryan.EpistemeOss" else "Aryan.Episteme")
|
|
.map(::normalizeDesktopMsixIdentityName)
|
|
.get()
|
|
val desktopMsixPublisher = providers.gradleProperty("desktopMsixPublisher")
|
|
.orElse("CN=$desktopVendorName")
|
|
val desktopMsixPublisherDisplayName = providers.gradleProperty("desktopMsixPublisherDisplayName")
|
|
.orElse(desktopVendor)
|
|
val desktopMsixVersion = providers.gradleProperty("desktopMsixVersion")
|
|
.orElse(desktopPackageVersion)
|
|
.map(::normalizeDesktopMsixVersion)
|
|
.get()
|
|
val desktopMsixArchitecture = desktopMsixArchitecture(desktopOsArch)
|
|
val desktopMakeAppxPath = providers.gradleProperty("desktopMakeAppxPath").orNull
|
|
val desktopSignToolPath = providers.gradleProperty("desktopSignToolPath").orNull
|
|
val desktopMsixCertificatePath = providers.gradleProperty("desktopMsixCertificatePath").orNull
|
|
val desktopMsixCertificatePassword = providers.gradleProperty("desktopMsixCertificatePassword")
|
|
.orElse("")
|
|
val desktopMsixTimestampUrl = providers.gradleProperty("desktopMsixTimestampUrl")
|
|
.orElse("http://timestamp.digicert.com")
|
|
val desktopNativePackageSupportedHost = desktopOsId(desktopOsName) in setOf("windows", "linux") &&
|
|
desktopArchId(desktopOsArch) == "x64"
|
|
val desktopReleaseProguardEnabled = providers.gradleProperty("desktopReleaseProguard")
|
|
.map { it.equals("true", ignoreCase = true) }
|
|
.orElse(false)
|
|
.get()
|
|
val desktopSwtVersion = "3.133.0"
|
|
val desktopSwtDependency = desktopSwtArtifactId(desktopOsName, desktopOsArch)
|
|
?.let { artifactId -> "org.eclipse.platform:$artifactId:$desktopSwtVersion" }
|
|
val generatedDesktopResourcesDir = layout.buildDirectory.dir("generated/desktopAppResources")
|
|
val generatedDesktopCloudConfigFile = layout.buildDirectory.file("generated/desktopCloudConfig/desktop-cloud.properties")
|
|
val generatedDesktopStringResourcesDir = layout.buildDirectory.dir("generated/desktopStringResources")
|
|
val rootLocalProperties = Properties()
|
|
val rootLocalPropertiesFile = rootProject.file("local.properties")
|
|
if (rootLocalPropertiesFile.exists()) {
|
|
rootLocalPropertiesFile.inputStream().use(rootLocalProperties::load)
|
|
}
|
|
fun desktopConfigValue(vararg keys: String): String {
|
|
return keys.firstNotNullOfOrNull { key ->
|
|
providers.gradleProperty(key).orNull
|
|
?: rootLocalProperties.getProperty(key)
|
|
?: System.getenv(key)
|
|
}?.trim().orEmpty()
|
|
}
|
|
val desktopCloudConfig = mapOf(
|
|
"AI_WORKER_URL" to desktopConfigValue("DESKTOP_AI_WORKER_URL", "AI_WORKER_URL"),
|
|
"TTS_WORKER_URL" to desktopConfigValue("DESKTOP_TTS_WORKER_URL", "TTS_WORKER_URL"),
|
|
"FIREBASE_WEB_API_KEY" to desktopConfigValue("DESKTOP_FIREBASE_WEB_API_KEY", "FIREBASE_WEB_API_KEY", "GOOGLE_API_KEY"),
|
|
"FIREBASE_PROJECT_ID" to desktopConfigValue("DESKTOP_FIREBASE_PROJECT_ID", "FIREBASE_PROJECT_ID").ifBlank { "reader-9fc469d7" },
|
|
"GOOGLE_OAUTH_CLIENT_ID" to desktopConfigValue("DESKTOP_GOOGLE_OAUTH_CLIENT_ID", "GOOGLE_OAUTH_CLIENT_ID", "GOOGLE_WEB_CLIENT_ID", "DEFAULT_WEB_CLIENT_ID"),
|
|
"GOOGLE_OAUTH_CLIENT_SECRET" to desktopConfigValue("DESKTOP_GOOGLE_OAUTH_CLIENT_SECRET", "GOOGLE_OAUTH_CLIENT_SECRET", "GOOGLE_WEB_CLIENT_SECRET", "DEFAULT_WEB_CLIENT_SECRET")
|
|
)
|
|
val desktopAllowUnconfiguredStandardServices = providers.gradleProperty("desktopAllowUnconfiguredStandardServices")
|
|
.map { it.equals("true", ignoreCase = true) }
|
|
.orElse(false)
|
|
.get()
|
|
val desktopMissingStandardServiceConfig = if (isOssOfflineDesktop || desktopAllowUnconfiguredStandardServices) {
|
|
emptyList()
|
|
} else {
|
|
listOf("FIREBASE_WEB_API_KEY", "GOOGLE_OAUTH_CLIENT_ID")
|
|
.filter { key -> desktopCloudConfig[key].isNullOrBlank() }
|
|
}
|
|
val bundledPdfiumDir = layout.projectDirectory.dir(
|
|
"../third_party/pdfium/${desktopPdfiumDirectoryName(desktopOsName, desktopOsArch)}"
|
|
)
|
|
val bundledPdfiumLibraryPath = desktopPdfiumLibraryPath(desktopOsName, desktopOsArch)
|
|
val desktopWindowsIconFile = layout.projectDirectory.file("src/desktopMain/resources/episteme.ico")
|
|
val desktopLinuxIconFile = layout.projectDirectory.file("src/desktopMain/resources/episteme_icon.png")
|
|
val desktopWindowsUpgradeUuid = if (isOssOfflineDesktop) {
|
|
"ca13b201-940a-420a-8a3f-16e7d83d12a8"
|
|
} else {
|
|
"c04c5823-b25a-4f38-a1cf-0da7b02ac397"
|
|
}
|
|
val desktopPackagingJavaHome = findDesktopPackagingJavaHome(
|
|
explicitCandidates = listOfNotNull(
|
|
providers.gradleProperty("desktopPackagingJavaHome").orNull,
|
|
providers.environmentVariable("DESKTOP_PACKAGING_JAVA_HOME").orNull,
|
|
providers.environmentVariable("JPACKAGE_HOME").orNull
|
|
),
|
|
implicitCandidates = buildList {
|
|
addAll(desktopGradleJdkCandidates())
|
|
providers.gradleProperty("org.gradle.java.home").orNull?.let { add(File(it)) }
|
|
providers.environmentVariable("GRADLE_LOCAL_JAVA_HOME").orNull?.let { add(File(it)) }
|
|
providers.environmentVariable("JAVA_HOME").orNull?.let { add(File(it)) }
|
|
providers.environmentVariable("JDK_HOME").orNull?.let { add(File(it)) }
|
|
add(File(System.getProperty("java.home")))
|
|
addAll(desktopPathJdkCandidates(desktopOsName))
|
|
addAll(desktopPlatformJdkCandidates(desktopOsName))
|
|
},
|
|
osName = desktopOsName
|
|
)?.absolutePath
|
|
|
|
val checkBundledPdfiumRuntime by tasks.registering(CheckBundledPdfiumRuntimeTask::class) {
|
|
bundleRootPath.set(bundledPdfiumDir.asFile.absolutePath)
|
|
libraryPath.set(bundledPdfiumLibraryPath)
|
|
}
|
|
|
|
val generateDesktopCloudConfig by tasks.registering(GenerateDesktopCloudConfigTask::class) {
|
|
configValues.set(desktopCloudConfig)
|
|
outputFile.set(generatedDesktopCloudConfigFile)
|
|
}
|
|
|
|
val prepareBundledDesktopResources by tasks.registering(Sync::class) {
|
|
dependsOn(checkBundledPdfiumRuntime, generateDesktopCloudConfig)
|
|
from(bundledPdfiumDir) {
|
|
into("common/third_party/pdfium/${desktopPdfiumDirectoryName(desktopOsName, desktopOsArch)}")
|
|
}
|
|
into("common") {
|
|
from(generatedDesktopCloudConfigFile)
|
|
}
|
|
into(generatedDesktopResourcesDir)
|
|
}
|
|
|
|
val prepareDesktopStringResources by tasks.registering(Sync::class) {
|
|
// Reuse Android string resources as the localization source for desktop.
|
|
from(rootProject.layout.projectDirectory.dir("app/src/main/res")) {
|
|
include("values*/strings.xml")
|
|
include("values*/plurals.xml")
|
|
into("desktop-android-res")
|
|
}
|
|
into(generatedDesktopStringResourcesDir)
|
|
}
|
|
|
|
val verifyDesktopNativePackaging by tasks.registering(VerifyDesktopNativePackagingTask::class) {
|
|
supportedHost.set(desktopNativePackageSupportedHost)
|
|
hostOsId.set(desktopOsId(desktopOsName))
|
|
hostArchId.set(desktopArchId(desktopOsArch))
|
|
missingStandardServiceConfig.set(desktopMissingStandardServiceConfig)
|
|
}
|
|
|
|
val desktopDistributableAppDir = layout.buildDirectory.dir("compose/binaries/main/app/$desktopPackageName")
|
|
val desktopReleaseDistributableAppDir = layout.buildDirectory.dir("compose/binaries/main-release/app/$desktopPackageName")
|
|
val desktopLinuxTarFileName = "${desktopLinuxPackageName}-${desktopPackageVersion.get()}-linux-$desktopPackageArchitecture.tar.gz"
|
|
val desktopAurOutputDir = layout.buildDirectory.dir("aur/${desktopAurPackageName.get()}")
|
|
val desktopMsixPackageDir = layout.buildDirectory.dir("msix/package")
|
|
val desktopMsixAssetsDir = layout.buildDirectory.dir("msix/generated/assets")
|
|
val desktopMsixManifestFile = layout.buildDirectory.file("msix/generated/AppxManifest.xml")
|
|
val desktopMsixOutputFile = layout.buildDirectory.file(
|
|
"compose/binaries/main-release/msix/${desktopLinuxPackageName}-${desktopPackageVersion.get()}-windows-$desktopPackageArchitecture.msix"
|
|
)
|
|
|
|
val packageLinuxTar by tasks.registering(Tar::class) {
|
|
group = "distribution"
|
|
description = "Packages the Linux desktop distributable as a tar.gz for Arch/AUR packaging."
|
|
dependsOn("createDistributable")
|
|
|
|
archiveFileName.set(desktopLinuxTarFileName)
|
|
destinationDirectory.set(layout.buildDirectory.dir("compose/binaries/main/linux-tar"))
|
|
compression = Compression.GZIP
|
|
|
|
from(desktopDistributableAppDir) {
|
|
into(desktopLinuxPackageName)
|
|
}
|
|
from(desktopLinuxIconFile) {
|
|
into("$desktopLinuxPackageName/share")
|
|
}
|
|
from(rootProject.layout.projectDirectory.file("LICENSE")) {
|
|
into("$desktopLinuxPackageName/share/licenses")
|
|
}
|
|
}
|
|
|
|
val prepareAurPackage by tasks.registering(PrepareDesktopAurPackageTask::class) {
|
|
group = "distribution"
|
|
description = "Generates a local AUR package directory with PKGBUILD and .SRCINFO."
|
|
dependsOn(packageLinuxTar)
|
|
|
|
aurPackageName.set(desktopAurPackageName)
|
|
providedPackageName.set(desktopLinuxPackageName)
|
|
packageVersion.set(desktopPackageVersion)
|
|
packageRelease.set(desktopAurPackageRelease)
|
|
packageDescription.set(desktopPackageDescription)
|
|
appDisplayName.set(desktopPackageName)
|
|
installDirectoryName.set(desktopLinuxPackageName)
|
|
launcherName.set(desktopLinuxPackageName)
|
|
executableName.set(desktopPackageName)
|
|
sourceUrl.set(desktopAurSourceUrl)
|
|
projectUrl.set(desktopProjectUrl)
|
|
linuxTarFile.set(packageLinuxTar.flatMap { it.archiveFile })
|
|
outputDirectory.set(desktopAurOutputDir)
|
|
}
|
|
|
|
tasks.register<Exec>("packageAur") {
|
|
group = "distribution"
|
|
description = "Builds the generated AUR package with makepkg. Run this on Arch Linux."
|
|
dependsOn(prepareAurPackage)
|
|
|
|
commandLine("makepkg", "-sf", "--cleanbuild")
|
|
workingDir = desktopAurOutputDir.get().asFile
|
|
}
|
|
|
|
val generateDesktopMsixManifest by tasks.registering(GenerateDesktopMsixManifestTask::class) {
|
|
identityName.set(desktopMsixIdentityName)
|
|
publisher.set(desktopMsixPublisher)
|
|
publisherDisplayName.set(desktopMsixPublisherDisplayName)
|
|
packageName.set(desktopPackageName)
|
|
packageDescription.set(desktopPackageDescription)
|
|
packageVersion.set(desktopMsixVersion)
|
|
architecture.set(desktopMsixArchitecture)
|
|
executablePath.set("$desktopPackageName.exe")
|
|
outputFile.set(desktopMsixManifestFile)
|
|
}
|
|
|
|
val generateDesktopMsixAssets by tasks.registering(GenerateDesktopMsixAssetsTask::class) {
|
|
sourceIconFile.set(desktopLinuxIconFile)
|
|
outputDirectory.set(desktopMsixAssetsDir)
|
|
}
|
|
|
|
val prepareReleaseMsixPackage by tasks.registering(Sync::class) {
|
|
group = "distribution"
|
|
description = "Stages the release Windows app image and MSIX metadata for makeappx."
|
|
dependsOn("createReleaseDistributable", generateDesktopMsixManifest, generateDesktopMsixAssets)
|
|
|
|
from(desktopReleaseDistributableAppDir)
|
|
from(desktopMsixManifestFile)
|
|
from(desktopMsixAssetsDir) {
|
|
into("Assets")
|
|
}
|
|
into(desktopMsixPackageDir)
|
|
}
|
|
|
|
val packageReleaseMsix by tasks.registering(PackageDesktopMsixTask::class) {
|
|
group = "distribution"
|
|
description = "Packages the release Windows app image as an MSIX using Windows SDK makeappx."
|
|
dependsOn(prepareReleaseMsixPackage)
|
|
|
|
val makeAppx = findWindowsSdkTool("makeappx", desktopMakeAppxPath)
|
|
packageRootDirectory.set(desktopMsixPackageDir)
|
|
outputFile.set(desktopMsixOutputFile)
|
|
makeAppxPath.set(makeAppx.absolutePath)
|
|
hostOsId.set(desktopOsId(desktopOsName))
|
|
hostArchId.set(desktopArchId(desktopOsArch))
|
|
}
|
|
|
|
val signReleaseMsix = desktopMsixCertificatePath?.trim()?.takeIf { it.isNotEmpty() }?.let { certificatePath ->
|
|
tasks.register<SignDesktopMsixTask>("signReleaseMsix") {
|
|
group = "distribution"
|
|
description = "Signs the release MSIX with signtool when -PdesktopMsixCertificatePath is configured."
|
|
dependsOn(packageReleaseMsix)
|
|
|
|
val signTool = findWindowsSdkTool("signtool", desktopSignToolPath)
|
|
val resolvedCertificateFile = File(certificatePath).let { file ->
|
|
if (file.isAbsolute) file else project.file(certificatePath)
|
|
}
|
|
unsignedMsixFile.set(desktopMsixOutputFile)
|
|
certificateFile.set(resolvedCertificateFile)
|
|
signToolPath.set(signTool.absolutePath)
|
|
certificatePassword.set(desktopMsixCertificatePassword)
|
|
timestampUrl.set(desktopMsixTimestampUrl)
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
jvm("desktop")
|
|
jvmToolchain(21)
|
|
|
|
sourceSets {
|
|
val desktopMain by getting {
|
|
resources.srcDir(prepareDesktopStringResources)
|
|
dependencies {
|
|
implementation(project(":shared"))
|
|
implementation(compose.desktop.currentOs)
|
|
implementation(compose.material3)
|
|
desktopSwtDependency?.let { dependency ->
|
|
compileOnly(dependency)
|
|
runtimeOnly(dependency)
|
|
}
|
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.1")
|
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
|
implementation("net.java.dev.jna:jna:5.17.0")
|
|
implementation("org.apache.commons:commons-compress:1.28.0")
|
|
implementation("org.tukaani:xz:1.10")
|
|
implementation("com.twelvemonkeys.imageio:imageio-webp:3.13.1")
|
|
}
|
|
}
|
|
val desktopTest by getting {
|
|
dependencies {
|
|
implementation(kotlin("test"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
compose.desktop {
|
|
application {
|
|
mainClass = "com.aryan.reader.desktop.LauncherKt"
|
|
desktopPackagingJavaHome?.let { javaHome = it }
|
|
|
|
jvmArgs("--add-opens", "java.desktop/sun.awt=ALL-UNNAMED")
|
|
jvmArgs("--add-opens", "java.desktop/java.awt.peer=ALL-UNNAMED")
|
|
jvmArgs("-Depisteme.desktop.flavor=$desktopFlavor")
|
|
jvmArgs("-Depisteme.desktop.diagnostics=${desktopDiagnostics.get()}")
|
|
jvmArgs("-Depisteme.desktop.diagnostics.tags=${desktopDiagnosticTags.get()}")
|
|
jvmArgs("-Depisteme.desktop.version=${desktopResolvedVersionName.get()}")
|
|
|
|
buildTypes.release.proguard {
|
|
// ProGuard still rewrites and shrinks release jars even when optimization and
|
|
// obfuscation are disabled. That has produced invalid stack-map frames in large
|
|
// Compose/PDF lambdas and stripped WebView bridge behavior in packaged MSIs.
|
|
isEnabled.set(desktopReleaseProguardEnabled)
|
|
obfuscate.set(false)
|
|
// Compose/Kotlin generated methods can produce very large stack-map frames.
|
|
// ProGuard optimization has emitted invalid frames for SharedAppTheme in release builds.
|
|
optimize.set(false)
|
|
configurationFiles.from(project.file("compose-desktop.pro"))
|
|
}
|
|
|
|
nativeDistributions {
|
|
targetFormats(*desktopPackageTargetFormats.toTypedArray())
|
|
modules(
|
|
"java.datatransfer",
|
|
"java.desktop",
|
|
"java.logging",
|
|
"java.management",
|
|
"java.net.http",
|
|
"jdk.charsets",
|
|
"jdk.httpserver",
|
|
"jdk.unsupported"
|
|
)
|
|
packageName = desktopPackageName
|
|
packageVersion = desktopPackageVersion.get()
|
|
description = desktopPackageDescription
|
|
vendor = desktopVendor.get()
|
|
appResourcesRootDir.set(generatedDesktopResourcesDir)
|
|
windows {
|
|
iconFile.set(desktopWindowsIconFile)
|
|
dirChooser = true
|
|
shortcut = true
|
|
menu = true
|
|
menuGroup = "Episteme"
|
|
perUserInstall = true
|
|
upgradeUuid = desktopWindowsUpgradeUuid
|
|
}
|
|
linux {
|
|
iconFile.set(desktopLinuxIconFile)
|
|
packageName = desktopLinuxPackageName
|
|
debMaintainer = "epistemereader@gmail.com"
|
|
menuGroup = "Office"
|
|
appCategory = "Office"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.withType<JavaExec>().configureEach {
|
|
jvmArgs("--add-opens", "java.desktop/sun.awt=ALL-UNNAMED")
|
|
jvmArgs("--add-opens", "java.desktop/java.awt.peer=ALL-UNNAMED")
|
|
jvmArgs("-Depisteme.desktop.flavor=$desktopFlavor")
|
|
jvmArgs("-Depisteme.desktop.diagnostics=${desktopDiagnostics.get()}")
|
|
jvmArgs("-Depisteme.desktop.diagnostics.tags=${desktopDiagnosticTags.get()}")
|
|
jvmArgs("-Depisteme.desktop.version=${desktopResolvedVersionName.get()}")
|
|
if (System.getProperty("os.name").contains("Mac")) {
|
|
jvmArgs("--add-opens", "java.desktop/sun.lwawt=ALL-UNNAMED")
|
|
jvmArgs("--add-opens", "java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
|
|
}
|
|
}
|
|
|
|
tasks.withType<Jar>().configureEach {
|
|
manifest {
|
|
attributes(
|
|
"Implementation-Title" to desktopPackageName,
|
|
"Implementation-Version" to desktopResolvedVersionName.get(),
|
|
"Implementation-Vendor" to desktopVendor.get()
|
|
)
|
|
}
|
|
}
|
|
|
|
val stripReleaseProguardJarSignatures = if (desktopReleaseProguardEnabled) {
|
|
tasks.registering(StripInvalidJarSignaturesTask::class) {
|
|
dependsOn("proguardReleaseJars")
|
|
jarDirectoryPath.set(layout.buildDirectory.dir("compose/tmp/main-release/proguard").map { it.asFile.absolutePath })
|
|
}
|
|
} else {
|
|
null
|
|
}
|
|
|
|
tasks.matching {
|
|
it.name in setOf(
|
|
"createReleaseDistributable",
|
|
"packageReleaseDistributionForCurrentOS",
|
|
"packageReleaseExe",
|
|
"packageReleaseMsi",
|
|
"packageReleaseMsix",
|
|
"packageReleaseDeb",
|
|
"packageReleaseRpm",
|
|
"runReleaseDistributable"
|
|
)
|
|
}.configureEach {
|
|
stripReleaseProguardJarSignatures?.let { dependsOn(it) }
|
|
}
|
|
|
|
mapOf(
|
|
"packageMsi" to "main",
|
|
"packageReleaseMsi" to "main-release"
|
|
).forEach { (taskName, distributionName) ->
|
|
val renameTask = tasks.register<RenameDesktopMsiOutputTask>("rename${taskName.replaceFirstChar(Char::titlecase)}Output") {
|
|
msiDirectoryPath.set(layout.buildDirectory.dir("compose/binaries/$distributionName/msi").get().asFile.absolutePath)
|
|
packageName.set(desktopPackageName)
|
|
packageVersion.set(desktopPackageVersion.get())
|
|
architecture.set(desktopPackageArchitecture)
|
|
}
|
|
tasks.matching { it.name == taskName }.configureEach {
|
|
finalizedBy(renameTask)
|
|
}
|
|
}
|
|
|
|
tasks.matching {
|
|
it.name in setOf(
|
|
"createDistributable",
|
|
"createReleaseDistributable",
|
|
"prepareAppResources",
|
|
"prepareReleaseAppResources",
|
|
"packageDistributionForCurrentOS",
|
|
"packageReleaseDistributionForCurrentOS",
|
|
"packageExe",
|
|
"packageReleaseExe",
|
|
"packageMsi",
|
|
"packageReleaseMsi",
|
|
"prepareReleaseMsixPackage",
|
|
"packageReleaseMsix",
|
|
"signReleaseMsix",
|
|
"packageDeb",
|
|
"packageReleaseDeb",
|
|
"packageRpm",
|
|
"packageReleaseRpm",
|
|
"packageLinuxTar",
|
|
"prepareAurPackage",
|
|
"packageAur",
|
|
"runDistributable",
|
|
"runReleaseDistributable"
|
|
)
|
|
}.configureEach {
|
|
dependsOn(verifyDesktopNativePackaging)
|
|
dependsOn(prepareBundledDesktopResources)
|
|
inputs.dir(generatedDesktopResourcesDir)
|
|
.withPropertyName("bundledDesktopResources")
|
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
|
}
|