mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: 支持 macOS 签名以及 Windows MSI 安装包 (#71)
This commit is contained in:
@@ -46,6 +46,10 @@ flatlaf 3.5.4
|
|||||||
Apache License 2.0
|
Apache License 2.0
|
||||||
https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE
|
https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE
|
||||||
|
|
||||||
|
flatlaf 3.5.4-no-natives
|
||||||
|
Apache License 2.0
|
||||||
|
https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE
|
||||||
|
|
||||||
flatlaf-extras 3.5.4
|
flatlaf-extras 3.5.4
|
||||||
Apache License 2.0
|
Apache License 2.0
|
||||||
https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE
|
https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE
|
||||||
|
|||||||
183
build.gradle.kts
183
build.gradle.kts
@@ -1,8 +1,7 @@
|
|||||||
import org.gradle.internal.jvm.Jvm
|
import org.gradle.internal.jvm.Jvm
|
||||||
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
|
||||||
import org.gradle.nativeplatform.platform.internal.ArchitectureInternal
|
|
||||||
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
|
||||||
import org.gradle.nativeplatform.platform.internal.DefaultOperatingSystem
|
import org.jetbrains.kotlin.org.apache.commons.io.FileUtils
|
||||||
import org.jetbrains.kotlin.org.apache.commons.lang3.StringUtils
|
import org.jetbrains.kotlin.org.apache.commons.lang3.StringUtils
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -16,9 +15,18 @@ plugins {
|
|||||||
group = "app.termora"
|
group = "app.termora"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
||||||
val os: DefaultOperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
||||||
var arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture()
|
val arch: Architecture = DefaultNativePlatform.getCurrentArchitecture()
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -27,6 +35,9 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// 由于签名和公证,macOS 不携带 natives
|
||||||
|
val useNoNativesFlatLaf = os.isMacOsX && System.getenv("ENABLE_BUILD").toBoolean()
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
testImplementation(libs.hutool)
|
testImplementation(libs.hutool)
|
||||||
testImplementation(libs.sshj)
|
testImplementation(libs.sshj)
|
||||||
@@ -50,9 +61,25 @@ dependencies {
|
|||||||
implementation(libs.commons.compress)
|
implementation(libs.commons.compress)
|
||||||
implementation(libs.kotlinx.coroutines.swing)
|
implementation(libs.kotlinx.coroutines.swing)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.flatlaf)
|
|
||||||
implementation(libs.flatlaf.extras)
|
implementation(libs.flatlaf) {
|
||||||
implementation(libs.flatlaf.swingx)
|
artifact {
|
||||||
|
if (useNoNativesFlatLaf) {
|
||||||
|
classifier = "no-natives"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
implementation(libs.flatlaf.extras) {
|
||||||
|
if (useNoNativesFlatLaf) {
|
||||||
|
exclude(group = "com.formdev", module = "flatlaf")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
implementation(libs.flatlaf.swingx) {
|
||||||
|
if (useNoNativesFlatLaf) {
|
||||||
|
exclude(group = "com.formdev", module = "flatlaf")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.swingx)
|
implementation(libs.swingx)
|
||||||
implementation(libs.jgoodies.forms)
|
implementation(libs.jgoodies.forms)
|
||||||
@@ -104,8 +131,44 @@ tasks.test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Copy>("copy-dependencies") {
|
tasks.register<Copy>("copy-dependencies") {
|
||||||
from(configurations.runtimeClasspath)
|
val dir = layout.buildDirectory.dir("libs")
|
||||||
.into("${layout.buildDirectory.get()}/libs")
|
from(configurations.runtimeClasspath).into(dir)
|
||||||
|
|
||||||
|
// 对 JNA 和 PTY4J 的本地库提取
|
||||||
|
// 提取出来是为了单独签名,不然无法通过公证
|
||||||
|
if (os.isMacOsX) {
|
||||||
|
doLast {
|
||||||
|
val jna = libs.jna.asProvider().get()
|
||||||
|
val dylib = dir.get().dir("dylib").asFile
|
||||||
|
val pty4j = libs.pty4j.get()
|
||||||
|
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/*") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Exec>("jlink") {
|
tasks.register<Exec>("jlink") {
|
||||||
@@ -137,6 +200,7 @@ tasks.register<Exec>("jlink") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Exec>("jpackage") {
|
tasks.register<Exec>("jpackage") {
|
||||||
|
|
||||||
val buildDir = layout.buildDirectory.get()
|
val buildDir = layout.buildDirectory.get()
|
||||||
val options = mutableListOf(
|
val options = mutableListOf(
|
||||||
"--add-exports java.base/sun.nio.ch=ALL-UNNAMED",
|
"--add-exports java.base/sun.nio.ch=ALL-UNNAMED",
|
||||||
@@ -165,6 +229,9 @@ tasks.register<Exec>("jpackage") {
|
|||||||
arguments.addAll(listOf("--temp", "$buildDir/jpackage"))
|
arguments.addAll(listOf("--temp", "$buildDir/jpackage"))
|
||||||
arguments.addAll(listOf("--dest", "$buildDir/distributions"))
|
arguments.addAll(listOf("--dest", "$buildDir/distributions"))
|
||||||
arguments.addAll(listOf("--java-options", options.joinToString(StringUtils.SPACE)))
|
arguments.addAll(listOf("--java-options", options.joinToString(StringUtils.SPACE)))
|
||||||
|
arguments.addAll(listOf("--vendor", "TermoraDev"))
|
||||||
|
arguments.addAll(listOf("--copyright", "TermoraDev"))
|
||||||
|
arguments.addAll(listOf("--description", "A terminal emulator and SSH client."))
|
||||||
|
|
||||||
|
|
||||||
if (os.isMacOsX) {
|
if (os.isMacOsX) {
|
||||||
@@ -194,6 +261,12 @@ tasks.register<Exec>("jpackage") {
|
|||||||
throw UnsupportedOperationException()
|
throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (os.isMacOsX && macOSSign) {
|
||||||
|
arguments.add("--mac-sign")
|
||||||
|
arguments.add("--mac-signing-key-user-name")
|
||||||
|
arguments.add(macOSSignUsername)
|
||||||
|
}
|
||||||
|
|
||||||
commandLine(arguments)
|
commandLine(arguments)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -201,53 +274,113 @@ tasks.register<Exec>("jpackage") {
|
|||||||
tasks.register("dist") {
|
tasks.register("dist") {
|
||||||
doLast {
|
doLast {
|
||||||
val vendor = Jvm.current().vendor ?: StringUtils.EMPTY
|
val vendor = Jvm.current().vendor ?: StringUtils.EMPTY
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage") if (!JvmVendorSpec.JETBRAINS.matches(vendor)) {
|
||||||
if (!JvmVendorSpec.JETBRAINS.matches(vendor)) {
|
|
||||||
throw GradleException("JVM: $vendor is not supported")
|
throw GradleException("JVM: $vendor is not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
val distributionDir = layout.buildDirectory.dir("distributions").get()
|
val distributionDir = layout.buildDirectory.dir("distributions").get()
|
||||||
val gradlew = File(projectDir, if (os.isWindows) "gradlew.bat" else "gradlew").absolutePath
|
val gradlew = File(projectDir, if (os.isWindows) "gradlew.bat" else "gradlew").absolutePath
|
||||||
|
val osName = if (os.isMacOsX) "osx" else if (os.isWindows) "windows" else "linux"
|
||||||
|
val finalFilenameWithoutExtension = "${project.name}-${project.version}-${osName}-${arch.name}"
|
||||||
|
val macOSFinalFilePath = distributionDir.file("${finalFilenameWithoutExtension}.dmg").asFile.absolutePath
|
||||||
|
|
||||||
// 清空目录
|
// 清空目录
|
||||||
exec { commandLine(gradlew, "clean") }
|
exec {
|
||||||
|
commandLine(gradlew, "clean")
|
||||||
|
}
|
||||||
|
|
||||||
// 打包并复制依赖
|
// 打包并复制依赖
|
||||||
exec { commandLine(gradlew, "jar", "copy-dependencies") }
|
exec {
|
||||||
|
commandLine(gradlew, "jar", "copy-dependencies")
|
||||||
|
environment("ENABLE_BUILD" to true)
|
||||||
|
}
|
||||||
|
|
||||||
// 检查依赖的开源协议
|
// 检查依赖的开源协议
|
||||||
exec { commandLine(gradlew, "check-license") }
|
exec { commandLine(gradlew, "check-license") }
|
||||||
|
|
||||||
// jlink
|
// jlink
|
||||||
exec { commandLine(gradlew, "jlink") }
|
exec {
|
||||||
|
commandLine(gradlew, "jlink")
|
||||||
|
environment("ENABLE_BUILD" to true)
|
||||||
|
}
|
||||||
|
|
||||||
// 打包
|
// 打包
|
||||||
exec { commandLine(gradlew, "jpackage") }
|
exec { commandLine(gradlew, "jpackage") }
|
||||||
|
|
||||||
// pack
|
// pack
|
||||||
exec {
|
if (os.isWindows) { // zip and msi
|
||||||
if (os.isWindows) { // zip
|
// zip
|
||||||
|
exec {
|
||||||
commandLine(
|
commandLine(
|
||||||
"tar", "-vacf",
|
"tar",
|
||||||
distributionDir.file("${project.name}-${project.version}-windows-${arch.name}.zip").asFile.absolutePath,
|
"-vacf",
|
||||||
|
distributionDir.file("${finalFilenameWithoutExtension}.zip").asFile.absolutePath,
|
||||||
project.name.uppercaseFirstChar()
|
project.name.uppercaseFirstChar()
|
||||||
)
|
)
|
||||||
workingDir = layout.buildDirectory.dir("jpackage/images/win-msi.image/").get().asFile
|
workingDir = layout.buildDirectory.dir("jpackage/images/win-msi.image/").get().asFile
|
||||||
} else if (os.isLinux) { // tar.gz
|
}
|
||||||
|
|
||||||
|
// msi
|
||||||
|
exec {
|
||||||
commandLine(
|
commandLine(
|
||||||
"tar", "-czvf",
|
"cmd", "/c", "move",
|
||||||
distributionDir.file("${project.name}-${project.version}-linux-${arch.name}.tar.gz").asFile.absolutePath,
|
"${project.name.uppercaseFirstChar()}-${project.version}.msi",
|
||||||
|
"${finalFilenameWithoutExtension}.msi"
|
||||||
|
)
|
||||||
|
workingDir = distributionDir.asFile
|
||||||
|
}
|
||||||
|
} else if (os.isLinux) { // tar.gz
|
||||||
|
exec {
|
||||||
|
commandLine(
|
||||||
|
"tar",
|
||||||
|
"-czvf",
|
||||||
|
distributionDir.file("${finalFilenameWithoutExtension}.tar.gz").asFile.absolutePath,
|
||||||
project.name.uppercaseFirstChar()
|
project.name.uppercaseFirstChar()
|
||||||
)
|
)
|
||||||
workingDir = distributionDir.asFile
|
workingDir = distributionDir.asFile
|
||||||
} else if (os.isMacOsX) { // rename
|
}
|
||||||
|
} else if (os.isMacOsX) { // rename
|
||||||
|
exec {
|
||||||
commandLine(
|
commandLine(
|
||||||
"mv",
|
"mv",
|
||||||
distributionDir.file("${project.name.uppercaseFirstChar()}-${project.version}.dmg").asFile.absolutePath,
|
distributionDir.file("${project.name.uppercaseFirstChar()}-${project.version}.dmg").asFile.absolutePath,
|
||||||
distributionDir.file("${project.name}-${project.version}-osx-${arch.name}.dmg").asFile.absolutePath,
|
macOSFinalFilePath,
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
throw GradleException("${os.name} is not supported")
|
} else {
|
||||||
|
throw GradleException("${os.name} is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// sign dmg
|
||||||
|
if (os.isMacOsX && macOSSign) {
|
||||||
|
exec {
|
||||||
|
commandLine(
|
||||||
|
"/usr/bin/codesign",
|
||||||
|
"-s",
|
||||||
|
macOSSignUsername,
|
||||||
|
"--timestamp",
|
||||||
|
"--force",
|
||||||
|
"-vvvv",
|
||||||
|
"--options",
|
||||||
|
"runtime",
|
||||||
|
macOSFinalFilePath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公证
|
||||||
|
if (macOSNotary) {
|
||||||
|
exec {
|
||||||
|
commandLine(
|
||||||
|
"/usr/bin/xcrun",
|
||||||
|
"notarytool",
|
||||||
|
"submit",
|
||||||
|
macOSFinalFilePath,
|
||||||
|
"--keychain-profile",
|
||||||
|
macOSNotaryKeychainProfile,
|
||||||
|
"--wait",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,44 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
|
import com.pty4j.util.PtyUtil
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import org.apache.commons.lang3.SystemUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
|
// 由于 macOS 签名和公证问题,依赖二进制依赖会单独在一个文件夹
|
||||||
|
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||||
|
setupNativeLibraries()
|
||||||
|
}
|
||||||
|
|
||||||
ApplicationRunner().run()
|
ApplicationRunner().run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun setupNativeLibraries() {
|
||||||
|
if (!SystemUtils.IS_OS_MAC_OSX) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val appPath = Application.getAppPath()
|
||||||
|
if (StringUtils.isBlank(appPath)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val contents = File(appPath).parentFile?.parentFile ?: return
|
||||||
|
val dylib = FileUtils.getFile(contents, "app", "dylib")
|
||||||
|
if (!dylib.exists()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val jna = FileUtils.getFile(dylib, "jna")
|
||||||
|
if (jna.exists()) {
|
||||||
|
System.setProperty("jna.boot.library.path", jna.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pty4j = FileUtils.getFile(dylib, "pty4j")
|
||||||
|
if (pty4j.exists()) {
|
||||||
|
System.setProperty(PtyUtil.PREFERRED_NATIVE_FOLDER_KEY, pty4j.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,7 +88,6 @@ class TerminalLoggerAction : AnAction(I18n.getString("termora.terminal-logger"),
|
|||||||
}
|
}
|
||||||
|
|
||||||
fc.defaultDirectory = getLogDir().absolutePath
|
fc.defaultDirectory = getLogDir().absolutePath
|
||||||
println(fc.defaultDirectory)
|
|
||||||
fc.showOpenDialog(owner).thenAccept { files ->
|
fc.showOpenDialog(owner).thenAccept { files ->
|
||||||
if (files.isNotEmpty()) {
|
if (files.isNotEmpty()) {
|
||||||
SwingUtilities.invokeLater {
|
SwingUtilities.invokeLater {
|
||||||
|
|||||||
Reference in New Issue
Block a user