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
|
||||
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
|
||||
Apache License 2.0
|
||||
https://github.com/JFormDesigner/FlatLaf/blob/main/LICENSE
|
||||
|
||||
175
build.gradle.kts
175
build.gradle.kts
@@ -1,8 +1,7 @@
|
||||
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.gradle.nativeplatform.platform.internal.DefaultOperatingSystem
|
||||
import org.jetbrains.kotlin.org.apache.commons.io.FileUtils
|
||||
import org.jetbrains.kotlin.org.apache.commons.lang3.StringUtils
|
||||
|
||||
plugins {
|
||||
@@ -16,9 +15,18 @@ plugins {
|
||||
group = "app.termora"
|
||||
version = "1.0.1"
|
||||
|
||||
val os: DefaultOperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
||||
var arch: ArchitectureInternal = DefaultNativePlatform.getCurrentArchitecture()
|
||||
val os: OperatingSystem = DefaultNativePlatform.getCurrentOperatingSystem()
|
||||
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 {
|
||||
mavenCentral()
|
||||
@@ -27,6 +35,9 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// 由于签名和公证,macOS 不携带 natives
|
||||
val useNoNativesFlatLaf = os.isMacOsX && System.getenv("ENABLE_BUILD").toBoolean()
|
||||
|
||||
testImplementation(kotlin("test"))
|
||||
testImplementation(libs.hutool)
|
||||
testImplementation(libs.sshj)
|
||||
@@ -50,9 +61,25 @@ dependencies {
|
||||
implementation(libs.commons.compress)
|
||||
implementation(libs.kotlinx.coroutines.swing)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.flatlaf)
|
||||
implementation(libs.flatlaf.extras)
|
||||
implementation(libs.flatlaf.swingx)
|
||||
|
||||
implementation(libs.flatlaf) {
|
||||
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.swingx)
|
||||
implementation(libs.jgoodies.forms)
|
||||
@@ -104,8 +131,44 @@ tasks.test {
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copy-dependencies") {
|
||||
from(configurations.runtimeClasspath)
|
||||
.into("${layout.buildDirectory.get()}/libs")
|
||||
val dir = layout.buildDirectory.dir("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") {
|
||||
@@ -137,6 +200,7 @@ tasks.register<Exec>("jlink") {
|
||||
}
|
||||
|
||||
tasks.register<Exec>("jpackage") {
|
||||
|
||||
val buildDir = layout.buildDirectory.get()
|
||||
val options = mutableListOf(
|
||||
"--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("--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("--description", "A terminal emulator and SSH client."))
|
||||
|
||||
|
||||
if (os.isMacOsX) {
|
||||
@@ -194,6 +261,12 @@ tasks.register<Exec>("jpackage") {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
if (os.isMacOsX && macOSSign) {
|
||||
arguments.add("--mac-sign")
|
||||
arguments.add("--mac-signing-key-user-name")
|
||||
arguments.add(macOSSignUsername)
|
||||
}
|
||||
|
||||
commandLine(arguments)
|
||||
|
||||
}
|
||||
@@ -201,54 +274,114 @@ tasks.register<Exec>("jpackage") {
|
||||
tasks.register("dist") {
|
||||
doLast {
|
||||
val vendor = Jvm.current().vendor ?: StringUtils.EMPTY
|
||||
@Suppress("UnstableApiUsage")
|
||||
if (!JvmVendorSpec.JETBRAINS.matches(vendor)) {
|
||||
@Suppress("UnstableApiUsage") if (!JvmVendorSpec.JETBRAINS.matches(vendor)) {
|
||||
throw GradleException("JVM: $vendor is not supported")
|
||||
}
|
||||
|
||||
val distributionDir = layout.buildDirectory.dir("distributions").get()
|
||||
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") }
|
||||
|
||||
// jlink
|
||||
exec { commandLine(gradlew, "jlink") }
|
||||
exec {
|
||||
commandLine(gradlew, "jlink")
|
||||
environment("ENABLE_BUILD" to true)
|
||||
}
|
||||
|
||||
// 打包
|
||||
exec { commandLine(gradlew, "jpackage") }
|
||||
|
||||
// pack
|
||||
if (os.isWindows) { // zip and msi
|
||||
// zip
|
||||
exec {
|
||||
if (os.isWindows) { // zip
|
||||
commandLine(
|
||||
"tar", "-vacf",
|
||||
distributionDir.file("${project.name}-${project.version}-windows-${arch.name}.zip").asFile.absolutePath,
|
||||
"tar",
|
||||
"-vacf",
|
||||
distributionDir.file("${finalFilenameWithoutExtension}.zip").asFile.absolutePath,
|
||||
project.name.uppercaseFirstChar()
|
||||
)
|
||||
workingDir = layout.buildDirectory.dir("jpackage/images/win-msi.image/").get().asFile
|
||||
} else if (os.isLinux) { // tar.gz
|
||||
}
|
||||
|
||||
// msi
|
||||
exec {
|
||||
commandLine(
|
||||
"tar", "-czvf",
|
||||
distributionDir.file("${project.name}-${project.version}-linux-${arch.name}.tar.gz").asFile.absolutePath,
|
||||
"cmd", "/c", "move",
|
||||
"${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()
|
||||
)
|
||||
workingDir = distributionDir.asFile
|
||||
}
|
||||
} else if (os.isMacOsX) { // rename
|
||||
exec {
|
||||
commandLine(
|
||||
"mv",
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
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() {
|
||||
// 由于 macOS 签名和公证问题,依赖二进制依赖会单独在一个文件夹
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
setupNativeLibraries()
|
||||
}
|
||||
|
||||
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
|
||||
println(fc.defaultDirectory)
|
||||
fc.showOpenDialog(owner).thenAccept { files ->
|
||||
if (files.isNotEmpty()) {
|
||||
SwingUtilities.invokeLater {
|
||||
|
||||
Reference in New Issue
Block a user