首页
/ fzf 发布流程全解:版本一致性校验、签名标签与 GitHub Actions 自动构建发布

fzf 发布流程全解:版本一致性校验、签名标签与 GitHub Actions 自动构建发布

2026-09-03 16:07:52作者:郜逊炳

本文以 fzf 仓库的发布说明 RELEASE.md 为主线,完整拆解 fzf 从版本号同步、make tag 一致性校验,到标签推送触发 GitHub Actions 发布工作流 的构建、签名、公证与上传全过程。读完后你可以独立执行一次完整的 fzf 版本发布,也能在不真正发布的情况下演练并验证整套发布流水线。

发布流程总览

fzf 的构建、签名、公证(notarization)与发布(publishing)全部由 .github/workflows/release.yml 处理,工作流的触发条件是 tag push——即本地签好标签并推送到远端。完整流程分为四步:

  1. master 分支上更新六个文件中的版本号并提交;
  2. make tag VERSION=0.74.3 校验文件一致性、签名并推送标签;
  3. 在 Actions 中批准 release 环境门禁,让工作流继续执行;
  4. GitHub Release 发布完成后,将 master 快进推送到远端。

之所以采用"先推 tag、后推 master"的顺序,RELEASE.md 中给出了明确原因:只有标签被推送,origin 上的 master 仍指向旧版本,因此在发布窗口期内 /master/install 始终解析到已存在的旧版二进制,安装脚本不会因版本与制品不匹配而失败。

第一步:同步更新版本号

发布前需要把新版本号写入以下文件,并在 master 上提交(以 0.74.3 为例):

  • CHANGELOG.md:新版本条目必须位于文件最顶部,如 0.74.3 小节;
  • main.go:定义 var version = "0.74" 的默认值(见 main.go#L14-L15),正式构建时会通过 ldflags 覆盖;
  • install:脚本第 5 行 version=0.74.3 硬编码了要下载的版本;
  • install.ps1:Windows 安装脚本第 1 行 $version="0.74.3"
  • man/man1/fzf.1man/man1/fzf-tmux.1:两个 man 页面中均需出现 "fzf <版本>" 字样。

这些文件各自承担不同角色,main.goversion 变量是 fzf --version 的输出来源之一;installinstall.ps1 则按硬编码版本从 release 制品中下载对应二进制,install.ps1 下载后还会执行二进制并比对 --version 输出,不一致即报 "Invalid version"(见 install.ps1#L13-L15)。因此任何一处漏改都会直接破坏用户侧的安装链路,这正是后续一致性校验存在的原因。

第二步:make tag —— 一致性校验、签名与推送标签

完成版本提交后,执行:

make tag VERSION=0.74.3

make tag 会先执行 prerelease 目标,通过一组 grep 检查版本是否已出现在全部约定位置,全部通过后才签名并推送标签。对照 Makefile#L130-L141 可以看到实际检查项:

prerelease:
	# Check if version numbers are properly updated
	grep -q ^$(VERSION_REGEX)$$ CHANGELOG.md
	grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf.1
	grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf-tmux.1
	grep -qF $(VERSION) install
	grep -qF $(VERSION) install.ps1
	@echo "OK: all files consistent at $(VERSION)"

tag: prerelease
	git tag -s v$(VERSION) -m v$(VERSION)
	git push origin v$(VERSION)

几个值得注意的细节:

  • VERSION_REGEXVERSION_TRIM(去掉 v 前缀和 -prerelease 后缀,见 Makefile#L26-L27)逐点转义后生成,因此 CHANGELOG.md 中的标题必须整行精确等于版本号;man 页面的检查则要求带引号的形式 "fzf 0.74.3"
  • 标签使用 git tag -s 签名(GPG/SSH 签名),保证发布标签可被验证;未通过 prerelease 时标签根本不会被创建和推送。
  • 版本号若未显式给出 VERSION,Makefile 默认通过 git describe --abbrev=0 从最新标签推导(见 Makefile#L18-L25),所以直接 make tag 会作用于最新标签版本,显式传参更稳妥。

此步骤结束时的状态:远端多了一个签名的 v0.74.3 标签,master 尚未推送。

第三步:工作流触发与 release 环境门禁

标签推送到远端后,.github/workflows/release.yml 立即触发。该工作流的触发与运行配置为:

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to validate (e.g. 0.73.0).'
        type: string
        required: true

permissions:
  contents: write

jobs:
  release:
    runs-on: macos-latest
    environment: release

要点:

  • v 开头的标签推送会触发真实发布;workflow_dispatch(手动运行)则用于演练,见后文测试章节。
  • 作业运行在 macos-latest 上,因为需要执行 macOS 签名与公证;environment: release 使运行暂停在环境门禁,需要在 Actions 界面批准后才继续(对应 RELEASE.md 第 3 步"Approve it in the Actions tab to release")。
  • permissions: contents: write 只授予写 release 内容所需的最小权限。

工作流内部的实际步骤

标签推送触发后,工作流依次执行以下阶段(完整定义见 .github/workflows/release.yml):

  1. 确定版本号:标签触发时从 GITHUB_REF_NAME 去掉 v 前缀;手动触发时取用户输入的 version 参数。

  2. 版本一致性复核:在 CI 侧重新执行与 make prerelease 相同的五条 grep 检查(release.yml#L41-L50),确保检出的代码树确实对应该版本,防止"tag 指错了提交"这类事故。

  3. 提取 Release Notes:用 sed -n "/^${R}$/,/^[0-9]/p"CHANGELOG.md 中截取当前版本小节,经反转、去空行、去前两行处理后写入 tmp/release-noterelease.yml#L52-L60),作为 GitHub Release 的说明文字。

  4. 运行 goreleaser:根据触发方式选择不同参数(release.yml#L62-L76):

    触发方式 goreleaser 参数 效果
    标签推送(真实发布) release --clean --release-notes tmp/release-note 构建全部制品并创建 GitHub Release
    手动运行(演练) release --snapshot --clean --skip=publish 构建 + 签名 + 公证,仅跳过 release 上传

    所需凭据全部来自仓库 Secrets:RELEASE_PAT(创建 release 的 token)、MACOS_SIGN_P12 / MACOS_SIGN_PASSWORD(签名证书)、MACOS_NOTARY_ISSUER_ID / MACOS_NOTARY_KEY_ID / MACOS_NOTARY_KEY(App Store Connect 公证密钥)。

构建与公证:.goreleaser.yml 中的实现

goreleaser 的具体行为由根目录的 .goreleaser.yml 定义,与发布流程直接相关的部分包括:

  • 交叉编译矩阵:覆盖 darwin/linux/windows/freebsd/openbsd/android × amd64/arm/arm64/loong64/ppc64le/s390x/riscv64,并排除若干不支持的组合(如 freebsd/armopenbsd/riscv64,见 .goreleaser.yml#L9-L48);
  • 版本注入ldflags 通过 -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }} 将版本与提交号写死进 main.goversion/revision 变量(.goreleaser.yml#L30-L33),这也解释了为什么 main.go 中的默认值只是占位;
  • macOS 签名 + 公证notarize.macos 段以 enabled: "{{ not .IsSnapshot }}" 控制——快照构建自动跳过公证,真实发布则先用 MACOS_SIGN_P12 证书签名,再用 App Store Connect 密钥执行公证且 wait: true 阻塞至完成(.goreleaser.yml#L51-L84);
  • 制品格式:非 Windows 平台打 tar.gz,Windows 打 zip;另通过 nfpms 生成含 man 页面与 LICENSE 的 deb 包(.goreleaser.yml#L86-L116);
  • 发布设置prerelease: auto 使带预发布后缀的 tag 自动标记为预发布版本;快照版本号模板为 {{ .Version }}-devel.goreleaser.yml#L118-L126)。

第四步:快进 master

GitHub Release 发布完成后,执行:

git push origin master

master 快进到包含新版本号的提交。至此发布窗口结束,/master/install 开始指向新版本,install 脚本中的版本与 master 分支源码保持一致。

演练发布流程:不产生真实 Release 的测试方法

RELEASE.md 专门给出了在不触发真实发布的前提下验证流水线的方法,非常适合在修改工作流 YAML 或轮换签名凭据后执行:

  1. 在 Actions 标签页点击 ReleaseRun workflow(即 workflow_dispatch);
  2. 选择一个分支,并输入该分支当前对应的版本号——版本一致性检查要求输入值与检出代码树中的文件内容匹配(例如 CHANGELOG.md 顶部、install 中的 version= 行);
  3. 按提示批准 release 环境门禁;
  4. 由于事件不是标签推送,goreleaser 以 release --snapshot --clean --skip=publish 运行:快照模式下 notarizeenabled: "{{ not .IsSnapshot }}" 会跳过公证,但构建、签名步骤照常执行,仅跳过 GitHub release 上传。

该方法可以一次性验证四件事:工作流 YAML 语法、版本提取逻辑、macOS runner 环境,以及签名/公证凭据是否有效——是发布前成本最低的回归手段。

关键文件索引

文件 在发布流程中的角色
RELEASE.md 发布流程的操作手册(本文主体)
Makefile prerelease/tag 目标的本地实现:一致性 grep 检查、签名并推送标签
.github/workflows/release.yml 标签触发的 CI:版本复核、release notes 提取、goreleaser 调用与环境门禁
.goreleaser.yml 多平台交叉编译、ldflags 版本注入、macOS 签名与公证、制品打包
main.go version/revision 变量,构建期由 ldflags 覆盖
install / install.ps1 硬编码待下载版本,是版本一致性检查的对象
CHANGELOG.md 版本条目来源,同时被裁剪为 GitHub Release 说明
BUILD.md 本地构建与测试说明(makemake build 等),与发布流程互为补充

适用前提提示:上述流程假定开发者持有仓库的推送权限、GPG/SSH 签名密钥,以及配置在仓库 Secrets 中的 macOS 签名与公证凭据;普通贡献者只需关注提交进入 master 后版本号相关文件的规范即可。

登录后查看全文
热门项目推荐
相关项目推荐