Compose Multiplatform 桌面分发打包工具对比:内置 jlink/jpackage 任务与第三方方案的深度剖析
【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform
本文基于仓库中 packaging-tools-comparison.md 教程展开,对照 Compose Multiplatform Gradle 插件的源码实现,讲清桌面应用分发链路中两条路线的边界:一是插件内置的jlink/jpackage打包任务能覆盖哪些基本需求、受哪些 JDK 与平台限制,二是第三方工具 Conveyor 在自动更新、跨平台构建等场景下提供了哪些增量能力,帮助你在发布 Compose Desktop 应用时做出有依据的工具选型。
一、内置 Gradle 打包任务:覆盖基本分发需求
Compose Multiplatform 的 Compose Gradle 插件(源码位于 gradle-plugins/compose 目录)把桌面应用的打包构建直接挂到了 Gradle 任务体系上。官方教程 packaging-tools-comparison.md 对内置能力的概括是三点:
- 为 Windows 生成 MSI 文件或 NSIS 安装器 EXE,为 macOS 生成签名的应用 bundle,为 Linux 生成 DEB/RPM 包;
- 使用
jlink捆绑 JVM; - 在每个操作系统上自定义打包选项。
下面结合源码逐项拆解这些能力在插件中的真实落地方式。
1.1 支持的打包格式与任务命名
所有可打包格式定义在 TargetFormat.kt 的枚举中,每个格式都绑定了目标操作系统,这直接决定了“格式与构建机 OS 必须匹配”的约束:
| 枚举值 | 格式 ID | 目标 OS | 产物 |
|---|---|---|---|
AppImage | app-image | 构建机当前 OS | 目录形态的应用镜像(无文件扩展名) |
Deb | deb | Linux | DEB 包 |
Rpm | rpm | Linux | RPM 包 |
Dmg | dmg | macOS | DMG 镜像 |
Pkg | pkg | macOS | PKG 安装器 |
Exe | exe | Windows | NSIS 安装器 EXE |
Msi | msi | Windows | MSI 安装器 |
枚举中的isCompatibleWithCurrentOS属性按currentOS == targetOS判定兼容性。任务名遵循 JvmTasks.kt 中注释声明的[动作][构建类型][对象]模式,例如runDistributable、runReleaseDistributable、packageDmg、packageReleaseDmg——即在package前缀后拼上构建类型(如Release)与格式名。
1.2 用 jlink 捆绑 JVM
jlink环节由 AbstractJLinkTask.kt 实现,它从 JDK 安装目录调用jlink工具,组装出一个只含应用所需模块的精简 runtime。从源码看,其命令行参数的拼装逻辑(makeArgs方法)如下:
--add-modules:每个模块单独追加一次。模块集合来自两处——若includeAllModules为 true,则从JvmRuntimeProperties(由前置的 JDK 探测任务生成)中读取该 JDK 的全部可用模块;否则使用用户在 distributions DSL 中显式声明的modules列表;--strip-debug、--no-header-files、--no-man-pages、--strip-native-commands:这四项默认均为true,即默认剥离调试信息、头文件、man 页面和本地命令,以减小 runtime 体积;--compress:对应可选的compressionLevel(取值定义见 RuntimeCompressionLevel.kt),用于在输出时压缩 runtime 中的 jar/class 文件;--generate-cds-archive:可选地生成 JRE CDS 归档以加速启动,源码中明确校验了它与--strip-native-commands互斥(二者同时开启会直接抛错);--output:指向任务的输出目录。
该任务标注了@DisableCachingByDefault,原因注释写明:它依赖平台特定的 JDK 工具,输出取决于本机的 JDK 安装情况,因此默认不参与跨构建缓存。
1.3 jpackage 的调用细节
安装器(以及 app image)由 AbstractJPackageTask.kt 调用jpackage生成。从源码的makeArgs方法可以看到完整的参数组织方式:
应用镜像相关参数(创建 app image、或不带--app-image的安装器时传入):
--input指向插件预先准备好的libs工作目录,--runtime-image指向 jlink 产物,--main-jar/--main-class指定启动入口;--arguments透传应用启动参数,javaOption透传 JVM 参数;- 两个对 Compose 渲染至关重要的系统属性:
-Dskiko.library.path=$APPDIR(告知 Skiko 原生库位置)以及应用资源目录属性。
安装器相关参数(由已有 app image 生成安装器时传入):
--app-image、--install-dir(对应 DSL 中的installationPath)、--license-file;--file-associations:源码会把 DSL 里fileAssociation(mimeType, extension, description, iconFile)声明的每种文件关联,写成形如FA<ext>.properties的临时文件(内容为mime-type/extension/description/icon四个键值),再逐一传给 jpackage。这一机制在 AbstractPlatformSettings 中定义,三个平台的设置基类都继承自它。
平台分支参数(按currentOS分别追加,这也是“任务与构建机 OS 绑定”的直接证据):
- Linux:
--linux-shortcut、--linux-package-name、--linux-app-release、--linux-app-category、--linux-deb-maintainer、--linux-menu-group、--linux-rpm-license-type; - Windows:
--win-dir-chooser、--win-per-user-install、--win-shortcut、--win-menu、--win-menu-group、--win-upgrade-uuid; - macOS:
--mac-package-name、--mac-package-identifier(bundleID)、--mac-app-store、--mac-app-category、--mac-entitlements,以及签名相关的--mac-sign、--mac-signing-key-user-name、--mac-signing-keychain、--mac-package-signing-prefix。
所有格式统一的公共参数包括--type <格式id>、--dest、--name、--icon、--description、--copyright、--app-version、--vendor。
此外,源码还处理了两个工程细节:一是多模块项目中 jar 简单名冲突问题(mangleJarFilesNames默认为 true,会给拷贝进 libs 目录的 jar 名追加内容哈希,避免:data:utils与:ui:utils这类同名片段互相覆盖);二是 Skiko 原生库的特殊处理——打包前会扫描并解出 Skiko AWT runtime jar 内的目标平台原生条目(stripAndUnpackSkikoNatives),把无关平台的.so/.dll/.dylib剔除,减小安装体积。
1.4 各平台的 DSL 配置项
各平台的可配置项定义在 PlatformSettings.kt,与 jpackage 参数一一对应,全部通过jvmApplication { nativeDistributions { ... } }下的windows { ... }、linux { ... }、macos { ... }块配置(入口见 JvmApplication.kt 中声明的nativeDistributions属性)。三平台共有:iconFile、packageVersion、installationPath、fileAssociation(...)。
Windows(WindowsPlatformSettings):
console(默认 false):是否显示控制台窗口;dirChooser(默认 true):安装时是否允许选择目录;perUserInstall(默认 false):按用户安装而非按机器安装;shortcut(默认 false)/menu(默认 false,设置menuGroup后自动置真)/menuGroup:桌面快捷方式与开始菜单项;upgradeUuid:MSI 升级识别 UUID;msiPackageVersion/exePackageVersion:分别为 MSI 与 EXE 指定包版本。
Linux(LinuxPlatformSettings):shortcut(默认 false)、packageName、appRelease、appCategory、debMaintainer、menuGroup、rpmLicenseType、debPackageVersion、rpmPackageVersion。
macOS(JvmMacOSPlatformSettings):除继承自AbstractMacOSPlatformSettings的packageName、packageBuildVersion、dmgPackageVersion、dmgPackageBuildVersion、appCategory、minimumSystemVersion、bundleID(推荐反向 DNS 风格,如com.mycompany.myapp,只允许字母数字、连字符与点)外,JVM 应用还有dockName、setDockNameSameAsPackageName、appStore、entitlementsFile/runtimeEntitlementsFile、pkgPackageVersion/pkgPackageBuildVersion、provisioningProfile/runtimeProvisioningProfile,以及infoPlist { extraKeysRawXml }用于向自动生成的 Info.plist 注入额外 XML 键。
1.5 Windows 平台的前置依赖:WiX Toolset
jpackage 生成 MSI 依赖 WiX Toolset。插件对这一依赖做了自动化处理,逻辑在 wixToolset.kt 中:
- 若设置了
WIX_PATH环境变量并指向有效目录,则直接使用本地的 WiX 安装; - 否则注册
downloadWix/unzipWix任务,自动下载 WiX 3.11 的二进制发行包并解压到构建目录,Windows 打包任务会自动依赖它; - 若设置 Gradle 属性
compose.desktop.application.downloadWix=false,则跳过自动下载(此时需自行准备 WiX 环境)。
1.6 JDK 要求与运行环境校验
打包前的 JDK 校验由 AbstractCheckNativeDistributionRuntime.kt 完成,关键约束有三条:
- 最低版本:
MIN_JAVA_RUNTIME_VERSION = 17,构建 JDK 主版本低于 17 会直接报“minimum required JDK version is '17'”错误; - 工具完整性:校验 JDK 的
bin目录下同时存在java、jlink、jpackage三个可执行文件,缺失即报“Failed to check JDK distribution”; - 发行商检查:在 macOS 上若探测到 Homebrew 的 JDK,会因已知的打包问题而报错,建议改用其他发行商的 JDK(如 Amazon Corretto),或在
gradle.properties中加compose.desktop.application.checkJdkVendor=false自担风险继续。
该任务同时会运行java --list-modules收集全部模块名并写入JvmRuntimeProperties,供后续includeAllModules的 jlink 任务读取。
1.7 macOS 签名与公证
macOS 平台的签名/公证通过 DSL 暴露:signing { sign, identity, keychain, prefix }(定义见 MacOSSigningSettings.kt,默认值可被compose.desktop.application.macSign*等 Gradle 属性覆盖)以及notarization { ... }。从 AbstractJPackageTask 的macSigner逻辑可以看出行为边界:
- 仅当构建机是 macOS 时才会创建签名器;启用
sign时使用配置了证书身份的MacSignerImpl,否则使用NoCertificateSigner(无证书签名); - 打包完成后,
modifyRuntimeOnMacOsIfNeeded会对 app image 内的 runtime 逐个重签所有可执行文件与 dylib,再签 runtime 与整个.app,并把 provisioning profile 写入Contents/runtime/Contents/embedded.provisionprofile; - 生成安装器时,签名信息会翻译为
--mac-sign等 jpackage 参数,让安装器内容在生成阶段就带签名。
1.8 为什么内置任务无法跨平台构建
教程中提到“内置任务必须从每个目标 OS 上运行”,这一点在源码中得到印证:
AbstractJPackageTask.makeArgs中的平台参数全部由currentOS分支决定(Linux/Windows/macOS 各一组);- WiX 配置函数开头就有
check(currentOS == OS.Windows),非 Windows 环境直接报错; - macOS 签名器在非 macOS 环境下恒为
null。
也就是说,插件的任务体系把“格式—构建机 OS”强绑定在了一起:在 macOS 上跑打包只会产出 macOS 格式,在 Windows 上只产出 Windows 格式。若要同时分发多平台,就得为每个目标平台各准备一台构建机(或 CI runner),这正是第三方方案切入的痛点。
二、第三方方案:Conveyor 提供的增量能力
packaging-tools-comparison.md 介绍的第二条路线是 Hydraulic 公司的Conveyor工具。它与 Compose Gradle 插件集成,针对内置任务覆盖不到的场景提供了一组能力,教程原文列出的九项如下:
- 在线更新(Online updates):插件生成的包需要用户手动重装来更新;Conveyor 生成的包在 Windows/macOS 上可以后台静默自更新,在 Linux 上则走 apt 等系统包管理通道;
- 跨构建(Cross-building):从任意 OS(开发笔记本或 CI 机器)为所有支持的目标生成、签名、公证包,而内置任务必须逐平台各开一台构建机;
- 自签名包(Self-signed packages):不需要购买签名证书,但代价是用户安装时要复制/粘贴终端命令;
- 下载页(Download pages):生成静态 HTML,自动探测用户的操作系统与 CPU 架构并给出对应下载项;
- 图标转换(Icon conversion):内置任务要求开发者手工把图标转成各平台格式(如 Windows 的
.ico、macOS 的.icns),Conveyor 代劳这一步; - 体积优化(Size optimization):使用
jdeps剔除未使用的 JDK 模块以缩小下载体积(内置 jlink 方案通过模块列表实现类似目标,jdeps 是从依赖分析角度补强); - 可访问性(Accessibility):通过 Java Accessibility Bridge 自动加入屏幕阅读器支持;
- 对企业 IT 部门友好:Windows 侧采用 MSIX——微软 Windows 10/11 的当代打包系统,与 Windows 网络管理工具深度集成;
- CLI 支持:包内可附带附属命令行工具。
教程还特别提示:以上并非完整功能清单,且 Licensing 模型为开源项目免费,商业项目度过引入期后需要购买许可证。选型时应以其官方文档与定价页为准确认最新功能与费用条款。
三、如何选型:内置任务 vs Conveyor
结合教程与源码证据,可以把两条路线的适用边界归纳如下:
优先使用内置 Gradle 任务,当你的场景满足:
- 只需基础分发产物(MSI/EXE、签名 app bundle、DEB/RPM),且接受“每个目标平台各备一台构建机”(本地或 CI 均可);
- 需要精细控制打包参数:jlink 的压缩级别与 CDS 归档、jpackage 的各平台安装器选项、
fileAssociation文件关联、Info.plist 注入(infoPlist { extraKeysRawXml })等,插件 DSL 已完整暴露(PlatformSettings.kt); - 希望零外部商业依赖,构建链路完全由 JDK 17+ 的
jlink/jpackage与插件任务组成。
考虑引入 Conveyor,当你的产品形态需要:
- 静默在线更新能力,或面向 Linux 的 apt 分发通道;
- 单一构建环境(如一台 CI 机器)产出全部平台、签名与公证产物;
- 不想维护签名证书(自签名路线),或需要开箱即用的下载页、图标转换、MSIX 打包;
- 需要附带 CLI 工具或企业级 Windows 部署(MSIX + 网络管理工具集成)。
需要注意的是,无论选择哪条路线,JVM 捆绑、图标、版本号、安装路径这些基础输入都是共通的——内置任务把这些暴露为 DSL 属性与 jpackage 参数,Conveyor 则以工具自身的工作流承接。两者并非互斥替代关系:Conveyor 同样以“与 Compose Gradle 插件集成”的方式工作。
四、小结与延伸阅读
本文以 tutorials/Native_distributions_and_local_execution/packaging-tools-comparison.md 为主线,对照 Compose Multiplatform 仓库源码核实了内置打包链路的关键实现:
- 格式与 OS 绑定关系见 TargetFormat.kt,任务命名规则见 JvmTasks.kt;
jlink参数组装见 AbstractJLinkTask.kt,jpackage参数组装、Skiko 原生库瘦身、macOS 重签逻辑见 AbstractJPackageTask.kt;- Windows WiX 自动下载见 wixToolset.kt,JDK 17+ 与
jlink/jpackage工具校验、Homebrew JDK 告警见 AbstractCheckNativeDistributionRuntime.kt; - 三平台 DSL 配置项(含 macOS 签名/公证入口)见 PlatformSettings.kt 与 MacOSSigningSettings.kt。
掌握上述内容后,你可以先跑通内置的package<构建类型><格式>任务完成基础分发,再依据是否涉及自动更新、跨平台 CI 与 MSIX 等企业需求,评估是否引入 Conveyor 补齐能力短板。
【免费下载链接】compose-multiplatformCompose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.项目地址: https://gitcode.com/GitHub_Trending/co/compose-multiplatform
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考