mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
857 lines
34 KiB
Kotlin
857 lines
34 KiB
Kotlin
import org.gradle.internal.jvm.Jvm
|
|
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
|
import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
|
|
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
|
import org.jetbrains.kotlin.org.apache.commons.io.FileUtils
|
|
import org.jetbrains.kotlin.org.apache.commons.io.filefilter.FileFilterUtils
|
|
import org.jetbrains.kotlin.org.apache.commons.lang3.StringUtils
|
|
import org.jetbrains.kotlin.org.apache.commons.lang3.time.DateFormatUtils
|
|
import java.io.FileNotFoundException
|
|
import java.nio.file.Files
|
|
import java.util.*
|
|
import java.util.concurrent.Executors
|
|
import java.util.concurrent.Future
|
|
|
|
plugins {
|
|
java
|
|
idea
|
|
application
|
|
`maven-publish`
|
|
alias(libs.plugins.kotlin.jvm)
|
|
alias(libs.plugins.kotlinx.serialization)
|
|
}
|
|
|
|
|
|
group = "app.termora"
|
|
version = rootProject.projectDir.resolve("VERSION").readText().trim()
|
|
|
|
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
|
val arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture()
|
|
val appVersion = project.version.toString().split("-")[0]
|
|
val isDeb = os.isLinux && System.getProperty("type") == "deb"
|
|
|
|
// macOS 签名信息
|
|
val macOSSignUsername = System.getenv("TERMORA_MAC_SIGN_USER_NAME") ?: StringUtils.EMPTY
|
|
val macOSSign = os.isMacOsX && macOSSignUsername.isNotBlank()
|
|
&& System.getenv("TERMORA_MAC_SIGN").toBoolean()
|
|
|
|
// macOS 公证信息
|
|
val macOSNotaryKeychainProfile = System.getenv("TERMORA_MAC_NOTARY_KEYCHAIN_PROFILE") ?: StringUtils.EMPTY
|
|
val macOSNotary = macOSSign && macOSNotaryKeychainProfile.isNotBlank()
|
|
&& System.getenv("TERMORA_MAC_NOTARY").toBoolean()
|
|
|
|
allprojects {
|
|
repositories {
|
|
mavenCentral()
|
|
maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies")
|
|
maven("https://www.jitpack.io")
|
|
maven("https://central.sonatype.com/repository/maven-snapshots")
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
|
|
testImplementation(kotlin("test"))
|
|
testImplementation(libs.hutool)
|
|
testImplementation(libs.sshj)
|
|
testImplementation(libs.jsch)
|
|
testImplementation(libs.rhino)
|
|
testImplementation(libs.delight.rhino.sandbox)
|
|
testImplementation(platform(libs.testcontainers.bom))
|
|
testImplementation(libs.testcontainers)
|
|
testImplementation(libs.h2)
|
|
testImplementation(libs.exposed.migration)
|
|
|
|
api(kotlin("reflect"))
|
|
api(libs.slf4j.api)
|
|
api(libs.pty4j)
|
|
api(libs.slf4j.tinylog)
|
|
api(libs.tinylog.impl)
|
|
api(libs.commons.codec)
|
|
api(libs.commons.io)
|
|
api(libs.commons.lang3)
|
|
api(libs.commons.csv)
|
|
api(libs.commons.net)
|
|
api(libs.commons.text)
|
|
api(libs.kotlinx.coroutines.swing)
|
|
api(libs.kotlinx.coroutines.core)
|
|
|
|
api(libs.flatlaf)
|
|
api(libs.flatlafextras)
|
|
api(libs.flatlafswingx)
|
|
|
|
api(libs.kotlinx.serialization.json)
|
|
api(libs.swingx)
|
|
api(libs.jgoodies.forms)
|
|
api(libs.jna)
|
|
api(libs.jna.platform)
|
|
api(libs.versioncompare)
|
|
api(libs.oshi.core)
|
|
api(libs.jSystemThemeDetector) { exclude(group = "*", module = "*") }
|
|
api(libs.jfa) { exclude(group = "*", module = "*") }
|
|
api(libs.jbr.api)
|
|
api(libs.okhttp)
|
|
api(libs.okhttp.logging)
|
|
api(libs.sshd.core)
|
|
api(libs.commonmark)
|
|
api(libs.jgit)
|
|
api(libs.jgit.sshd) { exclude(group = "*", module = "sshd-osgi") }
|
|
api(libs.jgit.agent) { exclude(group = "*", module = "sshd-osgi") }
|
|
api(libs.eddsa)
|
|
api(libs.jnafilechooser)
|
|
|
|
api(libs.colorpicker)
|
|
api(libs.mixpanel)
|
|
api(libs.ini4j)
|
|
api(libs.restart4j)
|
|
api(libs.exposed.core)
|
|
api(libs.exposed.crypt)
|
|
api(libs.exposed.jdbc)
|
|
api(libs.sqlite)
|
|
api(libs.jug)
|
|
api(libs.semver4j)
|
|
api(libs.jsvg)
|
|
api(libs.dom4j) { exclude(group = "*", module = "*") }
|
|
}
|
|
|
|
application {
|
|
val args = mutableListOf(
|
|
"-Xmx2048m",
|
|
"-Drelease-date=${DateFormatUtils.format(Date(), "yyyy-MM-dd")}",
|
|
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
|
|
)
|
|
|
|
if (os.isMacOsX) {
|
|
// macOS NSWindow
|
|
args.add("--add-opens java.desktop/java.awt=ALL-UNNAMED")
|
|
args.add("--add-opens java.desktop/sun.lwawt=ALL-UNNAMED")
|
|
args.add("--add-opens java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
|
|
args.add("--add-exports java.desktop/com.apple.eawt=ALL-UNNAMED")
|
|
args.add("-Dsun.java2d.metal=true")
|
|
args.add("-Dapple.awt.application.appearance=system")
|
|
}
|
|
|
|
args.add("-DTERMORA_PLUGIN_DIRECTORY=${layout.buildDirectory.get().asFile.absolutePath}${File.separator}plugins")
|
|
|
|
applicationDefaultJvmArgs = args
|
|
mainClass = "app.termora.MainKt"
|
|
}
|
|
|
|
publishing {
|
|
publications {
|
|
create<MavenPublication>("mavenJava") {
|
|
from(components["java"])
|
|
|
|
pom {
|
|
name = project.name
|
|
description = "Termora is a terminal emulator and SSH client for Windows, macOS and Linux"
|
|
url = "https://github.com/TermoraDev/termora"
|
|
|
|
licenses {
|
|
license {
|
|
name = "AGPL-3.0"
|
|
url = "https://opensource.org/license/agpl-v3"
|
|
}
|
|
}
|
|
|
|
developers {
|
|
developer {
|
|
name = "hstyi"
|
|
url = "https://github.com/hstyi"
|
|
}
|
|
}
|
|
|
|
scm {
|
|
url = "https://github.com/TermoraDev/termora"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.test {
|
|
useJUnitPlatform()
|
|
}
|
|
|
|
tasks.register<Copy>("copy-dependencies") {
|
|
val dir = layout.buildDirectory.dir("libs")
|
|
from(configurations.runtimeClasspath).into(dir)
|
|
val jna = libs.jna.asProvider().get()
|
|
val pty4j = libs.pty4j.get()
|
|
val flatlaf = libs.flatlaf.get()
|
|
val jSerialComm = libs.jSerialComm.get()
|
|
val restart4j = libs.restart4j.get()
|
|
val sqlite = libs.sqlite.get()
|
|
|
|
// 对 JNA 和 PTY4J 的本地库提取
|
|
// 提取出来是为了单独签名,不然无法通过公证
|
|
if (os.isMacOsX && macOSSign) {
|
|
doLast {
|
|
val archName = if (arch.isArm) "aarch64" else "x86_64"
|
|
val dylib = dir.get().dir("dylib").asFile
|
|
for (file in dir.get().asFile.listFiles() ?: emptyArray()) {
|
|
if ("${jna.name}-${jna.version}" == file.nameWithoutExtension) {
|
|
val targetDir = File(dylib, jna.name)
|
|
FileUtils.forceMkdir(targetDir)
|
|
// @formatter:off
|
|
exec { commandLine("unzip","-j","-o", file.absolutePath, "com/sun/jna/darwin-${arch.name}/*", "-d", targetDir.absolutePath) }
|
|
// @formatter:on
|
|
// 删除所有二进制类库
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/darwin-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/sunos-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/openbsd-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/linux-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/freebsd-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/dragonflybsd-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/aix-*") }
|
|
} else if ("${pty4j.name}-${pty4j.version}" == file.nameWithoutExtension) {
|
|
val targetDir = FileUtils.getFile(dylib, pty4j.name, "darwin")
|
|
FileUtils.forceMkdir(targetDir)
|
|
// @formatter:off
|
|
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "resources/com/pty4j/native/darwin*", "-d", targetDir.absolutePath) }
|
|
// @formatter:on
|
|
// 删除所有二进制类库
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*") }
|
|
} else if ("${jSerialComm.name}-${jSerialComm.version}" == file.nameWithoutExtension) {
|
|
val targetDir = FileUtils.getFile(dylib, jSerialComm.name, "OSX", archName)
|
|
FileUtils.forceMkdir(targetDir)
|
|
// @formatter:off
|
|
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "OSX/${archName}/*", "-d", targetDir.absolutePath) }
|
|
// @formatter:on
|
|
// 删除所有二进制类库
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Android/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "FreeBSD/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Linux/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "OpenBSD/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "OSX/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Solaris/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Windows/*") }
|
|
} else if ("${restart4j.name}-${restart4j.version}" == file.nameWithoutExtension) {
|
|
val targetDir = FileUtils.getFile(dylib, restart4j.name)
|
|
FileUtils.forceMkdir(targetDir)
|
|
// @formatter:off
|
|
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "darwin/${archName}/*", "-d", targetDir.absolutePath) }
|
|
// @formatter:on
|
|
// 删除所有二进制类库
|
|
exec { commandLine("zip", "-d", file.absolutePath, "win32/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "darwin/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "linux/*") }
|
|
// 设置可执行权限
|
|
for (e in FileUtils.listFiles(
|
|
targetDir,
|
|
FileFilterUtils.trueFileFilter(),
|
|
FileFilterUtils.falseFileFilter()
|
|
)) {
|
|
e.setExecutable(true)
|
|
}
|
|
} else if ("${sqlite.name}-${sqlite.version}" == file.nameWithoutExtension) {
|
|
val targetDir = FileUtils.getFile(dylib, sqlite.name)
|
|
FileUtils.forceMkdir(targetDir)
|
|
// @formatter:off
|
|
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "org/sqlite/native/Mac/${archName}/*", "-d", targetDir.absolutePath) }
|
|
// @formatter:on
|
|
// 删除所有二进制类库
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/*") }
|
|
} else if ("${flatlaf.name}-${flatlaf.version}" == file.nameWithoutExtension) {
|
|
val targetDir = FileUtils.getFile(dylib, flatlaf.name)
|
|
FileUtils.forceMkdir(targetDir)
|
|
val isArm = arch.isArm
|
|
// @formatter:off
|
|
exec { commandLine("unzip", "-j" , "-o", file.absolutePath, "com/formdev/flatlaf/natives/*macos*${if (isArm) "arm" else "x86"}*", "-d", targetDir.absolutePath) }
|
|
// @formatter:on
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*") }
|
|
}
|
|
}
|
|
|
|
// 对二进制签名
|
|
Files.walk(dylib.toPath()).use { paths ->
|
|
for (path in paths) {
|
|
if (Files.isRegularFile(path)) {
|
|
signMacOSLocalFile(path.toFile())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (os.isLinux || os.isWindows) { // 缩减安装包
|
|
doLast {
|
|
for (file in dir.get().asFile.listFiles() ?: emptyArray()) {
|
|
if ("${jna.name}-${jna.version}" == file.nameWithoutExtension) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/darwin-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/sunos-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/openbsd-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/freebsd-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/dragonflybsd-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/aix-*") }
|
|
if (os.isWindows) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/linux-*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-x86*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-aarch64/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-x86/*") }
|
|
}
|
|
} else if (os.isLinux) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/sun/jna/win32-*") }
|
|
}
|
|
} else if ("${pty4j.name}-${pty4j.version}" == file.nameWithoutExtension) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*darwin*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*freebsd*") }
|
|
if (os.isWindows) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*linux*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/x86/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/x86-64*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/x86/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win/aarch64/*") }
|
|
}
|
|
} else if (os.isLinux) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "resources/*win*") }
|
|
}
|
|
} else if ("${jSerialComm.name}-${jSerialComm.version}" == file.nameWithoutExtension) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Android/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "FreeBSD/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "OpenBSD/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "OSX/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Solaris/*") }
|
|
if (os.isWindows) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Linux/*") }
|
|
} else if (os.isLinux) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "Windows/*") }
|
|
}
|
|
} else if ("${restart4j.name}-${restart4j.version}" == file.nameWithoutExtension) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "darwin/*") }
|
|
if (os.isWindows) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "linux/*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "win32/x86_64/*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "win32/aarch64/*") }
|
|
}
|
|
} else if (os.isLinux) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "win32/*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "linux/x86_64/*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "linux/aarch64/*") }
|
|
}
|
|
}
|
|
} else if ("${sqlite.name}-${sqlite.version}" == file.nameWithoutExtension) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux-*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/FreeBSD/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Mac/*") }
|
|
if (os.isWindows) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/armv7/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/x86/*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/x86_64/*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/aarch64/*") }
|
|
}
|
|
} else if (os.isLinux) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Windows/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/arm*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/ppc64/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/riscv64/*") }
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/x86/*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/x86_64/*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "org/sqlite/native/Linux/aarch64/*") }
|
|
}
|
|
}
|
|
} else if ("${flatlaf.name}-${flatlaf.version}" == file.nameWithoutExtension) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*macos*") }
|
|
if (os.isWindows) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*linux*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*x86*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*x86.dll") }
|
|
}
|
|
} else if (os.isLinux) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*windows*") }
|
|
if (arch.isArm) {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*x86*") }
|
|
} else {
|
|
exec { commandLine("zip", "-d", file.absolutePath, "com/formdev/flatlaf/natives/*arm*") }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tasks.register<Exec>("jlink") {
|
|
val modules = listOf(
|
|
"java.base",
|
|
"java.desktop",
|
|
"java.logging",
|
|
"java.management",
|
|
"java.rmi",
|
|
"java.sql",
|
|
"java.security.jgss",
|
|
"jdk.crypto.ec",
|
|
"jdk.unsupported",
|
|
)
|
|
|
|
commandLine(
|
|
"${Jvm.current().javaHome}/bin/jlink",
|
|
"--verbose",
|
|
"--strip-java-debug-attributes",
|
|
"--strip-native-commands",
|
|
"--strip-debug",
|
|
"--compress=zip-9",
|
|
"--no-header-files",
|
|
"--no-man-pages",
|
|
"--add-modules",
|
|
modules.joinToString(","),
|
|
"--output",
|
|
"${layout.buildDirectory.get()}/jlink"
|
|
)
|
|
}
|
|
|
|
tasks.register<Exec>("jpackage") {
|
|
|
|
val buildDir = layout.buildDirectory.get()
|
|
val options = mutableListOf(
|
|
"-Xmx2048m",
|
|
"-XX:+HeapDumpOnOutOfMemoryError",
|
|
"-Dlogger.console.level=off",
|
|
"-Dkotlinx.coroutines.debug=off",
|
|
"-Dapp-version=${project.version}",
|
|
"-Drelease-date=${DateFormatUtils.format(Date(), "yyyy-MM-dd")}",
|
|
"--add-exports java.base/sun.nio.ch=ALL-UNNAMED",
|
|
)
|
|
|
|
options.add("-Dsun.java2d.metal=true")
|
|
|
|
if (os.isMacOsX) {
|
|
// NSWindow
|
|
options.add("-Dapple.awt.application.appearance=system")
|
|
options.add("--add-opens java.desktop/java.awt=ALL-UNNAMED")
|
|
options.add("--add-opens java.desktop/sun.font=ALL-UNNAMED")
|
|
options.add("--add-opens java.desktop/sun.lwawt=ALL-UNNAMED")
|
|
options.add("--add-opens java.desktop/sun.lwawt.macosx=ALL-UNNAMED")
|
|
options.add("--add-opens java.desktop/sun.lwawt.macosx.concurrent=ALL-UNNAMED")
|
|
options.add("--add-exports java.desktop/com.apple.eawt=ALL-UNNAMED")
|
|
}
|
|
|
|
if (os.isLinux) {
|
|
if (isDeb) {
|
|
options.add("-Djpackage.app-layout=deb")
|
|
}
|
|
}
|
|
|
|
val arguments = mutableListOf("${Jvm.current().javaHome}/bin/jpackage")
|
|
arguments.addAll(listOf("--runtime-image", "${buildDir}/jlink"))
|
|
arguments.addAll(listOf("--name", project.name.uppercaseFirstChar()))
|
|
arguments.addAll(listOf("--app-version", appVersion))
|
|
arguments.addAll(listOf("--main-jar", tasks.jar.get().archiveFileName.get()))
|
|
arguments.addAll(listOf("--main-class", application.mainClass.get()))
|
|
arguments.addAll(listOf("--input", "$buildDir/libs"))
|
|
arguments.addAll(listOf("--temp", "$buildDir/jpackage"))
|
|
arguments.addAll(listOf("--dest", "$buildDir/distributions"))
|
|
arguments.addAll(listOf("--java-options", options.joinToString(StringUtils.SPACE)))
|
|
arguments.addAll(listOf("--vendor", "TermoraDev"))
|
|
arguments.addAll(listOf("--copyright", "TermoraDev"))
|
|
arguments.addAll(listOf("--app-content", "$buildDir/plugins"))
|
|
|
|
if (os.isWindows) {
|
|
arguments.addAll(
|
|
listOf(
|
|
"--description",
|
|
"${project.name.uppercaseFirstChar()}: A terminal emulator and SSH client"
|
|
)
|
|
)
|
|
} else {
|
|
arguments.addAll(listOf("--description", "A terminal emulator and SSH client."))
|
|
}
|
|
|
|
|
|
if (os.isMacOsX) {
|
|
arguments.addAll(listOf("--mac-package-name", project.name.uppercaseFirstChar()))
|
|
arguments.addAll(listOf("--mac-app-category", "developer-tools"))
|
|
arguments.addAll(listOf("--mac-package-identifier", "${project.group}"))
|
|
arguments.addAll(listOf("--icon", "${projectDir.absolutePath}/src/main/resources/icons/termora.icns"))
|
|
}
|
|
|
|
if (os.isWindows) {
|
|
arguments.add("--win-dir-chooser")
|
|
arguments.add("--win-shortcut")
|
|
arguments.add("--win-shortcut-prompt")
|
|
arguments.addAll(listOf("--win-upgrade-uuid", "E1D93CAD-5BF8-442E-93BA-6E90DE601E4C"))
|
|
arguments.addAll(listOf("--icon", "${projectDir.absolutePath}/src/main/resources/icons/termora.ico"))
|
|
}
|
|
|
|
if (os.isLinux) {
|
|
arguments.addAll(listOf("--icon", "${projectDir.absolutePath}/src/main/resources/icons/termora.png"))
|
|
}
|
|
|
|
|
|
arguments.add("--type")
|
|
if (os.isMacOsX) {
|
|
arguments.add("dmg")
|
|
} else if (os.isWindows) {
|
|
arguments.add("msi")
|
|
} else if (os.isLinux) {
|
|
arguments.add(if (isDeb) "deb" else "app-image")
|
|
if (isDeb) {
|
|
arguments.add("--linux-deb-maintainer")
|
|
arguments.add("support@termora.app")
|
|
}
|
|
} else {
|
|
throw UnsupportedOperationException()
|
|
}
|
|
|
|
if (os.isMacOsX && macOSSign) {
|
|
arguments.add("--mac-sign")
|
|
arguments.add("--mac-signing-key-user-name")
|
|
arguments.add(macOSSignUsername)
|
|
}
|
|
|
|
commandLine(arguments)
|
|
|
|
}
|
|
|
|
tasks.register("dist") {
|
|
doLast {
|
|
|
|
val gradlew = File(projectDir, if (os.isWindows) "gradlew.bat" else "gradlew").absolutePath
|
|
|
|
// 清空目录
|
|
exec { commandLine(gradlew, "clean") }
|
|
|
|
// 构建自带的插件
|
|
exec { commandLine(gradlew, ":plugins:migration:build") }
|
|
|
|
// 打包并复制依赖
|
|
exec {
|
|
commandLine(gradlew, ":jar", ":copy-dependencies")
|
|
}
|
|
|
|
// 检查依赖的开源协议
|
|
exec { commandLine(gradlew, ":check-license") }
|
|
|
|
// jlink
|
|
exec { commandLine(gradlew, ":jlink") }
|
|
|
|
// 打包
|
|
exec { commandLine(gradlew, ":jpackage", "-Dtype=${System.getProperty("type")}") }
|
|
|
|
// 根据不同的系统构建不同的二进制包
|
|
pack()
|
|
}
|
|
}
|
|
|
|
tasks.register("check-license") {
|
|
doLast {
|
|
val iterator = File(projectDir, "THIRDPARTY").readLines().iterator()
|
|
val thirdPartyNames = mutableSetOf<String>()
|
|
|
|
while (iterator.hasNext()) {
|
|
val name = iterator.next()
|
|
if (name.isBlank()) {
|
|
continue
|
|
}
|
|
|
|
// ignore license name
|
|
iterator.next()
|
|
// ignore license url
|
|
iterator.next()
|
|
|
|
thirdPartyNames.add(name)
|
|
}
|
|
|
|
for (dependency in configurations.runtimeClasspath.get().allDependencies) {
|
|
if (!thirdPartyNames.contains(dependency.name)) {
|
|
throw GradleException("${dependency.name} No license found")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 构建包
|
|
*/
|
|
fun pack() {
|
|
val osName = if (os.isMacOsX) "osx" else if (os.isWindows) "windows" else "linux"
|
|
val distributionDir = layout.buildDirectory.dir("distributions").get()
|
|
val finalFilenameWithoutExtension = "${project.name}-${project.version}-${osName}-${arch.name}"
|
|
val projectName = project.name.uppercaseFirstChar()
|
|
|
|
if (os.isWindows) {
|
|
packOnWindows(distributionDir, finalFilenameWithoutExtension, projectName)
|
|
} else if (os.isLinux) {
|
|
packOnLinux(distributionDir, finalFilenameWithoutExtension, projectName)
|
|
} else if (os.isMacOsX) {
|
|
packOnMac(distributionDir, finalFilenameWithoutExtension, projectName)
|
|
} else {
|
|
throw GradleException("${os.name} is not supported")
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 创建 zip、msi
|
|
*/
|
|
fun packOnWindows(distributionDir: Directory, finalFilenameWithoutExtension: String, projectName: String) {
|
|
val dir = layout.buildDirectory.dir("jpackage/images/win-msi.image/").get().asFile
|
|
val cfg = FileUtils.getFile(dir, projectName, "app", "${projectName}.cfg")
|
|
val configText = cfg.readText()
|
|
|
|
// zip
|
|
cfg.writeText(StringBuilder(configText).appendLine("java-options=-Djpackage.app-layout=zip").toString())
|
|
exec {
|
|
commandLine(
|
|
"tar", "-vacf",
|
|
distributionDir.file("${finalFilenameWithoutExtension}.zip").asFile.absolutePath,
|
|
projectName
|
|
)
|
|
workingDir = dir
|
|
}
|
|
|
|
// exe
|
|
cfg.writeText(StringBuilder(configText).appendLine("java-options=-Djpackage.app-layout=exe").toString())
|
|
exec {
|
|
commandLine(
|
|
"iscc",
|
|
"/DMyAppId=${projectName}",
|
|
"/DMyAppName=${projectName}",
|
|
"/DMyAppVersion=${appVersion}",
|
|
"/DMyOutputDir=${distributionDir.asFile.absolutePath}",
|
|
"/DMySetupIconFile=${FileUtils.getFile(projectDir, "src", "main", "resources", "icons", "termora.ico")}",
|
|
"/DMySourceDir=${layout.buildDirectory.dir("jpackage/images/win-msi.image/${projectName}").get().asFile}",
|
|
"/F${finalFilenameWithoutExtension}",
|
|
FileUtils.getFile(projectDir, "src", "main", "resources", "termora.iss")
|
|
)
|
|
}
|
|
|
|
// msi
|
|
exec {
|
|
commandLine(
|
|
"cmd", "/c", "move",
|
|
"${projectName}-${appVersion}.msi",
|
|
"${finalFilenameWithoutExtension}.msi"
|
|
)
|
|
workingDir = distributionDir.asFile
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 对于 macOS 先对 jpackage 构建的 dmg 重命名 -> 签名 -> 公证,另外还会创建一个 zip 包
|
|
*/
|
|
fun packOnMac(distributionDir: Directory, finalFilenameWithoutExtension: String, projectName: String) {
|
|
val dmgFile = distributionDir.file("${finalFilenameWithoutExtension}.dmg").asFile
|
|
val zipFile = distributionDir.file("${finalFilenameWithoutExtension}.zip").asFile
|
|
|
|
// rename
|
|
// @formatter:off
|
|
exec { commandLine("mv", distributionDir.file("${projectName}-${appVersion}.dmg").asFile.absolutePath, dmgFile.absolutePath,) }
|
|
// @formatter:on
|
|
|
|
// sign dmg
|
|
if (macOSSign) signMacOSLocalFile(dmgFile)
|
|
|
|
// 找到 .app
|
|
val imageFile = layout.buildDirectory.dir("jpackage/images/").get().asFile
|
|
val appFile = imageFile.listFiles()?.firstOrNull()?.listFiles()?.firstOrNull()
|
|
?: throw FileNotFoundException("${projectName}.app")
|
|
|
|
// zip
|
|
// @formatter:off
|
|
exec { commandLine("ditto", "-c", "-k", "--sequesterRsrc", "--keepParent", appFile.absolutePath, zipFile.absolutePath) }
|
|
// @formatter:on
|
|
|
|
// sign zip
|
|
if (macOSSign) signMacOSLocalFile(zipFile)
|
|
|
|
// 公证
|
|
if (macOSNotary) {
|
|
val pool = Executors.newCachedThreadPool()
|
|
val jobs = mutableListOf<Future<*>>()
|
|
|
|
// zip
|
|
pool.submit {
|
|
// 对 zip 公证
|
|
notaryMacOSLocalFile(zipFile)
|
|
// 对 .app 盖章
|
|
stapleMacOSLocalFile(appFile)
|
|
// 删除旧的 zip ,旧的 zip 仅仅是为了公证
|
|
FileUtils.deleteQuietly(zipFile)
|
|
// 再对盖完章的 app 打成 zip 包
|
|
// @formatter:off
|
|
exec { commandLine("ditto", "-c", "-k", "--sequesterRsrc", "--keepParent", appFile.absolutePath, zipFile.absolutePath) }
|
|
// @formatter:on
|
|
// 再对 zip 签名
|
|
signMacOSLocalFile(zipFile)
|
|
}.apply { jobs.add(this) }
|
|
|
|
// dmg
|
|
pool.submit {
|
|
// 公证
|
|
notaryMacOSLocalFile(dmgFile)
|
|
// 盖章
|
|
stapleMacOSLocalFile(dmgFile)
|
|
}.apply { jobs.add(this) }
|
|
|
|
// join ...
|
|
jobs.forEach { it.get() }
|
|
|
|
// shutdown
|
|
pool.shutdown()
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* 创建 tar.gz 和 AppImage
|
|
*/
|
|
fun packOnLinux(distributionDir: Directory, finalFilenameWithoutExtension: String, projectName: String) {
|
|
|
|
if (isDeb) {
|
|
val arch = if (arch.isArm) "arm" else "amd"
|
|
distributionDir.file("${project.name}_${appVersion}_${arch}64.deb").asFile
|
|
.renameTo(distributionDir.file("${finalFilenameWithoutExtension}.deb").asFile)
|
|
return
|
|
}
|
|
|
|
val cfg = FileUtils.getFile(distributionDir.asFile, projectName, "lib", "app", "${projectName}.cfg")
|
|
val configText = cfg.readText()
|
|
|
|
// tar.gz
|
|
cfg.writeText(StringBuilder(configText).appendLine("java-options=-Djpackage.app-layout=tar.gz").toString())
|
|
exec {
|
|
commandLine(
|
|
"tar", "-czvf",
|
|
distributionDir.file("${finalFilenameWithoutExtension}.tar.gz").asFile.absolutePath,
|
|
projectName
|
|
)
|
|
workingDir = distributionDir.asFile
|
|
}
|
|
|
|
|
|
// AppImage
|
|
// Download AppImageKit
|
|
val appimagetool = FileUtils.getFile(projectDir, ".gradle", "appimagetool")
|
|
if (!appimagetool.exists()) {
|
|
exec {
|
|
commandLine(
|
|
"wget",
|
|
"-O", appimagetool.absolutePath,
|
|
"https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${if (arch.isArm) "aarch64" else "x86_64"}.AppImage"
|
|
)
|
|
workingDir = distributionDir.asFile
|
|
}
|
|
|
|
// AppImageKit chmod
|
|
exec { commandLine("chmod", "+x", appimagetool.absolutePath) }
|
|
}
|
|
|
|
|
|
// Desktop file
|
|
val termoraName = project.name.uppercaseFirstChar()
|
|
val desktopFile = distributionDir.file(termoraName + File.separator + termoraName + ".desktop").asFile
|
|
desktopFile.writeText(
|
|
"""[Desktop Entry]
|
|
Type=Application
|
|
Name=${termoraName}
|
|
Comment=Terminal emulator and SSH client
|
|
Icon=/lib/${termoraName}
|
|
Categories=Development;
|
|
Terminal=false
|
|
""".trimIndent()
|
|
)
|
|
|
|
// AppRun file
|
|
val appRun = File(desktopFile.parentFile, "AppRun")
|
|
val sb = StringBuilder()
|
|
sb.append("#!/bin/sh").appendLine()
|
|
sb.append("SELF=$(readlink -f \"$0\")").appendLine()
|
|
sb.append("HERE=\${SELF%/*}").appendLine()
|
|
sb.append("export LinuxAppImage=true").appendLine()
|
|
sb.append("exec \"\${HERE}/bin/${termoraName}\" \"$@\"")
|
|
appRun.writeText(sb.toString())
|
|
appRun.setExecutable(true)
|
|
|
|
// AppImage
|
|
cfg.writeText(StringBuilder(configText).appendLine("java-options=-Djpackage.app-layout=AppImage").toString())
|
|
exec {
|
|
commandLine(appimagetool.absolutePath, termoraName, "${finalFilenameWithoutExtension}.AppImage")
|
|
workingDir = distributionDir.asFile
|
|
}
|
|
}
|
|
|
|
/**
|
|
* macOS 对本地文件进行签名
|
|
*/
|
|
fun signMacOSLocalFile(file: File) {
|
|
if (os.isMacOsX && macOSSign) {
|
|
if (file.exists() && file.isFile) {
|
|
exec {
|
|
commandLine(
|
|
"/usr/bin/codesign",
|
|
"-s", macOSSignUsername,
|
|
"--timestamp", "--force",
|
|
"-vvvv", "--options", "runtime",
|
|
file.absolutePath,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* macOS 对本地文件进行公证
|
|
*/
|
|
fun notaryMacOSLocalFile(file: File) {
|
|
if (os.isMacOsX && macOSNotary) {
|
|
if (file.exists()) {
|
|
exec {
|
|
commandLine(
|
|
"/usr/bin/xcrun", "notarytool",
|
|
"submit", file,
|
|
"--keychain-profile", macOSNotaryKeychainProfile,
|
|
"--wait",
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 盖章
|
|
*/
|
|
fun stapleMacOSLocalFile(file: File) {
|
|
if (os.isMacOsX && macOSNotary) {
|
|
if (file.exists()) {
|
|
exec {
|
|
commandLine(
|
|
"/usr/bin/xcrun",
|
|
"stapler", "staple", file,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
kotlin {
|
|
jvmToolchain {
|
|
languageVersion = JavaLanguageVersion.of(21)
|
|
}
|
|
}
|
|
|
|
java {
|
|
withSourcesJar()
|
|
}
|
|
|
|
idea {
|
|
module {
|
|
isDownloadJavadoc = true
|
|
isDownloadSources = true
|
|
}
|
|
} |