原生發佈
您將在這裡了解原生發佈:如何為所有支援的系統建立安裝程式和套件,以及如何以與發佈相同的設定在本機執行應用程式。
請繼續閱讀以下主題的詳細資訊:
- 什麼是 Compose Multiplatform Gradle plugin?
- 基本任務的詳細資訊,例如在本機執行應用程式,以及進階任務,例如縮小化和混淆處理。
- 如何包含 JDK 模組並處理
ClassNotFoundException
。 - 如何指定發佈屬性:套件版本、JDK 版本、輸出目錄、啟動器屬性和中繼資料。
- 如何管理資源,透過資源庫、JVM 資源載入或將檔案新增至已封裝應用程式。
- 如何自訂原始碼集,透過 Gradle 原始碼集、Kotlin JVM 目標或手動方式。
- 如何為每個作業系統指定應用程式圖示。
- 平台特定選項,例如 Linux 上套件維護者的電子郵件,以及 macOS 上 Apple App Store 的應用程式類別。
- macOS 特定配置:簽章、公證和
Info.plist
。
Gradle plugin
本指南主要著重於使用 Compose Multiplatform Gradle plugin 封裝 Compose 應用程式。 org.jetbrains.compose
plugin 提供基本封裝、混淆處理和 macOS 程式碼簽章的任務。
此 plugin 簡化了使用 jpackage
將應用程式封裝為原生發佈以及在本機執行應用程式的流程。 可發佈的應用程式是自我包含、可安裝的二進位檔,其中包含所有必要的 Java 執行階段元件,無需在目標系統上安裝 JDK。
為了最小化套件大小,Gradle plugin 使用 jlink 工具,該工具確保只將必要的 Java 模組捆綁在可發佈套件中。 但是,您仍然必須配置 Gradle plugin 以指定您需要的模組。 更多資訊請參閱 undefined 章節。
或者,您也可以使用 Conveyor,這是一個非 JetBrains 開發的外部工具。 Conveyor 支援線上更新、跨平台建置和各種其他功能,但對於非開放原始碼專案需要授權。 更多資訊請參考 Conveyor 文件。
基本任務
Compose Multiplatform Gradle plugin 中最基本的配置單元是 application
(不要與已棄用的 Gradle application plugin 混淆)。
application
DSL 方法定義了一組最終二進位檔的共用配置,這表示它允許您將一組檔案連同 JDK 發佈捆綁成各種格式的壓縮二進位安裝程式。
以下格式適用於支援的作業系統:
- macOS:
.dmg
(TargetFormat.Dmg
)、.pkg
(TargetFormat.Pkg
) - Windows:
.exe
(TargetFormat.Exe
)、.msi
(TargetFormat.Msi
) - Linux:
.deb
(TargetFormat.Deb
)、.rpm
(TargetFormat.Rpm
)
以下是帶有基本桌面配置的 build.gradle.kts
檔案範例:
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
plugins {
kotlin("jvm")
id("org.jetbrains.compose")
}
dependencies {
implementation(compose.desktop.currentOs)
}
compose.desktop {
application {
mainClass = "example.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Exe)
}
}
}
當您建置專案時,plugin 會建立以下任務:
Gradle task | Description |
package<FormatName> | 將應用程式封裝成對應的 FormatName 二進位檔。目前不支援跨平台編譯, 這表示您只能使用對應的相容作業系統建置特定格式。 例如,要建置 .dmg 二進位檔,您必須在 macOS 上執行 packageDmg 任務。 如果任何任務與目前的作業系統不相容,它們會預設跳過。 |
packageDistributionForCurrentOS | 彙總應用程式的所有封裝任務。它是一個 生命週期任務。 |
packageUberJarForCurrentOS | 建立一個包含所有適用於目前作業系統之依賴項的單一 `jar` 檔案。 此任務預期將 compose.desktop.currentOS 用作 compile 、implementation 或 runtime 依賴項。 |
run | 從 mainClass 中指定的入口點在本機執行應用程式。run 任務會啟動一個具有完整執行階段的非封裝 JVM 應用程式。 與建立帶有縮小化執行階段的緊湊二進位映像相比,此方法更快且更容易偵錯。 要執行最終二進位映像,請改用 runDistributable 任務。 |
createDistributable | 建立最終應用程式映像而不建立安裝程式。 |
runDistributable | 執行預先封裝的應用程式映像。 |
所有可用的任務都列在 Gradle 工具視窗中。執行任務後,Gradle 會在 ${project.buildDir}/compose/binaries
目錄中生成輸出二進位檔。
包含 JDK 模組
為了減少可發佈的大小,Gradle plugin 使用 jlink 來幫助捆綁只必要的 JDK 模組。
目前,Gradle plugin 不會自動判斷必要的 JDK 模組。雖然這不會導致編譯問題, 但無法提供必要的模組可能導致執行階段的 ClassNotFoundException
。
如果您在執行已封裝應用程式或 runDistributable
任務時遇到 ClassNotFoundException
, 您可以使用 modules
DSL 方法包含額外的 JDK 模組:
compose.desktop {
application {
nativeDistributions {
modules("java.sql")
// Alternatively: includeAllModules = true
}
}
}
您可以手動指定所需的模組,或執行 suggestModules
。suggestModules
任務使用 jdeps 靜態分析工具來判斷可能遺失的模組。 請注意,此工具的輸出可能不完整或列出不必要的模組。
如果可發佈的大小不是關鍵因素且可以忽略,您可以選擇透過使用 includeAllModules
DSL 屬性來包含所有執行階段模組。
指定發佈屬性
套件版本
原生發佈套件必須具有特定的套件版本。 要指定套件版本,您可以使用以下 DSL 屬性,從最高優先順序到最低優先順序列出:
nativeDistributions.<os>.<packageFormat>PackageVersion
為單一套件格式指定版本。nativeDistributions.<os>.packageVersion
為單一目標作業系統指定版本。nativeDistributions.packageVersion
為所有套件指定版本。
在 macOS 上,您還可以使用以下 DSL 屬性指定建置版本,同樣從最高優先順序到最低優先順序列出:
nativeDistributions.macOS.<packageFormat>PackageBuildVersion
為單一套件格式指定建置版本。nativeDistributions.macOS.packageBuildVersion
為所有 macOS 套件指定建置版本。
如果您未指定建置版本,Gradle 會改用套件版本。更多關於 macOS 上的版本控制資訊,請參閱 CFBundleShortVersionString
和 CFBundleVersion
文件。
以下是依優先順序指定套件版本的範本:
compose.desktop {
application {
nativeDistributions {
// Version for all packages
packageVersion = "..."
macOS {
// Version for all macOS packages
packageVersion = "..."
// Version for the dmg package only
dmgPackageVersion = "..."
// Version for the pkg package only
pkgPackageVersion = "..."
// Build version for all macOS packages
packageBuildVersion = "..."
// Build version for the dmg package only
dmgPackageBuildVersion = "..."
// Build version for the pkg package only
pkgPackageBuildVersion = "..."
}
windows {
// Version for all Windows packages
packageVersion = "..."
// Version for the msi package only
msiPackageVersion = "..."
// Version for the exe package only
exePackageVersion = "..."
}
linux {
// Version for all Linux packages
packageVersion = "..."
// Version for the deb package only
debPackageVersion = "..."
// Version for the rpm package only
rpmPackageVersion = "..."
}
}
}
}
要定義套件版本,請遵循以下規則:
檔案類型 | 版本格式 | 詳細資訊 |
dmg , pkg | MAJOR[.MINOR][.PATCH] |
|
msi , exe | MAJOR.MINOR.BUILD |
|
deb | [EPOCH:]UPSTREAM_VERSION[-DEBIAN_REVISION] |
|
rpm | 任何格式 | 版本不得包含 - (連字號) 字元。 |
JDK 版本
此 plugin 使用 jpackage
,它要求 JDK 版本不低於 JDK 17。 指定 JDK 版本時,請確保您至少符合以下其中一項要求:
JAVA_HOME
環境變數指向相容的 JDK 版本。javaHome
屬性透過 DSL 設定:kotlincompose.desktop { application { javaHome = System.getenv("JDK_17") } }
輸出目錄
若要為原生發佈使用自訂輸出目錄,請如下所示配置 outputBaseDir
屬性:
compose.desktop {
application {
nativeDistributions {
outputBaseDir.set(project.layout.buildDirectory.dir("customOutputDir"))
}
}
}
啟動器屬性
為自訂應用程式啟動流程,您可以自訂以下屬性:
屬性 | 描述 |
mainClass | 包含 main 方法的類別完整名稱。 |
args | 應用程式 main 方法的引數。 |
jvmArgs | 應用程式 JVM 的引數。 |
以下是一個配置範例:
compose.desktop {
application {
mainClass = "MainKt"
args += listOf("-customArgument")
jvmArgs += listOf("-Xmx2G")
}
}
中繼資料
在 nativeDistributions
DSL 區塊內,您可以配置以下屬性:
屬性 | 描述 | 預設值 |
packageName | 應用程式的名稱。 | Gradle 專案的 名稱 |
packageVersion | 應用程式的版本。 | Gradle 專案的 版本 |
description | 應用程式的描述。 | 無 |
copyright | 應用程式的著作權資訊。 | 無 |
vendor | 應用程式的供應商。 | 無 |
licenseFile | 應用程式的授權檔案。 | 無 |
以下是一個配置範例:
compose.desktop {
application {
nativeDistributions {
packageName = "ExampleApp"
packageVersion = "0.1-SNAPSHOT"
description = "Compose Multiplatform App"
copyright = "© 2024 My Name. All rights reserved."
vendor = "Example vendor"
licenseFile.set(project.file("LICENSE.txt"))
}
}
}
管理資源
要封裝和載入資源,您可以使用 Compose Multiplatform 資源庫、JVM 資源載入,或將檔案新增至已封裝應用程式。
資源庫
設定專案資源最直接的方法是使用資源庫。 有了資源庫,您可以在所有支援的平台上透過通用程式碼存取資源。 詳情請參閱 多平台資源。
JVM 資源載入
Compose Multiplatform for desktop 在 JVM 平台上運作,這表示您可以使用 java.lang.Class
API 從 .jar
檔案載入資源。您可以透過 Class::getResource
或 Class::getResourceAsStream
存取 src/main/resources
目錄中的檔案。
將檔案新增至已封裝應用程式
在某些情況下,從 .jar
檔案載入資源可能不太實際,例如,當您有特定目標的資產,並且需要只將檔案包含在 macOS 套件中,而不包含在 Windows 套件中。
在這些情況下,您可以配置 Gradle plugin 以在安裝目錄中包含額外的資源檔案。 如下所示透過 DSL 指定根資源目錄:
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageVersion = "1.0.0"
appResourcesRootDir.set(project.layout.projectDirectory.dir("resources"))
}
}
}
在上述範例中,根資源目錄定義為 <PROJECT_DIR>/resources
。
Gradle plugin 將從資源子目錄中包含檔案,如下所示:
共用資源: 位於
<RESOURCES_ROOT_DIR>/common
中的檔案將包含在所有套件中,無論目標作業系統或架構為何。作業系統特定資源: 位於
<RESOURCES_ROOT_DIR>/<OS_NAME>
中的檔案將只包含在為特定作業系統建置的套件中。<OS_NAME>
的有效值為:windows
、macos
和linux
。作業系統和架構特定資源: 位於
<RESOURCES_ROOT_DIR>/<OS_NAME>-<ARCH_NAME>
中的檔案將只包含在為特定作業系統和 CPU 架構組合建置的套件中。<ARCH_NAME>
的有效值為:x64
和arm64
。 例如,位於<RESOURCES_ROOT_DIR>/macos-arm64
中的檔案將只包含在適用於 Apple Silicon Macs 的套件中。
您可以使用 compose.application.resources.dir
系統屬性存取包含的資源:
import java.io.File
val resourcesDir = File(System.getProperty("compose.application.resources.dir"))
fun main() {
println(resourcesDir.resolve("resource.txt").readText())
}
自訂原始碼集
如果您使用 org.jetbrains.kotlin.jvm
或 org.jetbrains.kotlin.multiplatform
plugins,您可以依賴預設配置:
- 使用
org.jetbrains.kotlin.jvm
的配置包含來自main
原始碼集的內容。 - 使用
org.jetbrains.kotlin.multiplatform
的配置包含來自單一 JVM 目標的內容。 如果您定義多個 JVM 目標,預設配置將會停用。在這種情況下,您需要手動配置 plugin, 或指定單一目標 (請參閱下方)。
如果預設配置模糊不清或不足,您可以透過幾種方式自訂它:
使用 Gradle 原始碼集:
plugins {
kotlin("jvm")
id("org.jetbrains.compose")
}
val customSourceSet = sourceSets.create("customSourceSet")
compose.desktop {
application {
from(customSourceSet)
}
}
使用 Kotlin JVM 目標:
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
jvm("customJvmTarget") {}
}
compose.desktop {
application {
from(kotlin.targets["customJvmTarget"])
}
}
手動:
- 使用
disableDefaultConfiguration
停用預設設定。 - 使用
fromFiles
指定要包含的檔案。 - 指定
mainJar
檔案屬性以指向包含main
類別的.jar
檔案。 - 使用
dependsOn
將任務依賴項新增至所有 plugin 任務。
compose.desktop {
application {
disableDefaultConfiguration()
fromFiles(project.fileTree("libs/") { include("**/*.jar") })
mainJar.set(project.file("main.jar"))
dependsOn("mainJarTask")
}
}
應用程式圖示
確保您的應用程式圖示以以下作業系統特定格式提供:
.icns
適用於 macOS.ico
適用於 Windows.png
適用於 Linux
compose.desktop {
application {
nativeDistributions {
macOS {
iconFile.set(project.file("icon.icns"))
}
windows {
iconFile.set(project.file("icon.ico"))
}
linux {
iconFile.set(project.file("icon.png"))
}
}
}
}
平台特定選項
平台特定設定可以使用對應的 DSL 區塊進行配置:
compose.desktop {
application {
nativeDistributions {
macOS {
// Options for macOS
}
windows {
// Options for Windows
}
linux {
// Options for Linux
}
}
}
}
下表描述了所有支援的平台特定選項。不建議使用未經文件記載的屬性。
平台 | 選項 | 描述 |
所有平台 | iconFile.set(File("PATH_TO_ICON")) | 指定應用程式的平台特定圖示路徑。詳情請參閱應用程式圖示章節。 |
packageVersion = "1.0.0" | 設定平台特定套件版本。詳情請參閱套件版本章節。 | |
installationPath = "PATH_TO_INST_DIR" | 指定預設安裝目錄的絕對或相對路徑。 在 Windows 上,您還可以使用 dirChooser = true 來啟用在安裝期間自訂路徑。 | |
Linux | packageName = "custom-package-name" | 覆寫預設應用程式名稱。 |
debMaintainer = "[email protected]" | 指定套件維護者的電子郵件。 | |
menuGroup = "my-example-menu-group" | 定義應用程式的功能表群組。 | |
appRelease = "1" | 設定 rpm 套件的發佈值或 deb 套件的修訂值。 | |
appCategory = "CATEGORY" | 為 rpm 套件分配群組值或為 deb 套件分配區段值。 | |
rpmLicenseType = "TYPE_OF_LICENSE" | 指示 rpm 套件的授權類型。 | |
debPackageVersion = "DEB_VERSION" | 設定 deb 特定套件版本。詳情請參閱套件版本章節。 | |
rpmPackageVersion = "RPM_VERSION" | 設定 rpm 特定套件版本。詳情請參閱套件版本章節。 | |
macOS | bundleID | 指定唯一的應用程式識別碼,其中只能包含字母數字字元 (A-Z 、a-z 、0-9 )、連字號 (- ) 和 句點 (. )。建議使用反向 DNS 標記法 (com.mycompany.myapp )。 |
packageName | 應用程式的名稱。 | |
dockName | 應用程式在選單列、「關於 <App>」選單項目, 以及在 Dock 中顯示的名稱。預設值為 packageName 。 | |
minimumSystemVersion | 執行應用程式所需的最低 macOS 版本。詳情請參閱 LSMinimumSystemVersion 。 | |
signing 、notarization 、provisioningProfile 、runtimeProvisioningProfile | 請參閱 《macOS 發佈的簽章和公證》教學課程。 | |
appStore = true | 指定是否為 Apple App Store 建置和簽署應用程式。至少需要 JDK 17。 | |
appCategory | Apple App Store 的應用程式類別。為 App Store 建置時,預設值為 public.app-category.utilities ,否則為 Unknown 。 請參閱 LSApplicationCategoryType 以取得有效類別列表。 | |
entitlementsFile.set(File("PATH_ENT")) | 指定包含簽章時使用的授權檔案路徑。當您提供自訂檔案時, 請務必新增 Java 應用程式所需的授權。請參閱 sandbox.plist 以取得為 App Store 建置時使用的預設檔案。請注意,此預設檔案可能因您的 JDK 版本而異。 如果未指定檔案,plugin 將使用 jpackage 提供的預設授權。詳情請參閱 《macOS 發佈的簽章和公證》教學課程。 | |
runtimeEntitlementsFile.set(File("PATH_R_ENT")) | 指定包含簽署 JVM 執行階段時使用的授權檔案路徑。當您提供自訂檔案時, 請務必新增 Java 應用程式所需的授權。請參閱 sandbox.plist 以取得為 App Store 建置時使用的預設檔案。請注意,此預設檔案可能因您的 JDK 版本而異。 如果未指定檔案,plugin 將使用 jpackage 提供的預設授權。詳情請參閱 《macOS 發佈的簽章和公證》教學課程。 | |
dmgPackageVersion = "DMG_VERSION" | 設定 DMG 特定套件版本。詳情請參閱套件版本章節。 | |
pkgPackageVersion = "PKG_VERSION" | 設定 PKG 特定套件版本。詳情請參閱套件版本章節。 | |
packageBuildVersion = "DMG_VERSION" | 設定套件建置版本。詳情請參閱套件版本章節。 | |
dmgPackageBuildVersion = "DMG_VERSION" | 設定 DMG 特定套件建置版本。詳情請參閱套件版本章節。 | |
pkgPackageBuildVersion = "PKG_VERSION" | 設定 PKG 特定套件建置版本。詳情請參閱套件版本章節。 | |
infoPlist | 請參閱Info.plist on macOS章節。 | |
Windows | console = true | 為應用程式新增控制台啟動器。 |
dirChooser = true | 啟用在安裝期間自訂安裝路徑。 | |
perUserInstall = true | 啟用應用程式的按使用者安裝。 | |
menuGroup = "start-menu-group" | 將應用程式新增至指定的開始功能表群組。 | |
upgradeUuid = "UUID" | 指定一個唯一 ID,當有比已安裝版本更新的版本時, 允許使用者透過安裝程式更新應用程式。該值對於單一應用程式必須保持不變。 詳情請參閱 如何:產生 GUID。 | |
msiPackageVersion = "MSI_VERSION" | 設定 MSI 特定套件版本。詳情請參閱套件版本章節。 | |
exePackageVersion = "EXE_VERSION" | 設定 EXE 特定套件版本。詳情請參閱套件版本章節。 |
macOS 特定配置
macOS 上的簽章和公證
現代 macOS 版本不允許使用者執行從網際網路下載的未簽章應用程式。如果您嘗試執行此類應用程式,將會遇到 以下錯誤:「YourApp 已損壞,無法開啟。您應退出磁碟映像檔」。
要了解如何簽章和公證您的應用程式,請參閱我們的教學課程。
macOS 上的資訊屬性清單 (Info.plist
)
儘管 DSL 支援必要的平台特定自訂,但仍可能存在超出提供功能範圍的案例。 如果您需要指定 DSL 中未表示的 Info.plist
值, 您可以包含一段原始 XML 片段作為解決方案。此 XML 將附加到應用程式的 Info.plist
。
範例:深度連結
在
build.gradle.kts
檔案中定義自訂 URL 配置:kotlincompose.desktop { application { mainClass = "MainKt" nativeDistributions { targetFormats(TargetFormat.Dmg) packageName = "Deep Linking Example App" macOS { bundleID = "org.jetbrains.compose.examples.deeplinking" infoPlist { extraKeysRawXml = macExtraPlistKeys } } } } } val macExtraPlistKeys: String get() = """ <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLName</key> <string>Example deep link</string> <key>CFBundleURLSchemes</key> <array> <string>compose</string> </array> </dict> </array> """
在
src/main/main.kt
檔案中使用java.awt.Desktop
類別設定 URI 處理程式:kotlinimport androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.window.singleWindowApplication import java.awt.Desktop fun main() { var text by mutableStateOf("Hello, World!") try { Desktop.getDesktop().setOpenURIHandler { event -> text = "Open URI: " + event.uri } } catch (e: UnsupportedOperationException) { println("setOpenURIHandler is unsupported") } singleWindowApplication { MaterialTheme { Text(text) } } }
執行
runDistributable
任務:./gradlew runDistributable
。
結果,像 compose://foo/bar
這樣的連結現在可以從瀏覽器重新導向到您的應用程式。
縮小化和混淆處理
Compose Multiplatform Gradle plugin 內建支援 ProGuard。 ProGuard 是一個用於程式碼縮小化和混淆處理的開放原始碼工具。
對於每個預設(無 ProGuard)封裝任務,Gradle plugin 都提供一個發佈任務(帶 ProGuard):
Gradle task | 描述 |
預設: 發佈: | 建立帶有捆綁 JDK 和資源的應用程式映像。 |
預設: 發佈: | 執行帶有捆綁 JDK 和資源的應用程式映像。 |
預設: 發佈: | 使用 Gradle JDK 執行非封裝應用程式 .jar 。 |
預設: 發佈: | 將應用程式映像封裝為 <FORMAT_NAME> 檔案。 |
預設: 發佈: | 將應用程式映像封裝為與目前作業系統相容的格式。 |
預設: 發佈: | 將應用程式映像封裝為一個 uber (fat) .jar 。 |
預設: 發佈: | 上傳 <FORMAT_NAME> 應用程式映像以進行公證 (僅限 macOS)。 |
預設: 發佈: | 檢查公證是否成功 (僅限 macOS)。 |
預設配置啟用了一些預定義的 ProGuard 規則:
- 應用程式映像已縮小化,這表示未使用的類別會被移除。
compose.desktop.application.mainClass
用作入口點。- 包含多個
keep
規則,以確保 Compose 執行階段保持正常運作。
在大多數情況下,您不需要任何額外配置即可獲得縮小化應用程式。 但是,ProGuard 可能無法追蹤位元組碼中的某些用法,例如,當類別透過反射使用時。 如果您遇到僅在 ProGuard 處理後發生的問題,您可能需要新增自訂規則。
要指定自訂配置檔案,請使用如下所示的 DSL:
compose.desktop {
application {
buildTypes.release.proguard {
configurationFiles.from(project.file("compose-desktop.pro"))
}
}
}
更多關於 ProGuard 規則和配置選項的資訊,請參考 Guardsquare 手冊。
混淆處理預設為停用。要啟用它,請透過 Gradle DSL 設定以下屬性:
compose.desktop {
application {
buildTypes.release.proguard {
obfuscate.set(true)
}
}
}
ProGuard 的優化預設為啟用。要停用它們,請透過 Gradle DSL 設定以下屬性:
compose.desktop {
application {
buildTypes.release.proguard {
optimize.set(false)
}
}
}
預設停用產生 uber JAR,ProGuard 會為每個輸入的 .jar
檔案產生一個對應的 .jar
檔案。要啟用它,請透過 Gradle DSL 設定以下屬性:
compose.desktop {
application {
buildTypes.release.proguard {
joinOutputJars.set(true)
}
}
}
接下來是什麼?
探索關於桌面元件的教學課程。