【免费下载链接】kotlin-footguns
Battle-tested agent skills mapping the footguns of Kotlin, Compose Multiplatform and the desktop JVM — mined from a production music app, not from documentation.
kotlin-footguns 是一个从生产级音乐应用中提炼出的 Kotlin 实战技能库,收录了 224 个经过战斗检验的 Agent Skills,覆盖 Kotlin、Compose Multiplatform 与桌面 JVM 开发的各类"暗坑"。本文聚焦其中的B 组:桌面打包、代码签名与 CI 构建基础设施,带你快速读懂 13 个多平台发布路上的经典实战坑,让你少走几周的弯路。💡
为什么这套"坑位清单"值得收藏?
与大多数"按官方文档写"的技能库不同,kotlin-footguns 的每一条经验都先在真实产品中付过学费——崩溃报告、失败的发布、用户提交的 Issue,然后才被写进 skills/ 目录。每个技能都是一个独立的SKILL.md,最大篇幅永远是Traps(陷阱)部分:具体的失败方式、机械性的原因、以及在你自己代码里验证失败的命令。
完整索引见 CATALOG.md,B 组共 13 个技能,正好对应本文的 13 个实战坑:
| # | 技能 | 一句话概括 |
|---|---|---|
| 1 | conveyor-desktop-packaging | HOCON 配置静默吞掉写错位置的键 |
| 2 | config-fails-open-verify-artifact | 对产物而非配置做 A/B 验证 |
| 3 | desktop-deep-link-plumbing | 自定义 URL scheme 的端到端接线 |
| 4 | r8-proguard-desktop-survival | R8 瘦身下桌面应用的存活规则 |
| 5 | gradle-config-resolved-too-early | 报错指向了错误的"嫌疑人" |
| 6 | transitive-version-pinning | 传递依赖的 strictly 约束偷梁换柱 |
| 7 | reproducible-native-bundling-two-tasks | 原生库捆绑拆成两个任务 |
| 8 | windows-vm-detection-post-wmic | Win11 移除 wmic 后的 VM 检测 |
| 9 | windows-msix-offline-installer | 自签名 MSIX 的离线安装方案 |
| 10 | arm64-native-gap-audit | 上 ARM64 前先审计原生切片 |
| 11 | ci-flaky-timing-luck | 靠时序运气通过的 CI 步骤 |
| 12 | github-actions-multiplatform-release | 单 Linux 作业交叉编译全平台 |
| 13 | buildkonfig-secrets-flavors | 构建期注入密钥与开源分支 |
一、打包器与配置类:坑 1、2、3、13
坑 1:配置键写错层级,构建日志毫无怨言 ⚠️
这是 conveyor-desktop-packaging 的核心教训:这类声明式打包器读取 HOCON 配置,对未知键"fails open"——不报错、不警告、不提示。把url-schemes写进mac/windows子段,解析完全正常,但任何平台都不会注册该 scheme,而且错误会一直潜伏到你亲手打开构建产物。
💡 心法:嵌套层级是键的一部分。每个"看起来没效果"的键,都要在产物里 grep 一次指纹(如 Info.plist 里的
CFBundleURLSchemes、.desktop里的MimeType),并把同样的 grep 接入 CI 作为硬断言。
同技能还记录了两个隐蔽陷阱:命令行覆盖要用-K<点分键>=值(列表值才用方括号);打包器的 JDK 来自你构建的 toolchain,记得把 CI runner 的 JDK 对齐到同一主版本,否则你打包的是一套从未测过的运行时。
坑 2:验证对象要换成"构建产物"
config-fails-open-verify-artifact 给出了方法论:不要对配置做断言,要对生成的产物做断言。标准动作是"两构建 A/B 差分"——只改变键的位置、构建两次、diff 产物中该功能的指纹。键看起来正确、构建全绿、功能却从未被观察到生效,正是这个技能要治的病。
坑 3:深链接"只是把窗口带到前台"
desktop-deep-link-plumbing 处理点击自定义链接后 App 只被唤起、流程却停在原地的问题。要点:启动时的argv 过滤必须匹配任意scheme://,.desktop文件要写全所有 handler,并保留手动粘贴 token 的降级通道;单实例场景还要注意链接要能转发给已运行的实例。
坑 13:开源构建绝不能携带凭据
buildkonfig-secrets-flavors 的做法:密钥从不入库,而是从未跟踪的本地 properties 文件读入,经 BuildKonfig 生成编译期常量;开源分支拿到空字符串——功能自动禁用而不是构建失败。另外,新版 Gradle 对生成源码有隐式任务依赖要求,需要显式dependsOn补上,否则报错很莫名。
二、R8 瘦身与版本治理:坑 4、5、6
坑 4:R8 优化/混淆在桌面端的三大雷区 🔥
r8-proguard-desktop-survival 是全文含金量最高的一个技能。桌面 JVM 的类文件校验器严格且运行在加载期,UI 层靠反射和名字找渲染后端,打包器又从构建输出拼 classpath——这三点决定了桌面的瘦身与移动端完全不同。
- 必须关掉三个优化族:返回类型特化、局部变量重分配、方法内联。三者都会在类加载时(不是构建时)只在瘦身包里炸出校验错误,开发运行完全复现不了。
- 混淆破坏"按名字解析":渲染后端被改名 → 画布/视频表面透明显露桌面;动画渲染器被改名 → 区域空白无输出,全程无日志无崩溃。"能跑但不画东西"就是混淆问题的指纹。
- keep 规则要按"族"预算:JNA 原生绑定、协程运行时(整包保留)、HTTP 客户端、反射序列化、数据库驱动、ServiceLoader 发现、泛型签名——七族缺一不可。
- 最坑的一点:打包插件自动探测的是原始 classpath。瘦身任务的输出必须显式替换它,且配置生成任务要
dependsOn瘦身任务,否则你发布的是没瘦过的包——用安装包体积是否变化来验证覆盖是否生效。
💡 政策:只加 keep 规则,永不整体关闭 R8。
optimize = false当"临时测试",就是临时补丁变成永久配置的起点。
坑 5:Gradle 报错点名的不是真凶
gradle-config-resolved-too-early 处理"cannot mutate a configuration after its child configuration was resolved"这类错误:消息点名的通常是你触碰的 configuration,而非过早 resolve 它的插件。解法不是重写那行依赖,而是拿一个最小可工作模板做插件二分,逐个还原定位真凶。
坑 6:一个陌生库,把共享后端"钉"到了新版本
transitive-version-pinning 记录了一次真实的"隔山打牛":加了一个玻璃特效 UI 库,它对渲染后端声明了strictly约束,strict 胜过你的版本、甚至胜过 BOM——编译全绿,直到某个动画第一次真正绘制,才在新后端已删除的矩阵辅助方法上抛NoSuchMethodError。
- 定位:
dependencyInsight一条命令给出约束和声明它的模块。 - 决策:钉回版本要按作用域论证(该平台根本不渲染这个功能,钉回零成本),并在注释里写明理由,防止后人"升级回去"。
三、代码签名与平台陷阱:坑 7、8、9、10
坑 7:原生库捆绑拆成"两个家"的两个任务
reproducible-native-bundling-two-tasks 的架构:开发机任务负责构建各架构切片、打 tarball 并打印摘要;CI 任务只下载并对齐到构建文件中钉死的摘要校验。这样 CI 不需要原生工具链,"以为升了版本、实际发的还是旧二进制"的静默事故也被摘要校验挡住。
坑 8:Windows 11 把 wmic 删了
windows-vm-detection-post-wmic:经典命令查询工具从 Win11 移除,检测需改走PowerShell 的 CIM,且Manufacturer 与 Model 两个字段都要查;更关键的是刻意选择失败方向——透明/无边框窗口在 VM 里"进程在、画面没有"的故障,检测失效时宁可误报也不要漏报。
坑 9:裸 MSIX 离线装不上
windows-msix-offline-installer 的结论:打包器输出的安装包清单会去访问 404 的 URL,裸包又因自签名证书不受信任而无法双击。用户真正能装的是三文件 zip:install.bat(提权→导入证书→sideload)+app.crt+.msix。
⚠️ 两个必查点:证书是产物,不是输入——签名失败而构建仍可能成功,zip 里少张证书,CI 必须先断言
app.crt存在;签名密钥跨版本保持稳定,否则新包拒绝覆盖旧包。
坑 10:上 ARM64 前先审计原生切片
arm64-native-gap-audit:缺一个原生切片,不会在打包时失败,而是在第一次用到数据库/渲染/媒体层时把整个目标架构打倒。审计要做成对解析产物可重复运行的命令,并在每次依赖升级后重跑。配套技能 bundled-native-soname-conflict 还提醒:捆绑的原生库会把自己携带的基础库 soname 抢占为整个进程的,导致平台 API 报"缺符号"——加载前先探测 API,根治靠排除 SYSTEM_LIBS。
四、CI 流水线:坑 11、12
坑 11:绿色了三个月,其实是时序运气
ci-flaky-timing-luck 抓到两个"侥幸通过"的步骤:同名卷的异步卸载与下一轮挂载相撞;下载器把404 错误页当成品存盘。修法:捕获工具报告的挂载点而不是猜测;下载用curl(失败会大声报错)而不是静默吞掉错误体的方式。runner 镜像一更新,这类步骤立刻现形。
坑 12:一个 Linux 作业交叉编译全平台 🚀
github-actions-multiplatform-release 的流水线设计:
- 重作业(Linux runner):一次产出 Linux 树 + 双架构 macOS + Windows 包;
- 薄作业(macOS runner):只做一件事——把 macOS 应用包进 DMG(该工具只随 macOS 分发),全程不编译,只花几分钟钱。
为什么要 DMG?同一应用同一签名,从压缩包解出比从挂载磁盘镜像拷出受到更严的隔离审查:前者弹出"开发者无法验证"的硬阻断,后者只有普通的首次启动提示。
CI 侧两大隐形坑:外部调用的打包器不触发 Gradle 任务依赖——dependsOn接好的原生切片准备步骤被绕过,作业照样成功、装好的安装包却缺原生库,一切前置必须写成打包器之前的显式 workflow 步骤;另外打包器会清空输出目录,分两次调用会把第二次之前暂存的东西吃掉。
五、新手落地清单 ✅
读完这 13 个坑,把它们压成一张可执行的发布前检查表:
- 每个"无副作用可观察"的配置键,都在产物里 grep 过一次指纹,且进了 CI 断言。
- 瘦身包与安装包在干净机器上跑完:文字渲染、动画、透明表面、一次网络请求、开库、原生绑定。
- macOS 包签名之前清理了
._*伴随文件(见 macos-codesign-sidecar-strip——签名机上看不到、用户机上"应用已损坏"的经典案例)。 - 原生切片对每个目标架构都有审计记录,且 CI 按钉死的摘要校验。
- Windows 离线安装 zip 里证书存在性有硬断言,签名密钥跨版本稳定。
- CI 中不存在靠时序运气通过的步骤,shell 步骤都有
set -euo pipefail。
每个坑的完整机制、验证命令与修复方案,都收录在 skills/ 对应目录的SKILL.md中——文件刻意控制在 60~140 行,两分钟可读,也适合直接喂给编码 Agent。想按顺序通读全部 224 个技能,从 CATALOG.md 的分组索引进入即可。
少踩一个坑,就是一次没白付的学费。🎯
【免费下载链接】kotlin-footguns
Battle-tested agent skills mapping the footguns of Kotlin, Compose Multiplatform and the desktop JVM — mined from a production music app, not from documentation.
相关推荐
holaOS 桌面端(Holaboss Local)开发与打包实战指南:从三栏原型到多平台签名发布
holaOS 桌面端(Holaboss Local)开发与打包实战指南:从三栏原型到多平台签名发布 本指南以 apps/desktop/README.md ht
人工智能AI AgentAI 应用前端后端即时通讯交互助手工具调用MCP ClientsDLSS 切换终极指南:9 种画质 DLL、8 大游戏库,不更新游戏自由升降级版本
DLSS 切换终极指南:9 种画质 DLL、8 大游戏库,不更新游戏自由升降级版本 DLSS Swapper 是一款开源的 DLL 切换工具,专为 Window
桌面应用Happy 桌面端 Tauri 打包与跨平台发布指南:从本地构建到签名分发
Happy 桌面端 Tauri 打包与跨平台发布指南:从本地构建到签名分发 本篇技术指南以仓库内 docs/plans/tauri desktop packag
人工智能AI AgentAI 应用移动开发CLI后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考