ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GitHub CLI 发布流程深度解析:cli/cli 的跨平台构建、签名、公证与包仓库发布机制

GitHub CLI 发布流程深度解析:cli/cli 的跨平台构建、签名、公证与包仓库发布机制 GitHub CLI 发布流程深度解析cli/cli 的跨平台构建、签名、公证与包仓库发布机制【免费下载链接】cliGitHub’s official command line tool项目地址: https://gitcode.com/GitHub_Trending/cli/cli本文基于 cli/cli 仓库中的 docs/release-process-deep-dive.md 展开逐层剖析gh官方命令行工具的完整发布流水线从deployment.yml工作流的标签校验、三大平台并行构建到 macOS 代码签名与公证、Windows Azure HSM 签名、Linux 包仓库 GPG 签名再到 GoReleaser 脚本级裁剪机制与 dry run 安全设计。读完本文你将能够完整理解一次生产发布如何从./script/release出发、在各 Job 间传递制品并最终安全地发布到 GitHub Release 与cli.github.com站点。背景为什么需要这份发布流程考古文档文档开篇交代了一个工程现实当前的发布工作流与配套脚本诞生于更早的维护者时代且当时的维护者已全部离开。在此后发布 macOS 安装器、迁移到 Azure HSM 签名、更新过期 GPG 密钥等若干场合现任维护者都曾花大量时间重新研究这套工作流。这份文档的定位正是给未来维护者的指南——它不是一份操作手册而是对deployment.yml工作流 及其配套脚本的逐 Job 源码级解读。高层概览一次发布的完整链路从高层看发布工作流的职责可以概括为六步由workflow_dispatch事件触发通常是执行./script/release的结果并行地构建、打包并签名 Linux、macOS、Windows 三平台的制品使用 GPG 签名 Debian 与 Red Hat 仓库制品构建并更新 CLI 手册 与包仓库为制品创建 GitHub Attestations溯源证明创建 GitHub Release 并挂载所有制品。工作流通过.github/workflows/deployment.yml中的workflow_dispatch输入参数控制行为当前仓库中定义的核心输入包括tag_name必填发布标签如v2.100.0或预发布v2.100.0-rc.1environment默认production可设为staging等非生产环境platforms默认linux,macos,windows逗号分隔可指定平台子集release是否执行最终的 release Job默认truedry_run是否干跑workflow_dispatch表单默认true。此外工作流还设置了concurrency组按 workflow ref name 分组、cancel-in-progress: true避免同一标签的重复运行互相干扰并通过permissions声明了attestations: write、contents: write、id-token: write三个必需权限。工作流的子集运行方式文档特别指出了工作流的几种部分执行模式这是理解整个发布系统安全模型的关键跳过 release Jobinputs.release设为false时整个 release Job 被跳过该能力未通过./script/release暴露staging 模式大量步骤由if: inputs.environment production守卫这些守卫保护两类操作——需要密钥的步骤如签名和产生外部变更的步骤如创建 GitHub Release。./script/release通过--staging标志触发该模式dry runinputs.dry_run设为true时表单默认值工作流仍然执行完整的 production 签名与打包步骤但不发布任何东西——不创建 Attestations、不创建 Release、不推送站点仓库。这使得维护者可以在不产生任何外部可见变更的前提下验证整条生产构建链路详见后文发布行为与 dry run单平台调试platforms只填linux、macos或windows之一时可单独调试某个构建 Job。此时 release Job 不应运行因为它needs: [linux, macos, windows]依赖全部三个 OS 构建。所有 OS 构建 Job以及 release Job都会 checkout 触发workflow_dispatch的 ref即传给gh workflow run的--ref由./script/release从--branch参数得出并设置timeout-minutes: 20——让挂死的构建例如等待远程服务的代码签名步骤快速失败而不是耗完默认的整段 Job 超时时间。Job 一validate-tag-name 标签格式校验validate-tag-nameJob 运行在ubuntu-latest上核心逻辑只有一段 bash 正则if [[ ! $TAG_NAME ~ ^v[0-9]\.[0-9]\.[0-9](-[0-9A-Za-z-](\.[0-9A-Za-z-])*)?$ ]]; then echo Invalid tag name format. Must be in the form v1.2.3 or v1.2.3-rc.1 exit 1 fi该 Job 的目的是防止打错标签的发布强制标签遵循v前缀的语义化版本major.minor.patch形式允许-rc.1这类可选的预发布后缀但拒绝之后的构建元数据。原因很具体——工作流后续通过标签中是否含连字符来识别预发布构建元数据会破坏这个约定当前工作流中此点已内联为注释。标签中的连字符会同时改变三件事release Job 创建 GitHub Release 时带上--prerelease站点cli.github.com不会被发布Windows MSI 会丢弃该后缀从而让ProductVersion保持纯数字。这一设计从源码中可以得到印证在 deployment.yml 的Create the release步骤里if [[ $TAG_NAME *-* ]]; then release_args( --prerelease ); fi正是看连字符定预发布的实现而Publish site步骤的DO_PUBLISH条件里也包含了!contains(inputs.tag_name, -)。Job 二~四三大平台的并行构建与签名标签校验通过后工作流在ubuntu、macos、windows三类 runner 上并行执行。这些 Job 的首要职责是构建并签名发布制品制品通过actions/upload-artifact传递给 release Job由actions/download-artifact取回上传均设置if-no-files-found: error与retention-days: 7。linux Job构建二进制、包与 Web 手册linux Job 的步骤编排是checkout →actions/setup-go版本取自go.mod→ 安装 GoReleaser →在 HEAD 上打一个临时标签git tag $TAG_NAME不推送远端目的是让构建出的二进制内嵌正确的版本号→ 执行script/release --local $TAG_NAME --platform linux→ 生成 Web 手册页 → 上传dist/*.tar.gz、dist/*.rpm、dist/*.deb。两个值得注意的细节linux Job 还负责构建 CLI 手册 网页版供后续 release Job 更新站点使用go run ./cmd/gen-docs --website --doc-path dist/manual tar -czvf dist/manual.tar.gz -C dist -- manual手册页由 cmd/gen-docs 从命令树生成随后被压缩为manual.tar.gz作为 linux 制品的一部分上传。linux Job 本身不做任何签名——.deb/.rpm的 GPG 签名发生在 release Job 中见后文而 Linux 二进制无需平台级代码签名。构建细节GoReleaser 如何被裁剪运行参见后文 script/release 工作机制。macos Job签名、公证与 Universal 安装器macos Job 是三个平台中签名链路最复杂的文档将其归纳为三个层级的签名Go 可执行文件签名由 .goreleaser.yml 中 macos 构建条目的posthook 触发./script/sign {{ .Path }}.zip归档公证工作流中直接执行script/sign dist/gh_*_macOS_*.zipNotarize macOS archives步骤仅 production 环境.pkg安装器签名在script/pkgmacos内部调用productbuild时完成。文档在此附了一条重要的术语警告该步骤标题虽为Build notarize universal macOS pkg installer但productbuild文档只提到 signing因此这里的notarization用词可能并不准确。另有一条历史 NOTE由于仓库变量APPLE_DEVELOPER_INSTALLER_ID曾被遗漏而从未设置.pkg安装器历史上实际上从未被真正签名工作流仍在传递该变量一旦补齐该变量productbuild的签名即会生效。这一点在当前 script/pkgmacos 源码中得到印证# include signing if developer id is set if [ -n $APPLE_DEVELOPER_INSTALLER_ID ]; then PRODUCTBUILD_ARGS(--timestamp) PRODUCTBUILD_ARGS(--sign) PRODUCTBUILD_ARGS(${APPLE_DEVELOPER_INSTALLER_ID}) else echo skipping macOS pkg code-signing; APPLE_DEVELOPER_INSTALLER_ID not set 2 fi三个 production 专属的准备步骤签名与公证的准备横跨三个只在inputs.environment production时运行的步骤Install code signing certificate创建专用 keychain导入codesign使用的 Developer ID Application 证书Add App Store Connect API key to keychain使用nodeselector/setup-apple-codesignaction 将 App Store Connect API key.p8格式notarytool的认证凭证物化并输出 key path、key id、issuer idConfigure notarization credentials执行xcrun notarytool store-credentials把上述 API key 信息以notarytool-password档案名持久化进 keychain后续notarytool submit即可引用档案而无需直传凭证。第一步的 keychain 脚本含工作流中补充的注释如下# 用 run 编号派生每次运行独立的 keychain 密码避免硬编码 PWpwd.${{ github.run_number }} # 创建存放凭证的新 keychain security create-keychain -p $PW $RUNNER_TEMP/build.keychain # 提高自动锁定超时6 小时避免构建中途锁定 security set-keychain-settings -lut 21600 $RUNNER_TEMP/build.keychain # 标记为系统默认 keychain后续签名步骤无需再按名引用 security default-keychain -s $RUNNER_TEMP/build.keychain # 解锁 keychain后续操作可直接读取秘密 security unlock-keychain -p $PW $RUNNER_TEMP/build.keychain # 把 base64 编码的证书秘密解码为 .p12。证书与密码经 env 变量传入 # DEVELOPER_ID_CERT / DEVELOPER_ID_CERT_PASSWORD 映射自 secrets # 而不是直接插值进脚本含 shell 元字符的密码不会破坏引号或注入命令 base64 -d $DEVELOPER_ID_CERT $RUNNER_TEMP/cert.p12 # 导入证书供后续签名步骤使用 # -k 指定 keychain-P 直接给解包口令默认会弹 GUI # -T 授予 /usr/bin/codesign 访问导入私钥的权限 security import $RUNNER_TEMP/cert.p12 -k $RUNNER_TEMP/build.keychain -P $DEVELOPER_ID_CERT_PASSWORD -T /usr/bin/codesign # 设置 partition list只有签名相关的应用可以访问 keychain # 三个分区值 # apple-tool: → Apple 开发工具 # apple: → Apple 通用加密工具 # codesign: → codesign 工具用于签名二进制与应用 security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $PW $RUNNER_TEMP/build.keychain # 清理证书文件避免遗留给后续步骤泄露 rm $RUNNER_TEMP/cert.p12文档还特别说明了凭证安全性的演进证书与口令来自GATEWATCHER_DEVELOPER_ID_CERT/GATEWATCHER_DEVELOPER_ID_PASSWORD两个 secret取代了旧的APPLE_APPLICATION_CERT系列 secret通过env:映射后以$DEVELOPER_ID_CERT形式引用杜绝了 shell 插值注入风险keychain 也从旧名buildagent.keychain改名为build.keychain并通过KEYCHAIN环境变量显式传给签名脚本。script/sign签名与公证的实际执行者codesign --timestamp --optionsruntime -s ${DEVELOPER_ID_CERT_IDENTIFIER?} -v $1这条命令中codesign会在 keychain 中查找与DEVELOPER_ID_CERT_IDENTIFIER源自仓库变量MAC_APP_SIGNING_IDENTITY匹配的证书--timestamp与--optionsruntime启用 hardened runtime是公证的前置硬性要求。当前仓库中的 script/sign 实现印证了文档描述的分发逻辑sign_macos() { if [[ -z $DO_SIGN_ARTIFACTS || $DO_SIGN_ARTIFACTS false ]]; then echo skipping macOS code-signing; DO_SIGN_ARTIFACTS not set or false 2 return 0 fi # ... 类似检查 DEVELOPER_ID_CERT_IDENTIFIER 与 KEYCHAIN ... if [[ $1 *.zip ]]; then xcrun notarytool submit $1 --keychain $KEYCHAIN --keychain-profile notarytool-password --wait else codesign --timestamp --optionsruntime -s ${DEVELOPER_ID_CERT_IDENTIFIER?} -v $1 fi }即zip 文件走 notarytool 公证认证凭据来自前述notarytool-password档案取代了旧的--apple-id/--team-id/--password流程可执行文件走 codesign。DO_SIGN_ARTIFACTS非false才执行签名这一守卫保证了 staging 构建在 keychain 从未配置的情况下能优雅跳过而非报错。文档附带的排障 TIP 同样实用若codesign报***: no identity found说明vars.MAC_APP_SIGNING_IDENTITY与 keychain 中实际导入的标识不匹配执行security find-identity -v -p codesigning $KEYCHAIN列出可用标识后精确更新该仓库变量即可。概念澄清代码签名 vs 公证文档在分隔线后厘清了两个常被混淆的概念代码签名证明一个gh可执行文件确实由 GitHub 创建**公证Notarization**则是把软件提交给 Apple 服务器做自动化扫描通过后 Apple 生成一张ticket可staple装订到软件上使 macOS Gatekeeper 知晓其已获审核。windows JobAzure HSM 远程签名与 WiX MSI 构建windows Job 的签名走的是Azure HSMAzure Dedicated HSM远程代码签名链路分四步第一步下载 Azure Code Signing 客户端 DLL。signtool.exe需要该 DLL 才能与 HSM 服务交互Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Azure.CodeSigning.Client/1.0.43 -OutFile $Env:ACS_ZIP -Verbose Expand-Archive $Env:ACS_ZIP -Destination $Env:ACS_DIR -Force -Verbose第二步生成 signtool 所需的 metadata.json{ CertificateProfileName GitHubInc CodeSigningAccountName GitHubInc CorrelationId $Env:CORRELATION_ID # 指向本次 Actions 运行便于审计关联 Endpoint https://wus.codesigning.azure.net/ } | ConvertTo-Json | Out-File -FilePath $Env:METADATA_PATH第三步script/sign.ps1 定位signtool$signtool Resolve-Path C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe | Select-Object -Last 1第四步执行签名 $signtool sign /d GitHub CLI /fd sha256 /td sha256 /tr http://timestamp.acs.microsoft.com /v /dlib $Env:DLIB_PATH /dmdf $Env:METADATA_PATH $Args[0]各参数含义/fd为文件摘要算法、/td为时间戳摘要算法、/tr指定时间戳服务器在签名中证明签名时间、/dlib指向已解压的 DLL、/dmdf指向 metadata 文件。当前仓库的 script/sign.ps1 中还保留着与 macos 对称的守卫DO_SIGN_ARTIFACTS、DLIB_PATH、METADATA_PATH任一缺失即打印提示并退出保证非生产构建不会触发 HSM 签名。windows Job 的两个签名层级分别是GoReleaser 产出的 Go 可执行文件在 .goreleaser.yml 的 windows 构建条目posthook 中执行pwsh .\script\sign.ps1 {{ .Path }}与MSI 安装器Sign .msi release binaries步骤逐个执行.\script\sign.ps1。两处均设置DO_SIGN_ARTIFACTS: ${{ inputs.environment production }}——尽管 MSI 签名步骤本身已有 environment 守卫保留该变量是为了与构建步骤保持一致。MSI 构建MSBuild WiX除了 GoReleaser 产出的.zipwindows Job 还使用MSBuild.exe为每种架构构建 MSI 安装器包装 GoReleaser 生成的架构 zip${MSBUILD_PATH}\MSBuild.exe ./build/windows/gh.wixproj -p:SourceDir$source_dir -p:OutputPath$PWD/dist -p:OutputName$MSI_NAME -p:ProductVersion${MSI_VERSION#v} -p:Platform$platform-p:ProductVersion${MSI_VERSION#v}的${...#v}正好实现了前述MSI 丢弃预发布后缀、保持数字版本号的行为bash 的#v去掉 v 前缀且MSI_VERSION在cut -d- -f1处已截断连字符后缀。这段循环对_386/_amd64/_arm64三种架构 zip 分别映射到windows_windows_386_sse2、windows_windows_amd64_v1、windows_windows_arm64_v8.0等 GoReleaser 产物目录当前工作流中的架构目录名如sse2后缀与arm64_v8.0与文档快照时的命名略有演进最终调用 build/windows 下的 WiX 工程文件。文档指出这些文件相当晦涩构成一种清单manifest其内容与动机参见引入它们的 PR。当前仓库状态说明以当前 deployment.yml 为准windows Job 已改用windows-2022runner并新增了azure/loginOIDC 认证步骤allow-no-subscriptions: trueAzure Code Signing 客户端包名也已演进为Microsoft.Trusted.Signing.Client。这些属于文档快照之后的安全加固机制与文档描述一致。Job 五release 聚合、签名仓库与发布release Job 运行在ubuntu-latest上needs: [linux, macos, windows]整体受if: inputs.release守卫。文档说明其小节并非严格按工作流顺序排列而是按职责归类。站点手册更新release Job 会在cli.github.com站点仓库中创建一个包含 linux Job 上传的手册内容的 git 提交但此时不推送推送到后面包仓库就绪之后。站点仓库使用短生命周期的 GitHub App 安装 token而非长期 PATGenerate site deploy token步骤仅 production通过actions/create-github-app-token配合SITE_DEPLOY_APP_CLIENT_ID/SITE_DEPLOY_APP_PRIVATE_KEY铸造一个仅作用于github/cli.github.com仓库的 token取代了旧的SITE_DEPLOY_PATsecret。所有站点相关步骤checkout 站点、更新手册、createrepo、reprepro、Publish site都由if: inputs.environment production守卫非生产环境下站点既不被 checkout 也不被变更即便在 production推送仍由独立的DO_PUBLISH门控。手册更新脚本还通过sed更新站点index.html中的assign version ...字段并用git -C site diff --quiet --cached || git -C site commit保证无实际变更则不产生空提交。GPG 密钥准备为了提供安全的包安装方式cli.github.com托管的 RPM 与 Debian 包仓库支撑 docs/install_linux.md 中的官方软件源安装方式中的所有制品都由 GPG 密钥签名。release Job 先把密钥载入gpg# 非交互式导入公钥与私钥 base64 -d $GPG_PUBKEY | gpg --import --no-tty --batch --yes base64 -d $GPG_KEY | gpg --import --no-tty --batch --yes # 配置 gpg 允许预设口令避免后续每次操作都提供口令 echo allow-preset-passphrase ~/.gnupg/gpg-agent.conf # 通知 gpg 重载配置 gpg-connect-agent RELOADAGENT /bye # 将特定密钥按 keygrip 引用的口令存入内存 /usr/lib/gnupg2/gpg-preset-passphrase --preset $GPG_KEYGRIP $GPG_PASSPHRASE当前仓库状态说明当前 deployment.yml 中已同时导入两代 GPG 密钥GPG_*与GPG_*_2026两组 secret口令以 base64 管道传入gpg-preset-passphraseRun createrepo步骤的repomd.xml分离签名也显式指定了--default-key 2C6106201985B60E6C7AC87323F3D4EA75716059——这正是 script/rpmmacros 中%_gpg_name与 script/distributions 各SignWith行引用的那个 Key ID对应文档开头提到的更新过期 GPG 密钥这一维护事件。RPM 仓库rpmsign createrepolinux Job 上传的.rpm由rpmsign --addsign dist/*.rpm签名前一步cp script/rpmmacros ~/.rpmmacros提供签名者配置。createrepo 工具生成描述 Red Hat 仓库内容的repomd.xml元数据制品与repomd.xml拷入站点仓库后用gpg --yes --detach-sign --armor repodata/repomd.xml产出分离签名文件。文档附带一条 WARNINGcreaterepo目前仍在一个 Docker 容器fedora:32镜像 createrepo_c中执行这是出于包管理原因的历史选择参见当年 PR该理由可能已不再成立。当前 script/createrepo.sh 源码确认了这一实现原样保留动态生成 Dockerfile、docker build后以卷挂载dist/执行。Debian 仓库reprepro.deb文件按 Debian 发行版逐个迭代处理使用reprepro生成 Debian 包仓库的目录与文件结构for release in $RELEASES; do for file in dist/*.deb; do reprepro --confdirb/script includedeb $release $file done done其中RELEASES列表当前包含stable oldstable testing sid unstable、Ubuntu 各版本代号cosmic至impish等历史版本以及kali-rolling。script/distributions 配置文件中每个 Codename 段落的SignWith行指明了reprepro应使用哪个 GPG Key ID 签名。文档在此留了两条值得记住的备注其一apt 安装文档已统一要求使用stable不再向发行版列表新增条目列表中的遗留发行版未来会移除其二遗留发行版的清理时间点尚无明确计划。Attestations 溯源证明release Job 通过actions/attest为每个发布制品创建 Attestations溯源记录证明制品由哪次构建产生。该步骤的守卫是inputs.environment production !inputs.dry_run——因为 Attestation 属于外部可见的溯源记录只应为真实发布产生干跑一律跳过。GitHub Release 与校验和所有制品生成并签名后Create the release步骤先为dist/下全部gh_*文件生成 SHA-256 校验和shasum -a 256 gh_* checksums.txt改名为gh_${TAG_NAME#v}_checksums.txt供gh用户校验所下载制品然后调用gh release create创建 Release、挂载全部归档/包/安装器与校验和文件。制品文件名附带人类可读的显示标签机制如下Upload a release asset with a display label $ gh release create v1.2.3 /path/to/asset.zip#My display label生成标签逻辑由 script/label-assets 实现label$(basename $asset) label${label%.*} # 去掉扩展名 label${label%.tar} # 处理 .tar.gz 的双扩展 labelGitHub CLI $(tr _ ${label#gh_}) case $asset in *.msi ) label${label} installer ;; *.deb ) label${label} deb ;; *.rpm ) label${label} RPM ;; esac printf %s#%s\n $asset $label输出的路径#标签形式经xargs交给gh release create。至于当初为何使用可读标签文档承认除了相关 PR 中这是有意为之的评论外并无更多说明。Release 创建的关键参数--title GitHub CLI ${TAG_NAME#v}、--target $GITHUB_SHA精确锚定到构建的提交、--generate-notes以及标签含连字符时追加的--prerelease。站点发布与 HomebrewPublish site步骤在./site工作目录下提交包仓库结构并推送推送触发站点仓库自身的部署工作流。推送仅当DO_PUBLISH为trueproduction、非预发布标签、非 dry run时发生否则打印git log --oneline {upstream}..与git diff --name-status {upstream}..供检查。文档还提到一个运维风险由于长期托管大量大型二进制品站点仓库偶尔会变得臃肿处理方法见该仓库 README。关于 Homebrew历史上发布流程使用bump-homebrew-formula-action为gh的 homebrew-core formula 创建 PRfork 仓库由个人账号持有因为组织间的 PR 不受支持。由于该方式依赖遗留 PAT 在两个仓库间开 PR、被评估为安全风险过高现已改由Homebrew 的 autobump 机制自动跟进新版 formula发布工作流不再直接参与。发布行为与 dry rundry_run输入workflow_dispatch表单默认true的布尔值是叠加在environment守卫之上的最终安全阀。当dry_run为true时工作流仍执行完整的 production 构建——包括代码签名、公证与包仓库生成——但跳过一切会变更外部可见状态的步骤步骤守卫条件Attest release artifactsinputs.environment production !inputs.dry_runCreate the releasegh release createDO_PUBLISH: inputs.environment production !inputs.dry_runPublish site推送cli.github.comDO_PUBLISH: inputs.environment production !contains(inputs.tag_name, -) !inputs.dry_runCreate the release与Publish site步骤通过DO_PUBLISH环境变量落地这一门控当其为false时release 命令前缀echogh release create只被打印而不执行见工作流中guardecho; [ $DO_PUBLISH false ] || guard的写法站点推送则被替换为待提交内容的git log/git diff。这意味着 dry run 能把整条流水线端到端跑通是验证签名与打包变更而不产生任何外部副作用的安全方式。为了让 dry run 在 Actions UI 中一眼可辨工作流的run-name在inputs.dry_run为true时追加(dry run)后缀run-name: ${{ inputs.tag_name }} / ${{ inputs.environment }}${{ inputs.dry_run true (dry run) || }}重要提醒文档原文的 IMPORTANT 框dry_run的默认值取决于触发方式两者恰好相反。workflow_dispatch表单默认true——手动触发除非明确取消勾选否则是干跑而./script/release默认dry_runfalse仅当带--dry-run标志时才转发dry_runtrue。换言之./script/release tag-name执行真实发布./script/release --dry-run tag-name完整跑流程但不发布。Deepest Dive核心脚本机制script/release 的工作机制script/release 是维护者创建新发布的入口对应 docs/releasing.md 的操作流程。调用时它执行gh workflow run触发前述工作流但工作流各 OS Job 又会以--local标志回调script/release在 runner 机器上实际产出制品并附加--platform指定平台。该脚本最反直觉的行为是用sed裁剪基础 .goreleaser.yml只保留目标平台的配置段。当前源码清晰展示了这一点build_local() { local goreleaser_config.goreleaser.yml case $platform in linux ) sed /#build:windows/,/^$/d; /#build:macos/,/^$/d .goreleaser.yml .goreleaser.generated.yml goreleaser_config.goreleaser.generated.yml ;; # macos / windows 同理删除另两个平台的标记段 esac [ -z $tag_name ] || export GORELEASER_CURRENT_TAG$tag_name announce goreleaser release -f $goreleaser_config --clean --skip validate,publish,announce --release-notes$(mktemp) }裁剪依赖.goreleaser.yml中各配置段落的#build:platform标记注释。例如 linux 平台下只有标记为#build:linux的linuxbuild 条目与nfpms段.deb/.rpm包配置会生效archives段则通过ids: [linux]这类前置平台构建约束自动衔接。每个 build 条目声明支持的平台与架构例如- id: linux #build:linux goos: [linux] goarch: [386, arm, amd64, arm64]从 .goreleaser.yml 还能看到发布构建的几个关键工程细节版本内嵌三个平台macos/linux/windows的构建都通过ldflags注入-X github.com/cli/cli/v2/internal/build.Version{{.Version}} -X github.com/cli/cli/v2/internal/build.Date...配合工作流中的临时打标签步骤保证二进制gh version输出正确前置 hookbefore段在各平台生成 manpages 与 shell 补全Windows 端用echo跳过本机生成改为交叉产物打包非 Windows 构建还会执行 script/gen-winres.ps1 基于 script/versioninfo.template.json 生成各架构的.syso文件把 Windows 版本信息资源嵌入 exe签名 hookmacos 与 windows 条目的posthook 分别调用./script/sign与pwsh .\script\sign.ps1这正是文档所述可执行文件签名发生在 GoReleaser hook 中的落点nfpms 打包linux 的deb/rpm包除二进制外还安装 man 页与各 shell 补全脚本含 Debian/Ubuntu zsh 的vendor-completions双路径处理release 段draft: true注释写明只在 Windows MSI 上传后才真正发布prerelease: auto。此外script/release的trigger_deployment函数展示了对用户的友好设计announce会先以$TMPDIR占位符打印命令避免泄露临时目录路径再实际执行生产部署触发后会提示Go to Slack to manually approve this production deployment即production 环境部署还叠加了人工审批关卡GitHub Environments 的审批机制。脚本还支持--current基于当前分支构建 staging 二进制、自动取最近 tag等模式。注意工作流中 GoReleaser 的版本是有意固定的工作流注释明确说明固定版本不仅出于安全还因为配套脚本依赖 GoReleaser 生成的特定文件命名。当前仓库固定为v2.13.1文档快照时为~1.17.1。script/pkgmacos 的工作机制script/pkgmacoszsh 脚本要求 macOS 12 与 Xcode Command Line Tools被 macos Job 用于构建 Universal.pkg安装器串联三个核心工具lipo把arm64与amd64两个 Go 二进制合并为单一 Universal 二进制lipo -create -output ${payload_local_bin}/gh \ ./dist/macos_darwin_arm64_v8.0/bin/gh ./dist/macos_darwin_amd64_v1/bin/ghpkgbuild构建component package——安装器实际要装的载荷。对gh而言该包标识为com.github.cli内容为 Universal 二进制、zsh 补全Catalina 起 macOS 默认 shell 为 zsh故只含 zsh 补全与 man 页pkgbuild \ --root $payload_root \ --identifier com.github.cli \ --version $tag_name \ --install-location / \ ./dist/com.github.cli.pkgproductbuild构建product archive——安装器真正使用的产品包。除 component package 外product archive 还可携带定制化安装元素gh打包了LICENSE文件并使用仓库中的 build/macOS/distribution.xml 作为安装器 GUI 脚本。该distribution.xml是文档未展开的实操细节值得补充它声明了安装器标题GitHub CLI、以text/plain呈现的 LICENSE 协议页、arm64,x86_64双架构选项并通过内嵌 JavaScript 的installCheck()函数在系统低于 macOS 12 时给出用户友好的致命错误提示GitHub CLI requires macOS 12 or later.配合allowed-os-versions的min12.0双重把关。脚本最后通过trap cleanup EXIT删除中间产物com.github.cli.pkg确保它不会被误上传为发布资产最终产物命名为gh_${tag_name}_macOS_universal.pkg。pkgbuild与productbuild的区别前者是载荷组件包后者是面向安装器的产品归档是理解 macOS 安装器体系的关键文档建议读者自行查阅相关资料深入。小结这套发布系统的防御性设计综合文档与源码cli/cli 的发布流程在多个层面体现了纵深防御的思路值得借鉴入口约束标签格式正则在最前端拦截错误发布连字符语义贯穿 Release/站点/MSI 三处行为环境分层staging/production环境守卫 dry_run二次门控 production 人工审批三重机制保证能完整演练但默认不产生外部副作用密钥卫生凭证一律经env:注入而非 shell 插值防注入、base64 编码存储、短期 tokenGitHub App token / OIDC替代长期 PAT、keychain 每 run 独立密码、用后rm证书文件信任链闭环二进制签名codesign/Azure HSM→ 公证notarytool→ 包仓库 GPG 签名rpmsign/reprepro/repomd→ SHA-256 校验和 → GitHub Attestations从下载到溯源逐层可验证。对需要维护或复刻类似发布流水线的开发者而言本文引用的全部关键路径均已给出工作流主体.github/workflows/deployment.yml、发布入口script/release、打包配置.goreleaser.yml、签名脚本script/sign与script/sign.ps1、macOS 安装器script/pkgmacos与build/macOS/distribution.xml、仓库元数据script/createrepo.sh、script/distributions、script/rpmmacros与资产标签script/label-assets。结合 docs/release-process-deep-dive.md 原文即可对整条发布链路做到逐行可核对。【免费下载链接】cliGitHub’s official command line tool项目地址: https://gitcode.com/GitHub_Trending/cli/cli创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表