从零到发布:Screendrop 的 Go 发布工具如何自动完成公证、DMG 与 Homebrew 推送
【免费下载链接】ScreendropA beautiful screenshot + screen recording + Loom alternative - all native, self hostable and free.项目地址: https://gitcode.com/gh_mirrors/sc/Screendrop
Screendrop 是一款免费、全原生的 macOS 截图与录屏工具,它的发版工作全部由一个 Go 编写的发布工具完成:一条命令自动跑完 Apple 公证、DMG 打包、Sparkle 签名、自动更新推送与 Homebrew cask 更新——开发者不需要再手动点一次 Xcode 的导出按钮。
为什么不用手动发版?
发布一个 macOS 应用,手动流程至少要串起 5 个环节:
- Xcode Archive 并以 Developer ID 导出
- 打包成 DMG
- 对 DMG 做 Sparkle 签名(供自动更新校验)
- 创建 Release 并更新 appcast.xml
- 生成并推送 Homebrew cask
每一步都可能出错,而且顺序错了就会留下"引用了不存在下载地址"的 appcast。Screendrop 的解法是把整条流水线封装进一个 Go 命令行工具 cmd/screendrop-release/main.go,约 1000 行代码覆盖了从归档到推送的全部步骤。
发布流水线全景:一条命令,五个阶段
带上-build参数运行时,工具按固定顺序执行:
| 阶段 | 做什么 | 关键命令 |
|---|---|---|
| ① 构建与公证 | 归档 → Developer ID 导出 → 公证 → staple | xcodebuild/xcrun notarytool |
| ② 打包签名 | 生成 DMG 并用 Sparkle 签名 | create-dmg/sign_update |
| ③ 发布到仓库 | 推代码 → 建 Release → 更新 appcast | git push/gh release |
| ④ 推送 Homebrew | 重算 sha256,生成 cask 并提交 tap 仓库 | git push(tap 仓库) |
第一步:Apple 公证自动化——归档、签名、钉上公证票据
核心逻辑在 main.go 的 runBuildPhase 中:
- 归档:
xcodebuild archive指定 Release 配置,产物落在~/Downloads/Screendrop.xcarchive - 导出:自动生成
ExportOptions.plist(Developer ID + 自动签名),用-exportArchive导出 - 公证:
ditto压缩后用xcrun notarytool submit --wait等待 Apple 返回,直到输出包含status: Accepted才算通过 - Staple:
xcrun stapler staple把公证票据钉进应用,离线下载也能通过 Gatekeeper 校验
它还支持在归档前用-set-version和-set-build直接改写工程里的MARKETING_VERSION/CURRENT_PROJECT_VERSION(见 bumpVersion),并自动提交这次版本号变更——"升版本"这件小事也不再需要打开 Xcode。
第二步:DMG 打包与 Sparkle 签名
DMG 由create-dmg生成,工具内置了窗口尺寸、图标位置与应用拖拽链接的坐标(main.go#L231-L255),保证每次装出的 DMG 观感一致。
紧接着是关键一步:用 Sparkle 的sign_update对 DMG 签名。这个二进制工具藏在 Xcode DerivedData 里,findSignUpdate 会自动按前缀扫描找到它,签名后从输出中解析出edSignature与文件长度(parseSparkleSignature,有单测覆盖),供后面写入 appcast。
第三步:顺序很重要——先建 Release,再动 appcast
发布顺序是这个工具最值得学习的设计(main.go#L274-L364):
- 先推送本地提交——保证 Release 的 tag 包含已发布的源码
- 创建 GitHub Release 并上传 DMG——此时下载 URL 已真实存在
- 最后才更新并推送 appcast——appcast 指向第 2 步生成的 URL
顺序反了的话,一旦中途失败,appcast 就会指向一个不存在的下载地址,客户端全部更新失败。此外两个健壮性细节也很实用:
- 幂等重跑:Release 已存在时改为重新上传 DMG,而不是报错退出
- 网络重试:所有易抖动的
git push/gh调用都走 runCmdRetry,按 2s、4s 间隔退避重试 3 次
第四步:Homebrew cask 一键推送
cask 更新逻辑在 updateHomebrewCask:先对 DMG 计算 sha256,再由 renderCask 模板化生成Casks/screendrop.rb,最后提交并推送到 tap 仓库。
注意它的"非致命"设计:tap 仓库未配置或推送失败时,只打一条 WARN,绝不让 Homebrew 环节阻塞主发布流程。
自动更新闭环:appcast.xml 是另一半
工具负责"生产"更新,客户端的 UpdaterManager.swift 负责"消费":它基于 Sparkle 的SPUStandardUpdaterController轮询仓库中的 appcast.xml。
工具更新 appcast 时的细节值得留意(writeAppcast):
- 新条目置顶,并按 build 号去重——同一构建重跑是"替换"而不是"重复"
- 描述字段写成
<![CDATA[...]]>包裹的 HTML,客户端弹窗里能渲染出标题和列表 - 每次生成都会把
sparkle:edSignature和length写进 enclosure,与第二步的签名一一对应
以当前仓库中的 appcast.xml 第 29-54 行 为例,就能直接看到一条由该工具生成的完整 v0.34.0 更新记录。
上手指南:从交互模式到无人值守
在仓库根目录(或任意包含appcast.xml的目录下)运行:
# 完整流水线:构建 → 公证 → DMG → 发布 → Homebrew go run ./cmd/screendrop-release -build不带-build时,工具假设你已把Screendrop.app导出到~/Downloads,只执行打包与发布。
| 常用参数 | 作用 |
|---|---|
-build | 包含归档、Developer ID 导出、公证与 staple 阶段 |
-set-version 0.35.0 | 归档前自动升版本号并提交 |
-notary-profile <name> | 指定 notarytool 钥匙串配置 |
-notes "..."/-notes-file <path> | 非交互方式提供发布说明(一行一条) |
-yes | 跳过所有确认提示,适合 CI / 代理调用 |
配合环境变量可覆盖默认路径:SCREENDROP_REPO指定仓库目录,SCREENDROP_TAP_DIR指定 tap 仓库的本地克隆(findRepoDir、resolveTapDir)。
工具启动时会先用 requireCommand 检查create-dmg、gh、git等依赖,缺什么直接给出brew install提示,新手也不会卡在"神秘失败"上。
💡 仓库为只读镜像;如需自行实验,可克隆:
git clone https://gitcode.com/gh_mirrors/sc/Screendrop,模块定义见 go.mod。
写在最后
Screendrop 的发布工具给出了一个小团队做 macOS 发布的务实范式:🎯顺序正确(先有下载 URL,再更新 appcast)、可重跑(幂等 + 重试)、故障隔离(Homebrew 失败不阻塞主流程)、零手工(版本号、签名、哈希全部自动生成)。它不是复杂的 CI 编排,而是一个约千行的 Go 程序——这恰恰说明:发布自动化不一定要引入重工具链,把步骤、顺序和兜底写清楚就够了。
【免费下载链接】ScreendropA beautiful screenshot + screen recording + Loom alternative - all native, self hostable and free.项目地址: https://gitcode.com/gh_mirrors/sc/Screendrop
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考