This is an automated email from the ASF dual-hosted git repository.
wwbmmm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new 25dc2130 Add release skill (#3537)
25dc2130 is described below
commit 25dc21305155a947c66c1a5d0b458af2e18a9c9f
Author: Bright Chen <[email protected]>
AuthorDate: Sun Sep 13 11:09:31 2026 +0800
Add release skill (#3537)
---
community/release_cn.md | 36 ++-
community/release_en.md | 41 ++-
community/skills/README.md | 221 ++++++++++++++
community/skills/brpc-release/SKILL.md | 286 ++++++++++++++++++
.../skills/brpc-release/references/checklist.md | 212 ++++++++++++++
.../brpc-release/references/mail_templates.md | 323 +++++++++++++++++++++
.../brpc-release/references/state_template.md | 73 +++++
.../skills/brpc-release/scripts/bump_version.sh | 183 ++++++++++++
.../skills/brpc-release/scripts/make_package.sh | 177 +++++++++++
.../skills/brpc-release/scripts/release_notes.sh | 240 +++++++++++++++
.../skills/brpc-release/scripts/verify_package.sh | 253 ++++++++++++++++
11 files changed, 2019 insertions(+), 26 deletions(-)
diff --git a/community/release_cn.md b/community/release_cn.md
index ee24c1bc..4774eb81 100644
--- a/community/release_cn.md
+++ b/community/release_cn.md
@@ -1,5 +1,7 @@
# brpc 发布 apache release 版本流程 step by step
+> **推荐先使用发版 Skill**:[`community/skills/brpc-release/`](skills/brpc-release)
将本流程编排为可恢复的阶段任务,提供版本更新、Release Notes、打包和验包脚本,并在 GPG 签名、推 tag、SVN 提交、发邮件及 GitHub
Release 等不可逆操作前停止并提示 Release Manager(RM)手工执行。安装方法、依赖检查、RM/校验者使用指南见
[`community/skills/README.md`](skills/README.md)。本文件仍是权威发布流程;Skill
与本文不一致时,以本文为准并同步修正 Skill。
+
## 准备工作
### 1. 确认 Release Notes
@@ -199,6 +201,14 @@ set(BRPC_VERSION 1.0.0)
Version: 1.0.0
```
+#### 更新 `CLAUDE.md` 文件
+
+编辑项目根目录下 `CLAUDE.md` 文件,将项目概述中的 `Current version` 更新为本次发布版本:
+
+```
+Current version: 1.0.0.
+```
+
#### 更新 `MODULE.bazel` 文件
编辑项目根目录下 `MODULE.bazel` 文件,更新版本号,并提交至代码仓库,本文以 `1.0.0` 版本为例,修改 `version` 为:
@@ -262,13 +272,13 @@ sha512sum --check
apache-brpc-$BRPCVERSION-src.tar.gz.sha512
如无本地工作目录,则先创建本地工作目录。将 Apache SVN 仓库克隆下来,username 需要使用自己的 Apache LDAP 用户名。
```bash
-mkdir -p ~/brpc_svn/dev/
+mkdir -p ~/brpc_release/svn/dev/
-cd ~/brpc_svn/dev/
+cd ~/brpc_release/svn/dev/
svn --username=$BRPCUSERNAME co https://dist.apache.org/repos/dist/dev/brpc/
-cd ~/brpc_svn/dev/brpc
+cd ~/brpc_release/svn/dev/brpc
```
### 2. 添加 GPG 公钥
@@ -291,15 +301,15 @@ cd ~/brpc_svn/dev/brpc
### 3. 将待发布的代码包添加至 SVN 目录
```bash
-mkdir -p ~/brpc_svn/dev/brpc/$BRPCVERSION
+mkdir -p ~/brpc_release/svn/dev/brpc/$BRPCVERSION
-cd ~/brpc_svn/dev/brpc/$BRPCVERSION
+cd ~/brpc_release/svn/dev/brpc/$BRPCVERSION
-cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz ~/brpc_svn/dev/brpc/$BRPCVERSION
+cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz
~/brpc_release/svn/dev/brpc/$BRPCVERSION
-cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz.asc
~/brpc_svn/dev/brpc/$BRPCVERSION
+cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz.asc
~/brpc_release/svn/dev/brpc/$BRPCVERSION
-cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz.sha512
~/brpc_svn/dev/brpc/$BRPCVERSION
+cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz.sha512
~/brpc_release/svn/dev/brpc/$BRPCVERSION
```
### 4. 提交 SVN
@@ -307,7 +317,7 @@ cp ~/brpc/apache-brpc-$BRPCVERSION-src.tar.gz.sha512
~/brpc_svn/dev/brpc/$BRPCVE
退回到上级目录,使用 Apache LDAP 账号提交 SVN。
```bash
-cd ~/brpc_svn/dev/brpc
+cd ~/brpc_release/svn/dev/brpc
svn add *
@@ -316,7 +326,7 @@ svn --username=$BRPCUSERNAME commit -m "release
$BRPCVERSION"
## 检查发布结果
```bash
-cd ~/brpc_svn/dev/brpc/$BRPCVERSION
+cd ~/brpc_release/svn/dev/brpc/$BRPCVERSION
```
### 1. 检查 sha512 哈希
@@ -404,8 +414,8 @@ diff -r brpc-$BRPCVERSION apache-brpc-$BRPCVERSION-src
### 1. 投票阶段
-1. 发起投票邮件到 [email protected]。PMC 需要先按文档检查版本的正确性,然后再进行投票。经过至少 72 小时并统计到 3 个
+1 PMC member 票后,方可进入下一阶段。
-2. 宣布投票结果,发起投票结果邮件到 [email protected]。
+1. 发起投票邮件到 [email protected]。邮件发出后,在 [[email protected]
邮件归档](https://lists.apache.org/[email protected]) 找到该邮件并保存其永久链接。PMC
需要先按文档检查版本的正确性,然后再进行投票。经过至少 72 小时并统计到 3 个 +1 PMC member 票后,方可进入下一阶段。
+2. 宣布投票结果,发起投票结果邮件到 [email protected];同样在邮件归档中保存结果邮件的永久链接。
### 2. 投票邮件模板
@@ -487,7 +497,7 @@ Non-binding votes:
- bbb
- ccc
-Vote thread: xxx (vote email link in https://lists.apache.org/)
+Vote thread: {VOTE_THREAD_URL}
Thank you to all the above members to help us to verify and vote for
the 1.0.0 release. I will process to publish the release and send ANNOUNCE.
diff --git a/community/release_en.md b/community/release_en.md
index 8a314826..d48cf743 100644
--- a/community/release_en.md
+++ b/community/release_en.md
@@ -1,5 +1,12 @@
# brpc apache release guide step by step
+> **Start with the release Skill when possible**:
[`community/skills/brpc-release/`](skills/brpc-release) orchestrates
+> this process as resumable stages, provides scripts for version updates,
Release Notes, packaging, and verification,
+> and stops for the Release Manager (RM) before irreversible actions such as
GPG signing, pushing tags, SVN commits,
+> sending emails, and publishing a GitHub Release. See
[`community/skills/README.md`](skills/README.md) for installation,
+> dependency checks, and RM/verifier usage guidance. This document remains the
authoritative release process; if the
+> Skill differs from this guide, follow this guide and update the Skill
accordingly.
+
## Preparation
### 1. Confirm the release notes
@@ -198,6 +205,14 @@ Edit the `/package/rpm/brpc.spec` file in the project root
directory, update the
Version: 1.0.0
```
+#### Update the `CLAUDE.md` file
+
+Edit the `CLAUDE.md` file in the project root directory and update `Current
version` in the project overview to the release version:
+
+```
+Current version: 1.0.0.
+```
+
#### Update the `MODULE.bazel` file
Edit the `MODULE.bazel` file in the project root directory, update the version
number, and submit it to the code repository. For example:
@@ -258,13 +273,13 @@ sha512sum --check apache-brpc-1.0.0-src.tar.gz.sha512
If there is no local working directory, create a local working directory
first. Checkout the Apache SVN repository, username needs to use your own
Apache LDAP username:
```bash
-mkdir -p ~/brpc_svn/dev/
+mkdir -p ~/brpc_release/svn/dev/
-cd ~/brpc_svn/dev/
+cd ~/brpc_release/svn/dev/
svn --username=lorinlee co https://dist.apache.org/repos/dist/dev/brpc/
-cd ~/brpc_svn/dev/brpc
+cd ~/brpc_release/svn/dev/brpc
```
### 2. Add GPG public key
@@ -289,15 +304,15 @@ By fingerprint:
### 3. Add the releasing package to SVN directory
```bash
-mkdir -p ~/brpc_svn/dev/brpc/1.0.0
+mkdir -p ~/brpc_release/svn/dev/brpc/1.0.0
-cd ~/brpc_svn/dev/brpc/1.0.0
+cd ~/brpc_release/svn/dev/brpc/1.0.0
-cp ~/brpc/apache-brpc-1.0.0-src.tar.gz ~/brpc_svn/dev/brpc/1.0.0
+cp ~/brpc/apache-brpc-1.0.0-src.tar.gz ~/brpc_release/svn/dev/brpc/1.0.0
-cp ~/brpc/apache-brpc-1.0.0-src.tar.gz.asc ~/brpc_svn/dev/brpc/1.0.0
+cp ~/brpc/apache-brpc-1.0.0-src.tar.gz.asc ~/brpc_release/svn/dev/brpc/1.0.0
-cp ~/brpc/apache-brpc-1.0.0-src.tar.gz.sha512 ~/brpc_svn/dev/brpc/1.0.0
+cp ~/brpc/apache-brpc-1.0.0-src.tar.gz.sha512 ~/brpc_release/svn/dev/brpc/1.0.0
```
### 4. Submit SVN
@@ -305,7 +320,7 @@ cp ~/brpc/apache-brpc-1.0.0-src.tar.gz.sha512
~/brpc_svn/dev/brpc/1.0.0
Return to the parent directory and use the Apache LDAP account to submit SVN
```bash
-cd ~/brpc_svn/dev/brpc
+cd ~/brpc_release/svn/dev/brpc
svn add *
@@ -314,7 +329,7 @@ svn --username=lorinlee commit -m "release 1.0.0"
## Verify release
```bash
-cd ~/brpc_svn/dev/brpc/1.0.0
+cd ~/brpc_release/svn/dev/brpc/1.0.0
```
### 1. Verify SHA512 checksum
@@ -406,8 +421,8 @@ This stage will cost 3+ days.
### 1. Vote stage
-1. Send a voting email to `[email protected]`. PMC needs to check the
correctness of the version according to the document before voting. After at
least 72 hours and 3 +1 PMC member votes, you can move to the next stage.
-2. Announce the voting result and send the voting result to
[email protected].
+1. Send a voting email to `[email protected]`. After sending it, find the
message in the [[email protected] mail
archive](https://lists.apache.org/[email protected]) and save its
permanent link. PMC needs to check the correctness of the version according to
the document before voting. After at least 72 hours and 3 +1 PMC member votes,
you can move to the next stage.
+2. Announce the voting result and send the voting result to
[email protected]; likewise save the permanent link of the result email from
the mail archive.
### 2. Vote email template
@@ -491,7 +506,7 @@ Non-binding votes:
- bbb
- ccc
-Vote thread: xxx (vote email link in https://lists.apache.org/)
+Vote thread: {VOTE_THREAD_URL}
Thank you to all the above members to help us to verify and vote for
the 1.0.0 release. I will process to publish the release and send ANNOUNCE.
diff --git a/community/skills/README.md b/community/skills/README.md
new file mode 100644
index 00000000..3bec9ec7
--- /dev/null
+++ b/community/skills/README.md
@@ -0,0 +1,221 @@
+# 社区维护工作的 AI Agent Skills
+
+这里放给社区维护者使用的通用 AI agent skill,可供 GitHub Copilot、Claude Code、Codex、Cursor
+等支持 skill 的编码助手使用。每个 skill 是一个自包含目录,权威流程仍然是 `community/` 下对应的
+`.md` 文档;skill 只负责把流程编排起来、自动化机械步骤、并在不可逆操作前刹车。
+
+| Skill | 用途 | 对应文档 |
+|---|---|---|
+| `brpc-release` | Apache bRPC 发版全流程 |
`community/release_cn.md`、`release_en.md`、`releasecheck.md` |
+
+## 安装指南
+
+Skill 可以安装在项目级目录(仅当前仓库生效),也可以安装在用户级目录(所有项目生效)。推荐使用
+符号链接,仓库里的 skill 更新后无需重新复制。以下命令均在 bRPC 仓库根目录执行。
+
+### Claude Code
+
+项目级目录为 `.claude/skills/`:
+
+```bash
+mkdir -p .claude/skills
+ln -sfn ../../community/skills/brpc-release .claude/skills/brpc-release
+```
+
+用户级安装:
+
+```bash
+mkdir -p ~/.claude/skills
+ln -sfn "$(pwd)/community/skills/brpc-release" ~/.claude/skills/brpc-release
+```
+
+### GitHub Copilot
+
+项目级目录为 `.github/skills/`:
+
+```bash
+mkdir -p .github/skills
+ln -sfn ../../community/skills/brpc-release .github/skills/brpc-release
+```
+
+### Codex
+
+项目级目录为 `.agents/skills/`,用户级目录为 `~/.agents/skills/`:
+
+```bash
+mkdir -p .agents/skills
+ln -sfn ../../community/skills/brpc-release .agents/skills/brpc-release
+
+# 或安装到用户级
+mkdir -p ~/.agents/skills
+ln -sfn "$(pwd)/community/skills/brpc-release" ~/.agents/skills/brpc-release
+```
+
+### Cursor
+
+项目级目录为 `.cursor/skills/`,用户级目录为 `~/.cursor/skills/`:
+
+```bash
+mkdir -p .cursor/skills
+ln -sfn ../../community/skills/brpc-release .cursor/skills/brpc-release
+
+# 或安装到用户级
+mkdir -p ~/.cursor/skills
+ln -sfn "$(pwd)/community/skills/brpc-release" ~/.cursor/skills/brpc-release
+```
+
+安装后重新打开会话,在 bRPC 仓库中输入「发版 <version>」「验证一下这个发布包」等请求即可触发。
+支持斜杠命令的客户端也可以尝试 `/brpc-release`。如果客户端没有识别到 skill,请确认:
+
+1. 安装目录中的 `brpc-release/SKILL.md` 可访问;
+2. 符号链接没有失效;
+3. 客户端版本支持 Agent Skills,并已重新加载项目或会话;
+4. 客户端采用了不同的 skill 目录时,以该客户端当前版本的配置说明为准。
+
+## 依赖
+
+| 依赖 | 用途 | 必需角色/阶段 |
+|---|---|---|
+| Git | 读取提交范围、创建 release 分支和本地 tag、生成源码包 | RM;阶段 1–4、9 |
+| Bash(macOS/Linux) | 运行版本更新、Release Notes、打包和验包脚本 | RM、校验者 |
+| GitHub CLI `gh` | 批量查询合并 PR 的标题、作者和编号以生成 PR 维度 Release Notes;首次使用需执行 `gh
auth login` | RM;阶段 7 |
+| GnuPG `gpg` | RM 交互式签名候选包,以及签名验证 | RM;阶段 2、4;校验者验证签名 |
+| Subversion `svn` | 检出 Apache `dist/dev` 工作副本、提交候选包、移动正式发布包 | RM;阶段 5、8 |
+| SHA-512 工具 | 生成和校验候选包哈希;脚本依次支持 `sha512sum`、`gsha512sum` 或 `shasum -a 512` |
RM;阶段 4;校验者验证哈希 |
+| 网络访问 | 访问 GitHub、Apache `dist/dev`、下载候选包和查询 PR | RM、校验者;按需 |
+
+`make_package.sh` 会调用 GPG 签名,必须由 RM 在交互式终端运行;`svn commit`、`svn mv`、推
tag、发邮件和发布 GitHub Release 也必须由 RM 手工执行。
+
+### 执行前检查
+
+每次启动 skill、恢复发版会话或执行依赖外部服务的步骤前,助手都必须先检查所需依赖和登录态;缺失时只说明
+安装、登录或授权方式,不能代填密码、token、私钥口令或其他凭据。建议按当前角色和计划步骤执行:
+
+```bash
+# 基础依赖:所有脚本均需要
+command -v git
+command -v bash
+
+# 按 PR 生成 Release Notes:需要 GitHub CLI 已登录且可访问目标仓库
+command -v gh
+gh auth status --hostname github.com
+gh repo view apache/brpc --json nameWithOwner >/dev/null
+
+# RM 打包签名:确认 GnuPG 及对应 Apache ID 的私钥可用
+command -v gpg
+gpg --list-secret-keys --keyid-format=long "${APACHE_ID}@apache.org"
+
+# RM 上传候选包:确认 SVN 客户端及 Apache SVN 认证可用
+command -v svn
+svn --username="$APACHE_ID" info https://dist.apache.org/repos/dist/dev/brpc/
>/dev/null
+
+# 打包或验包:确认至少存在一种 SHA-512 工具
+command -v sha512sum || command -v gsha512sum || command -v shasum
+```
+
+检查结果处理:
+
+- `gh auth status` 失败:提示用户在其终端执行 `gh auth login --hostname github.com`,完成后重新检查;
+- `gpg --list-secret-keys` 没有目标私钥:回到 GPG 准备流程,不进入打包;
+- `svn info` 要求认证或失败:由 RM 在交互式终端完成认证或确认权限后再继续;
+- SHA-512 工具缺失:先安装可用实现,再执行打包或验包;
+- 网络、仓库权限或登录态不满足:将该步骤标记为阻塞,汇报当前已完成和可选的其他步骤,等待 RM 选择。
+
+## 使用指南
+
+`brpc-release` 支持两种使用角色。开始新会话时应明确自己的角色,避免校验者误触发发版写操作。
+
+### Release Manager(RM)
+
+RM 负责准备并发布候选版本。下面示例中的 `<version>`、`<previous-version>` 和 `<apache-id>`
都是占位符;使用前请替换为本次版本、上一版本和自己的 Apache ID(例如 `your-id`),不要填写密码、token 或其他凭据。可以这样开始:
+
+```text
+使用 brpc-release,以 RM 角色发布 <version>,我的 Apache ID 是 <apache-id>。
+先读取已有状态并告诉我下一步;遇到 GPG 签名、push tag、SVN commit/mv、发邮件或发布 GitHub
Release时停止,给我命令,等我确认成功后再继续。
+```
+
+助手会读取 `~/brpc_release/<version>/STATE.md`;若文件不存在则创建,然后按阶段继续。RM 需要准备:
+
+- 本次版本号、Apache ID 和上一版本号;
+- 可访问 Apache SVN 的账号;
+- 可用的 GPG 私钥;
+- 对 `apache/brpc` 和发布网站所需的操作权限。
+
+职责边界:
+
+| 助手可以执行 | 必须由 RM 执行 |
+|---|---|
+| 前置检查、创建 release 分支、更新版本文件、整理 SVN 工作副本、验证候选包 | `make_package.sh`(含本地
tag、打包、GPG 签名和校验) |
+| 生成 Release Notes 和邮件草稿 | push tag、SVN commit/mv、发送邮件、发布 GitHub Release |
+
+阶段 4 由助手提供命令,RM 在交互式终端一次执行完成:
+
+```bash
+export VERSION="<version>"
+export APACHE_ID="<apache-id>"
+cd ~/brpc
+BRPCUSERNAME="$APACHE_ID"
community/skills/brpc-release/scripts/make_package.sh "$VERSION"
+```
+
+脚本执行期间由 GPG pinentry 向 RM 获取私钥口令。RM 明确回报脚本成功并提供 tag commit id 后,助手会
+更新状态文件,汇报已完成和未完成事项,并列出当前可选下一步;RM 选择后才继续,不会自动进入下一阶段。
+
+中断数小时或数天后,可以输入以下指令恢复,不需要从头开始:
+
+```text
+使用 brpc-release,以 RM 角色继续发布 <version>。先读取状态文件,只执行尚未完成的步骤。
+```
+
+### 候选包校验者(Verifier)
+
+校验者只检查候选包,不创建分支、不改版本号、不打 tag,也不向 SVN 或 GitHub 写入内容。可以直接使用
+公开的 `dist/dev` 地址:
+
+```text
+使用 brpc-release,以校验者角色验证 Apache bRPC <version>
候选包。只执行只读校验,不修改仓库、不签名、不上传或发布任何内容;最后给我校验结果和投票建议。
+```
+
+也可以手工执行:
+
+```bash
+export VERSION="<version>"
+community/skills/brpc-release/scripts/verify_package.sh \
+ "https://dist.apache.org/repos/dist/dev/brpc/${VERSION}/"
+```
+
+校验范围包括下载链接、SHA512、GPG 签名、源码包命名、许可证文件、归档内容,以及源码包与 GitHub tag
+的逐文件差异。校验者应根据实际结果独立投票,并在回复 `+1` 或 `-1` 时列出检查项;助手不能代发邮件。
+校验公开候选包通常不需要 Apache ID,只有访问受认证资源时才由用户在终端交互认证。
+
+校验脚本只会创建临时工作目录,并在退出时自动删除该临时目录及其中下载、解压的校验副本;**不会**删除
+传入的本地产物目录、SVN 工作副本、Git tag、分支或任何正式发布文件。
+
+## 脚本可以脱离 AI 助手单独用
+
+`scripts/` 下的脚本都是普通 bash,不依赖任何 AI 助手,手工发版时照样能用:
+
+```bash
+export VERSION="<version>"
+export PREV_VERSION="<previous-version>"
+export APACHE_ID="<apache-id>"
+
+community/skills/brpc-release/scripts/bump_version.sh "$VERSION"
+community/skills/brpc-release/scripts/bump_version.sh "$VERSION" --check
+# 通过 GitHub PR(而不是 commit)生成草稿;终点使用本地 release tag,无需推送到 GitHub;需要先完成 gh auth
login
+community/skills/brpc-release/scripts/release_notes.sh "$PREV_VERSION"
"$VERSION"
+# 以下命令必须由 RM 在交互式终端执行:
+BRPCUSERNAME="$APACHE_ID"
community/skills/brpc-release/scripts/make_package.sh "$VERSION"
+community/skills/brpc-release/scripts/verify_package.sh \
+ "https://dist.apache.org/repos/dist/dev/brpc/${VERSION}/"
+```
+
+macOS 和 Linux 都验证过(sha512 工具会在 `sha512sum` / `gsha512sum` / `shasum -a 512`
之间自动选择)。
+
+> `verify_package.sh` 是 `community/apache-package-validator.sh` 的可移植替代:
+> 后者依赖 wget 和 GNU coreutils,在 macOS 上跑不起来。两者检查项基本一致,
+> `verify_package.sh` 另外做了「源码包 vs GitHub tag」的逐文件 diff。
+
+## 改动须知
+
+发版流程变了,`community/release_cn.md`(权威)和这里的 skill 要一起改,否则 skill 会带偏 RM。
+版本号硬编码的位置变了,记得同步 `scripts/bump_version.sh` 里的文件表和 `references/checklist.md`
的清单。
diff --git a/community/skills/brpc-release/SKILL.md
b/community/skills/brpc-release/SKILL.md
new file mode 100644
index 00000000..41cd3752
--- /dev/null
+++ b/community/skills/brpc-release/SKILL.md
@@ -0,0 +1,286 @@
+---
+name: brpc-release
+description: Drive an Apache bRPC release end to end - confirm the release
slot, draft Release Notes, bump version files, cut the tag, build/sign/checksum
the source tarball, stage it to Apache SVN dist/dev, verify the candidate, and
draft the VOTE/RESULT/ANNOUNCE mails. Use when cutting, resuming, or verifying
a bRPC release ("发版 <version>", "release apache brpc", "准备 RC", "写投票邮件",
"验证发布包", "check the release candidate").
+---
+
+# Apache bRPC 发版
+
+面向 Release Manager(RM)的发版助手。权威流程是 `community/release_cn.md`(英文
`community/release_en.md`),
+本 skill 不取代它,只做三件事:**编排顺序、自动化机械步骤、在不可逆操作前刹车**。
+
+## 铁律:这些命令永远不要代 RM 执行
+
+发版有大量对外且不可撤回的动作。遇到下面这些,**把命令打印出来让 RM 自己跑**,然后等待他明确回报成功;
+未收到成功确认前不得进入下一步:
+
+| 动作 | 为什么不能代跑 |
+|---|---|
+| `git push origin --tags` | tag 推到 apache/brpc 后立刻被镜像和下游 CI 抓取,回退要惊动 ASF
INFRA |
+| `svn commit` / `svn mv` | 写入 Apache 官方分发仓库;`dist/release` 下的内容会同步到全球镜像 |
+| `gpg --detach-sign` | 模型不能执行签名,也不能接触私钥口令;必须由 RM 在交互式终端执行 |
+| `git config user.name` / `git config user.email` | 发版不要求修改 Git
身份;模型不得读取后重写、覆盖或临时修改用户配置 |
+| 发送任何邮件 | `dev@` / `announce@` 是公开存档,发出即无法撤回 |
+| 发布 GitHub Release | 对外可见 |
+
+改版本号、打本地 tag、打包、算校验和、生成草稿这些**本地可逆操作**可以直接做。
+
+## 安全条款与强制检查
+
+发版助手必须采用 fail-closed 原则:前置条件不明确或检查失败时立即停止,不猜测、不绕过,也不以
+`--force`、临时改脚本或跳过校验的方式继续。
+
+在任何会修改仓库或生成候选包的步骤前,必须逐项检查:
+
+1. 当前目录是预期的 Apache bRPC Git 仓库,且远端指向可识别的 bRPC 仓库;
+2. 目标版本符合 `X.Y.Z` 或 `X.Y.Z-rcNN`,目标分支严格为 `release-X.Y`;
+3. 当前分支已是目标 release 分支;minor 首发分支必须从 `origin/master` 创建,不能先改版本号再切分支;
+4. 工作区没有与本次步骤无关的未提交修改;发现用户修改时不得覆盖、丢弃或自动 stash;
+5. tag 不存在,或已存在且明确指向预期 commit;不得静默覆盖或移动 tag;
+6. 待发布 commit 来自目标 release 分支,且版本文件检查全部通过;
+7. 即将执行的操作不在上方“永远不要代 RM 执行”的列表中。
+
+所有脚本必须使用严格模式、校验外部命令返回值,并将错误输出到 stderr。涉及版本号、分支、tag、
+产物路径的参数不得直接拼接成未经验证的命令。日志和草稿不得包含私钥、密码、token、cookie 或其他凭据。
+发版沿用仓库已有的 Git 身份,不要求 Apache 邮箱;模型不得执行任何修改本地或全局 Git 用户名、邮箱的命令。
+
+## 依赖与登录态检查
+
+每次启动、恢复会话,以及执行依赖外部服务的步骤前,必须先检查当前步骤所需的命令、网络、账号权限和
+登录态。基础检查包括 `git`、`bash`;阶段 2/4 检查 `gpg` 和 `${APACHE_ID}@apache.org` 私钥;阶段 5/8
+检查 `svn` 与 Apache SVN 认证;阶段 6 检查网络和 SHA-512 工具;阶段 7 检查 `gh`、GitHub 登录态和对
+`apache/brpc` 的访问权限。
+
+检查失败时,将对应步骤标记为阻塞,汇报已完成项和其他可选下一步,等待 RM 选择;不得跳过检查、代填密码、
+token 或私钥口令。具体命令和失败处理见 `community/skills/README.md` 的“执行前检查”。
+
+## 每次开工前先做这三件事
+
+1. **读状态文件** `~/brpc_release/<version>/STATE.md`。发版横跨数周,绝大多数会话是接着上次继续,
+ 不要默认从头开始。文件不存在时,用 `references/state_template.md` 建一个。
+2. **确认版本号、RM 和 Apache ID**:查 `community/release_schedule.md` 里的排期表,并向 RM 询问
+ Apache ID(仅用户名,不询问密码、token 或其他凭据)。Apache ID 用于以 `--username` 参数检出和操作
+ Apache SVN 仓库,并写入状态文件。补丁版本 `${MAJOR}.${MINOR}.${PATCH}` 在已有的
+ `release-${MAJOR}.${MINOR}` 分支上发;minor 首发版本 `${MAJOR}.${MINOR}.0` 要从 master
拉新分支。
+ **先创建或切换 release 分支,再做任何版本号修改**;不得在 master 或其他工作分支先改版本号。
+3. **确认目录约定**(与 `release_cn.md` 一致,脚本也按这个假设):
+
+ | 路径 | 用途 |
+ |---|---|
+ | `~/brpc` | release 分支的 clone,打包在这里做 |
+ | `~/brpc_release/svn/dev/brpc` | Apache SVN `dist/dev` 工作副本 |
+ | `~/brpc_release/<version>/` | 本次发版的草稿、邮件、状态文件 |
+
+每完成一个阶段,**立刻回写 STATE.md**(勾选 + 记下关键产物,如 tag 的 commit id、投票邮件链接)。
+
+## 进度汇报与下一步选择
+
+每完成一个步骤或收到 RM 对手工步骤的成功回报后,助手必须先读取并回写 `STATE.md`,再向 RM 汇报:
+
+1. **本次完成**:列出刚完成的阶段或子步骤,以及产物路径、tag commit id、候选包 URL 等关键结果;
+2. **当前进度**:列出已完成项和未完成项;对于被跳过的可选项,明确标记为“跳过”及原因;
+3. **前置条件/阻塞项**:说明下一步是否需要 RM 执行 GPG、push tag、SVN commit/mv、发邮件或其他外部操作;
+4. **可选下一步**:只列出当前前置条件已满足的步骤,按推荐顺序编号;如果工作可并行,明确标为“可并行”;
+5. **等待选择**:停止执行,询问 RM 选择哪一项。除非 RM 明确指示连续执行,否则助手不能自动进入下一阶段。
+
+推荐使用以下固定格式,保证跨会话可恢复:
+
+```text
+当前发版进度:<version>
+本次完成:
+- [x] <刚完成的步骤与关键产物>
+
+已完成:<编号列表>
+未完成:<编号列表>
+阻塞项:<无 / 需要 RM 执行的动作>
+
+可选下一步:
+1. <推荐步骤>
+2. <可并行步骤>
+3. <其他已满足前置条件的步骤>
+
+请回复序号或直接说明希望继续的工作。
+```
+
+RM 可以随时选择任何**前置条件已满足**的未完成项,例如“先生成 Release Notes”“先验证本地产物”或“继续阶段 5”。
+助手应先核对依赖关系,满足则执行并更新状态;不满足则解释所缺前置条件,并继续等待 RM 选择。
+
+## 阶段地图
+
+流程分 9 个阶段,跨度约 2 周。详细检查项在 `references/checklist.md`,邮件模板在
+`references/mail_templates.md`——**按需读取,不要一次性全读进来**。
+
+### 阶段 1 — 确认发布范围(约 1 周)
+
+和社区确认本次发布范围,合入“计划发布但还没进来”的 PR,并冻结候选 commit 范围。
+此时不生成 Release Notes;分支、版本和候选包尚未稳定,提前生成容易遗漏或反复返工。
+
+### 阶段 2 — GPG 准备(非首次发版跳过)
+
+先检查有没有可用的密钥:
+
+```bash
+gpg --list-secret-keys --keyid-format=long <apache-id>@apache.org
+```
+
+有输出就跳过。没有则照 `references/checklist.md` 的「GPG 首次设置」走完四步:建 4096 位 RSA 密钥
+(邮箱必须是 Apache 邮箱)→ 发布公钥到 keyserver → 把 fingerprint 填进 https://id.apache.org →
+把公钥追加进 SVN 的 `KEYS` 文件。
+
+### 阶段 3 — 拉发版分支 + 改版本号
+
+顺序不可颠倒:**先拉/切换发版分支,确认当前分支正确,再改版本号**。脚本会强制检查目标 release
+分支、仓库身份和工作区状态;任一检查不通过都不会写文件。
+
+```bash
+export VERSION="<version>"
+export APACHE_ID="<apache-id>"
+export RELEASE_BRANCH="release-${VERSION%.*}"
+
+# minor 首发版本才需要从 master 拉新分支
+# 如工作区已有未提交修改,应先提交或暂存,不能为了切分支而提前改版本号
+git checkout -b "$RELEASE_BRANCH" origin/master
+git branch --show-current # 必须输出 $RELEASE_BRANCH
+
+# 改发布所需的硬编码版本号(幂等,可重复跑)
+scripts/bump_version.sh "$VERSION"
+
+# 只校验不修改
+scripts/bump_version.sh "$VERSION" --check
+```
+
+脚本只更新发布元数据及用户文档;`example/build_with_bazel_module/MODULE.bazel` 中示例模块自身的
+`version` 和示例依赖的 bRPC `version` 都不跟随本次发版修改。脚本结尾会扫描其他旧版本号残留。
+另外**检查 `NOTICE` 的年份**(`Copyright 2018-<当年>`),年初发版时尤其容易漏。
+
+发版过程中发现问题,一律在 release 分支上改,不要回 master。
+
+### 阶段 4 — 打 tag、打包、签名、校验和
+
+模型不得执行本阶段脚本,只能根据已确认的 Apache ID 和版本号向 RM 提供完整命令:
+
+```bash
+cd ~/brpc
+BRPCUSERNAME="$APACHE_ID"
community/skills/brpc-release/scripts/make_package.sh "$VERSION"
+```
+
+由 RM 在交互式终端执行。脚本会一次完成:校验工作区和版本号 → 创建**本地** tag → 生成源码包 →
+调用 GPG 并等待 RM 输入私钥口令 → 验证签名 → 生成并验证 `.sha512`。脚本不会修改 Git 用户名或邮箱,
+也不会推送 tag。
+
+模型提供命令后必须停止。只有 RM 明确回报脚本成功,并提供输出中的 tag commit id 后,才把阶段 4
+标为完成并进入下一阶段;失败或结果不明确时不得继续。`git push origin --tags` 仍由 RM 单独执行。
+
+### 阶段 5 — 上传到 Apache SVN dist/dev
+
+如果状态文件中没有 Apache ID,先向 RM 询问;只记录 ID,**不得询问、记录或代填密码**。首次使用时,
+可代 RM 使用 Apache ID 检出 SVN 工作副本(认证需要密码时由 RM 在终端交互输入):
+
+```bash
+export BRPCUSERNAME="$APACHE_ID"
+mkdir -p ~/brpc_release/svn/dev/
+svn --username="$BRPCUSERNAME" co https://dist.apache.org/repos/dist/dev/brpc/
~/brpc_release/svn/dev/brpc
+```
+
+按 `references/checklist.md` 的「SVN 上传」小节,把三个文件(`.tar.gz` / `.asc` / `.sha512`)
+放进 `~/brpc_release/svn/dev/brpc/<version>/`,`svn add` 之后**由 RM 执行 `svn
commit`**。
+首次发版的 RM 还要先把公钥追加进 `KEYS` 并一起提交。
+
+### 阶段 6 — 验证候选包
+
+```bash
+# 验证已上传到 dist/dev 的包(联网下载并全面校验)
+scripts/verify_package.sh
"https://dist.apache.org/repos/dist/dev/brpc/${VERSION}/"
+
+# 或验证本地产物
+scripts/verify_package.sh "${HOME}/brpc_release/${VERSION}"
+```
+
+对标仓库里已有的 `community/apache-package-validator.sh`(那个脚本依赖 wget 和 GNU coreutils),
+本脚本是可在 macOS 直接运行的独立替代实现,并额外做一项文档里要求但那个脚本没覆盖的
+检查:**源码包与 GitHub tag 逐文件 diff**。
+
+自己验过一遍再发投票邮件——PMC 常见的 -1 原因见 `references/checklist.md`。
+
+### 阶段 7 — 准备 Release Notes 并发起投票(至少 72 小时)
+
+候选包验证通过后,以最终 tag 为边界生成 Release Notes 草稿:
+
+```bash
+export PREV_VERSION="<previous-version>"
+# 使用本地 release tag 作为终点;tag 无需推送到 GitHub。
+scripts/release_notes.sh "$PREV_VERSION" "$VERSION" \
+ > "${HOME}/brpc_release/${VERSION}/notes-draft.md"
+```
+
+脚本以最终 tag 范围内的 GitHub PR 为唯一条目来源:每个合并 PR 只输出一项,并从 GitHub 获取 PR 标题和
+作者;输出格式为 `@GitHub-ID #PR-id`,不生成显式 PR URL,GitHub Release 会自动识别链接。不会再按 commit
+逐条生成。分类仍是 Feature / Bugfix / Enhancement / Other 的启发式猜测,必须人工核对完整性和准确性;没有
+关联 GitHub PR 的 commit 会在草稿注释中单独提示,供人工确认归属。脚本按每批最多 50 个 PR 的 GraphQL
+请求加载元数据,避免逐 commit 查询导致长时间等待。按渠道准备:
+- GitHub Release:完整 Release Notes,逐项保留 `by @GitHub-ID (#PR-id)`,不附显式链接;分类内按功能主题
+ 排序。同一 GitHub ID 的不同功能或修复必须作为独立条目保留;
+- 微信公众号:与 GitHub Release 保持相同的“新功能 / Bug 修复 / 功能增强”分类和 PR 条目粒度,每项保留
+ `by @GitHub-ID (#PR-id)`,不写 PR URL。阅读相关 PR 的说明和必要上下文后,以自然中文润色功能描述;
+ 不逐字直译、不照搬生硬的提交标题,无法确认含义时不臆测;
+- VOTE 邮件的 Release Note:只挑重要 PR,Feature 优先,不粘贴完整列表;
+- ANNOUNCE 邮件:只写几条主要变化,不标贡献者和 PR 编号。
+
+从 `references/mail_templates.md` 取 `[VOTE]` 模板,填好版本号、精选 Release Note、
+**tag 的 commit id**,草稿写到 `~/brpc_release/<version>/vote-mail.txt`,**由 RM 发到
[email protected]**。邮件发出后,在 [[email protected]
邮件归档](https://lists.apache.org/[email protected])
+找到该 `[VOTE]` 邮件并复制其永久链接,立即写入 `STATE.md` 的“投票邮件链接”。
+
+需要至少 72 小时 + 3 张 PMC binding +1。够票后从同一归档页面找到 `[RESULT][VOTE]` 邮件的永久链接,
+回写到 `STATE.md` 的“已完成记录”,再用 `[RESULT][VOTE]` 模板宣布结果。
+
+投票没过:在 release 分支修问题,回到阶段 3 重新打包,版本号不变但要重打 tag。
+
+### 阶段 8 — 完成发布
+
+依次(每一步都由 RM 执行):
+
+1. **PMC 成员**把包从 `dist/dev` 移到 `dist/release`(`svn mv`)
+2. 在 GitHub 对应 tag 上创建 Release,标题统一为 `Apache bRPC ${VERSION}`
+3. 等包同步到 Apache 镜像后,更新 https://brpc.apache.org/docs/downloadbrpc/
+ (在 `apache/brpc-website` 仓库,中英文都要改)
+ - 签名和哈希链接前缀:`https://downloads.apache.org/brpc/`
+ - 代码包链接前缀:`https://dlcdn.apache.org/brpc/`
+4. 用 `[ANNOUNCE]` 模板发信到 `[email protected]` 和 `[email protected]`
+ —— 必须用**个人 apache 邮箱**、必须**纯文本格式**,announce@ 要人工审核约一天
+
+### 阶段 9 — 收尾
+
+- 把 release 分支合回 master
+- 更新 `community/release_schedule.md` 里本次发版的实际日期
+- 微信公众号等外部渠道(可选)
+
+完成上述事项并确认正式发布可下载后,助手应汇报可清理的本地发版产物,并由 RM 自行决定是否执行。
+**不得自动删除**,也不得删除 Git 分支、tag、SVN 工作副本或 `STATE.md`。建议 RM 在确认无需保留本地
+归档、且 `STATE.md` 已记录 tag commit id、SHA512、投票链接等关键事实后,执行:
+
+```bash
+export VERSION="<version>"
+
+# 仅删除本次生成的本地源码包、签名、哈希和草稿;保留 STATE.md 作为发版记录。
+rm -f
"${HOME}/brpc_release/${VERSION}"/apache-brpc-"${VERSION}"-src.tar.gz{,.asc,.sha512}
+rm -f "${HOME}/brpc_release/${VERSION}"/notes-draft.md \
+ "${HOME}/brpc_release/${VERSION}"/wechat-draft.md \
+
"${HOME}/brpc_release/${VERSION}"/{vote-mail,result-mail,announce-mail}.txt
+
+# 若确认不再需要整个本次草稿目录,先检查内容,再由 RM 手工删除。
+ls -la "${HOME}/brpc_release/${VERSION}"
+# rm -rf "${HOME}/brpc_release/${VERSION}"
+```
+
+SVN 工作副本 `~/brpc_release/svn/dev/brpc` 通常应保留供下次发版复用;若 RM 明确要清理,先确认不存在
+未提交修改(`svn status`),再由 RM 手工删除该工作副本。
+
+## 常见坑
+
+- **改了版本号却漏了文件**:一定跑 `bump_version.sh --check`,别手改。
+- **在 master 上打 tag**:所有发版操作都在 `release-<major.minor>` 分支。
+- **sha512 文件里带路径**:`sha512sum` 必须在 tarball 所在目录执行,否则校验方 `--check` 会失败。
+ 脚本已处理。
+- **macOS 没有 `sha512sum`**:脚本会自动回退到 `gsha512sum` 或 `shasum -a 512`。
+- **投票邮件写错 commit id**:填 tag 指向的 commit,不是分支 HEAD。
+- **只回 `+1` 不附检查项**:PMC 投票必须列出检查了哪些项。
diff --git a/community/skills/brpc-release/references/checklist.md
b/community/skills/brpc-release/references/checklist.md
new file mode 100644
index 00000000..17d23edf
--- /dev/null
+++ b/community/skills/brpc-release/references/checklist.md
@@ -0,0 +1,212 @@
+# 发版检查清单
+
+`SKILL.md` 里省略的细节都在这里。按需查阅对应小节,不必通读。
+
+---
+
+## GPG 首次设置
+
+只有**从未发过版**的 RM 需要做。已有密钥的直接跳到「SVN 上传」。
+
+> **口令安全铁律**:模型不得执行 `make_package.sh` 或任何签名命令,只能把完整的脚本命令提供给 RM。
+> RM 在交互式终端运行脚本,由 GPG pinentry 获取私钥口令。任何要求用 `--passphrase`、
+> `--pinentry-mode loopback` 或环境变量传递口令的做法都要**拒绝**——私钥口令绝不落盘、
+> 绝不进命令行历史、绝不交给 agent。脚本会完成签名、签名验证和 SHA512 校验;RM 明确回报成功前,
+> 模型不得进入下一阶段。
+
+### 1. 安装
+
+```bash
+brew install gnupg # macOS
+# Linux 发行版通常自带 GnuPG
+gpg --version
+```
+
+### 2. 创建密钥
+
+```bash
+gpg --full-gen-key
+```
+
+交互式提示的关键选择:
+
+| 提示 | 填什么 |
+|---|---|
+| kind of key | `1`(RSA and RSA) |
+| keysize | `4096` |
+| valid for | `0`(永不过期) |
+| Real name | 姓名拼音 / Apache ID / GitHub ID 均可 |
+| Email address | **必须是 `<apache-id>@apache.org`** |
+| Passphrase | 设一个并记牢,后面每次签名都要输 |
+
+生成后会打印公钥 ID,形如 `C30F211F071894258497F46392E18A11B6585834`。
+
+### 3. 发布公钥到 keyserver
+
+```bash
+gpg --keyserver hkps://pgp.mit.edu --send-key <公钥ID>
+```
+
+`hkps://keys.openpgp.org` 和 `hkps://keyserver.ubuntu.com` 也可以,都提供 Web 查询界面。
+
+### 4. 登记 fingerprint
+
+```bash
+gpg --fingerprint <用户ID>
+```
+
+把输出里带空格的指纹(如 `C30F 211F 0718 9425 8497 F463 92E1 8A11 B658 5834`)粘贴到
+https://id.apache.org 的 `OpenPGP Public Key Primary Fingerprint:` 字段。
+
+公钥服务器没有校验机制,任何人都能以你的名义上传公钥,所以必须在 Apache 官方渠道公布指纹供人核对。
+
+### 5. 把公钥追加进 SVN 的 KEYS
+
+在 `~/brpc_release/svn/dev/brpc` 目录下:
+
+```bash
+(gpg --list-sigs $BRPCUSERNAME && gpg -a --export $BRPCUSERNAME) >> KEYS
+```
+
+同名密钥有多个时,用完整邮箱或公钥 ID 指定:
+
+```bash
+(gpg --list-sigs [email protected] && gpg -a --export
[email protected]) >> KEYS
+# 或
+(gpg --list-sigs <公钥ID> && gpg -a --export <公钥ID>) >> KEYS
+```
+
+---
+
+## 版本号文件清单
+
+`bump_version.sh` 覆盖下面全部 7 个文件。手改容易遗漏,应优先使用脚本并执行 `--check` 校验。
+
+| 文件 | 形式 |
+|---|---|
+| `RELEASE_VERSION` | 整个文件就是版本号 |
+| `CMakeLists.txt` | `set(BRPC_VERSION 1.18.0)` |
+| `package/rpm/brpc.spec` | `Version:\t1.18.0` |
+| `CLAUDE.md` | `Current version: 1.18.0.` |
+| `MODULE.bazel` | `module(... version = '1.18.0' ...)` |
+| `docs/cn/bazel_support.md` | `bazel_dep(name = "brpc", version = "1.18.0",
...)` |
+| `docs/en/bazel_support.md` | 同上 |
+
+`example/build_with_bazel_module/MODULE.bazel` 是示例配置,其中的 module 版本和 bRPC
依赖版本不随本次发版修改。
+
+另外手工确认:
+
+- `NOTICE` 的年份是 `Copyright 2018-<当前年份>`(年初发版必查)
+
+发版不要求修改 Git 身份。模型和脚本不得执行 `git config user.name` 或 `git config user.email`;
+若创建 annotated tag 时发现身份未配置,只提示 RM 自行处理并停止。
+
+---
+
+## SVN 上传
+
+先从状态文件读取 Apache ID;若尚未记录,则向 RM 询问。这里只需要 Apache ID(用户名),不要询问或保存
+Apache LDAP 密码。`svn` 需要认证时,由 RM 在终端中交互输入密码。
+
+```bash
+export BRPCVERSION=1.18.0
+export BRPCUSERNAME=<你的 apache id>
+
+# 首次:检出 dist/dev 工作副本
+mkdir -p ~/brpc_release/svn/dev/
+svn --username=$BRPCUSERNAME co https://dist.apache.org/repos/dist/dev/brpc/
~/brpc_release/svn/dev/brpc
+
+# 放入三个产物(源目录是 make_package.sh 的输出目录)
+mkdir -p ~/brpc_release/svn/dev/brpc/$BRPCVERSION
+cp
~/brpc_release/$BRPCVERSION/apache-brpc-$BRPCVERSION-src.tar.gz{,.asc,.sha512} \
+ ~/brpc_release/svn/dev/brpc/$BRPCVERSION/
+
+cd ~/brpc_release/svn/dev/brpc
+svn add --force .
+```
+
+最后一步**由 RM 亲自执行**:
+
+```bash
+svn --username=$BRPCUSERNAME commit -m "release $BRPCVERSION"
+```
+
+首次发版的 RM 记得把「GPG 首次设置」第 5 步生成的 `KEYS` 变更一起提交。
+
+---
+
+## 候选包检查项
+
+`verify_package.sh` 自动覆盖的:
+
+- [ ] 下载链接有效
+- [ ] sha512 哈希正确
+- [ ] GPG 签名正确
+- [ ] 包内 `RELEASE_VERSION` 和 `CMakeLists.txt` 的版本号与本次发布一致
+- [ ] 存在 `LICENSE` 和 `NOTICE`
+- [ ] 不含编译产物 / 意外的二进制文件
+- [ ] 源码包内容与 GitHub tag 完全一致
+
+需要**人工判断**的:
+
+- [ ] `NOTICE` 年份正确
+- [ ] 源码包体积合理,没有夹带无关文件
+- [ ] 所有源文件都有 ASF License 头(本仓库用 skywalking-eyes 在 CI 里查,配置见 `.licenserc.yaml`)
+- [ ] 能正常编译,单测通过
+- [ ] 没有空目录等多余文件夹
+- [ ] 第三方依赖许可证:
+ - 许可证兼容(见下方分类)
+ - 所有第三方依赖都在 `LICENSE` 中声明
+ - 依赖许可证全文都在 `licenses/` 目录
+ - 依赖若是 Apache 许可证且带 `NOTICE`,其 NOTICE 内容要并入本项目 `NOTICE`
+
+### ASF 许可证分类
+
+| 类别 | 含义 | 例子 |
+|---|---|---|
+| Category A | 允许 | Apache-2.0, BSD-3-Clause, MIT |
+| Category B | 允许依赖,但不允许放进源码包 | EPL, MPL, CDDL |
+| Category X | **禁止** | GPL, LGPL, CC Non-Commercial |
+
+### 导入他人公钥做验证
+
+帮别人验包时需要先导入并信任发布人公钥(RM 验自己的包不需要):
+
+```bash
+curl https://dist.apache.org/repos/dist/dev/brpc/KEYS | gpg --import
+gpg --edit-key <发布人用户名>
+# gpg> trust → 选 5(ultimate)→ y → save
+```
+
+### 常见的 -1 原因
+
+- 包名不对,和当前发布版本对不上
+- 签名或哈希校验失败
+- 源码包里混进了编译产物或 jar/so 等二进制
+- 缺 `LICENSE` / `NOTICE`,或 NOTICE 年份过期
+- 源文件缺 ASF License 头
+- 引入了 Category X 许可证的依赖
+
+---
+
+## PMC 投票回复格式
+
+**不要只回 `+1`**,必须列出实际检查了哪些项:
+
+```
++1 (binding)
+
+I checked:
+- LICENSE and NOTICE are good
+- signatures and hashes correct
+- All ASF files have ASF headers
+- no unexpected binary files
+- source distribution matches the git tag
+- builds and unit tests pass
+```
+
+投 `-1` 同样必须给出明确理由。
+
+清单来源:Incubator PMC Chair Justin 在 ApacheCon North America 2019 的分享
+(https://training.apache.org/topics/ApacheWay/NavigatingASFIncubator/index.html),
+详见 `community/releasecheck.md`。
diff --git a/community/skills/brpc-release/references/mail_templates.md
b/community/skills/brpc-release/references/mail_templates.md
new file mode 100644
index 00000000..f11942c0
--- /dev/null
+++ b/community/skills/brpc-release/references/mail_templates.md
@@ -0,0 +1,323 @@
+# 发版邮件模板
+
+三封邮件,按顺序发。全部用**个人 Apache 邮箱**、**纯文本格式**(Gmail 里选「纯文本模式」)。
+
+Apache 邮箱配置参考 https://shenyu.apache.org/zh/community/use-apache-email ,
+注意 SMTP 服务器要填 `mail-relay.apache.org`。
+
+占位符:`{VERSION}` 版本号、`{COMMIT}` tag 指向的 commit id、`{RM}` 发布人名字。
+
+---
+
+## 1. 投票邮件 → [email protected]
+
+**标题**
+
+```
+[VOTE] Release Apache bRPC {VERSION}
+```
+
+**正文**
+
+```
+Hi Apache bRPC Community,
+
+This is a call for vote to release Apache bRPC version {VERSION}
+
+[Release Note]
+
+Features:
+
+- {feature description} by @github-id (#1234)
+- {feature description} by @github-id (#1235)
+
+Bugfixes:
+
+- {bugfix description} by @github-id (#1236)
+- {bugfix description} by @github-id (#1237)
+
+Enhancements:
+
+- {enhancement description} by @github-id (#1238)
+
+The release candidates:
+https://dist.apache.org/repos/dist/dev/brpc/{VERSION}/
+
+Git tag for the release:
+https://github.com/apache/brpc/releases/tag/{VERSION}
+
+Release Commit ID:
+https://github.com/apache/brpc/commit/{COMMIT}
+
+Keys to verify the Release Candidate:
+https://dist.apache.org/repos/dist/dev/brpc/KEYS
+
+The vote will be open for at least 72 hours or until the necessary number of
+votes are reached.
+
+Please vote accordingly:
+[ ] +1 approve
+[ ] +0 no opinion
+[ ] -1 disapprove with the reason
+
+PMC vote is +1 binding, all others are +1 non-binding.
+
+Checklist for reference:
+[ ] Download links are valid.
+[ ] Checksums and PGP signatures are valid.
+[ ] Source code distributions have correct names matching the current release.
+[ ] LICENSE and NOTICE files are correct for each brpc repo.
+[ ] All files have license headers if necessary.
+[ ] No compiled archives bundled in source archive.
+
+Regards,
+{RM}
+```
+
+> `[Release Note]` 只挑重要 PR,Feature 优先,不要粘贴完整 Release Notes;完整列表留给 GitHub
Release。
+>
+> `Release Commit ID` 填 **tag 指向的 commit**,不是分支 HEAD。
+> 取值:`git rev-parse {VERSION}^{commit}`
+
+投票邮件发出后,在 [[email protected]
邮件归档](https://lists.apache.org/[email protected]) 找到
+对应 `[VOTE]` 邮件并复制其永久链接,填写到 `STATE.md` 的“投票邮件链接”。投票需开放至少 72 小时,
+且收集到 **3 张 PMC binding +1** 才能进入下一步。
+
+---
+
+## 2. 投票回复模板
+
+投票者应根据实际检查结果回复投票邮件。PMC 成员使用 `+1 (binding)`,非 PMC 成员使用
+`+1 (non-binding)`;不得在未完成检查时勾选通过项。
+
+**标题**
+
+保持邮件客户端的回复标题,例如:
+
+```
+Re: [VOTE] Release Apache bRPC {VERSION}
+```
+
+**正文(PMC 成员)**
+
+```text
++1 (binding)
+
+I have checked:
+
+[x] Download links are valid.
+[x] Checksums and PGP signatures are valid.
+[x] Source code distributions have correct names matching the current release.
+[x] LICENSE and NOTICE files are correct for each brpc repo.
+[x] All files have license headers if necessary.
+[x] No compiled archives bundled in source archive.
+
+Best regards,
+{VOTER_NAME}
+```
+
+**正文(非 PMC 成员)**
+
+将首行替换为:
+
+```text
++1 (non-binding)
+```
+
+其余检查清单和签名保持不变。
+
+> 按实际检查情况填写。若有未完成项,保留 `[ ]` 并说明原因;发现阻塞性问题时投 `-1` 并附可复现信息。
+> 校验命令见 `verify_package.sh`,其输出可帮助填写上述清单。
+
+---
+
+## 3. 结果邮件 → [email protected]
+
+**标题**
+
+```
+[RESULT] [VOTE] Release Apache bRPC {VERSION}
+```
+
+**正文**
+
+```
+Hi all,
+
+The vote to release Apache bRPC {VERSION} has passed.
+
+The vote PASSED with 3 binding +1, 3 non binding +1 and no -1 votes:
+
+Binding votes:
+- xxx
+- yyy
+- zzz
+
+Non-binding votes:
+- aaa
+- bbb
+- ccc
+
+Vote thread: {VOTE_THREAD_URL}
+
+Thank you to all the above members to help us to verify and vote for
+the {VERSION} release. I will process to publish the release and send ANNOUNCE.
+
+Regards,
+{RM}
+```
+
+> 票数要按实际统计填写。`Vote thread` 填 `[VOTE]` 邮件的永久链接:在
+> [[email protected]
邮件归档](https://lists.apache.org/[email protected]) 中找到该邮件后复制链接。
+> `[RESULT][VOTE]` 发出后,也应复制其永久链接并写入 `STATE.md` 的“已完成记录”。
+
+---
+
+## 4. 发布公告 → [email protected] + [email protected]
+
+在包已经从 `dist/dev` 移到 `dist/release`、GitHub Release 已发布之后再发。
+
+**标题**
+
+```
+[ANNOUNCE] Apache bRPC {VERSION} released
+```
+
+**正文**
+
+```
+Hi all,
+
+The Apache bRPC community is glad to announce the new release
+of Apache bRPC {VERSION}.
+
+Apache bRPC is an Industrial-grade RPC framework using C++ Language,
+which is often used in high performance systems such as Search, Storage,
+Machine learning, Advertisement, Recommendation etc.
+
+Brief notes of this release:
+- xxx
+- yyy
+- zzz
+
+More details regarding Apache brpc can be found at:
+https://brpc.apache.org/
+
+The release is available for download at:
+https://brpc.apache.org/download/
+
+The release notes can be found here:
+https://github.com/apache/brpc/releases/tag/{VERSION}
+
+Website: https://brpc.apache.org/
+
+Apache bRPC Resources:
+- Issue: https://github.com/apache/brpc/issues/
+- Mailing list: [email protected]
+- Documents: https://brpc.apache.org/docs/
+
+We would like to thank all contributors of the Apache bRPC community
+who made this release possible!
+
+
+Best Regards,
+Apache bRPC Community
+```
+
+> `Brief notes of this release` 只列本次的**主要**变更,不要贴完整 Release Notes,
+> 也不用标注贡献人和 PR 编号。建议先翻一下 lists.apache.org 上之前的 ANNOUNCE 邮件对齐风格。
+>
+> `[email protected]` 需人工审核,发出后耐心等,一般一天内通过。
+
+---
+
+## Release Notes 模板
+
+阶段 7 在候选包验证通过后,需分别准备 GitHub Release 英文版和微信公众号中文版。两者均以**合并 PR
+为单位**:每个 PR 仅一项,不按该 PR 的多个 commit 分拆。`scripts/release_notes.sh` 生成的内容仅作为
+待人工打磨的初稿。
+
+### GitHub Release(英文)
+
+参考 Apache bRPC 1.16.0 的结构:用一段简洁摘要概括本次版本主题,再按 `Features`、`Bugfixes`、
+`Enhancements` 分类列出完整变更,最后致谢全部贡献者。每项使用 `@GitHub-ID #PR-id`;不要写显式 PR
+URL,GitHub 会自动将 `#PR-id` 识别为链接。
+
+```markdown
+Apache bRPC {VERSION} is a {feature/maintenance} release that includes
{one-sentence summary of the most important improvements}. This release
{user-visible value summary}.
+
+## Features
+
+- {feature description} by @github-id (#1234)
+- {feature description} by @github-id (#1235)
+
+## Bugfixes
+
+- {bugfix description} by @github-id (#1236)
+- {bugfix description} by @github-id (#1237)
+
+## Enhancements
+
+- {enhancement description} by @github-id (#1238)
+
+## Other
+
+- {documentation, test, build, or maintenance description} by @github-id
(#1239)
+```
+
+写作要求:
+
+- 摘要面向用户说明版本价值,避免仅罗列内部实现;
+- 变更描述使用动词开头的简洁英文,保留协议名、API、类名和配置项等必要技术术语;
+- 完整列出所有应发布的合并 PR;文档、测试、CI、构建和维护类变更归入 `Other`;
+- **变更条目**按 `Features`、`Bugfixes`、`Enhancements`、`Other`
分类;同一分类内按功能主题排序,并且仅合并重复描述的同一 PR;
+ 同一 GitHub ID 贡献了不同功能或修复时,必须保留为多条独立条目,不能按贡献者去重;
+- Contributors 仅致谢所有贡献者,不重复罗列 GitHub ID;
+- 无法确认 PR 含义时标记待确认,不得臆测。
+
+### 微信公众号(中文)
+
+微信公众号稿沿用 GitHub Release 的分类和条目粒度:先用一段发布摘要说明本次版本主题,再按“新功能”、
+“Bug 修复”、“功能增强”和“其他”列出变更,最后统一致谢。每项保留 `by @GitHub-ID (#PR-id)`,不写显式 PR URL;
+公众号不需要逐条展开链接。参考结构:
+
+```markdown
+# Apache bRPC {VERSION} 版本已发布
+
+很高兴地通知大家,Apache bRPC {VERSION} 版本已发布,这是一次{版本定位}的版本,在{改进方向一}、
+{改进方向二}与{改进方向三}方面均有显著改进。本次发布{总体价值总结}。
+
+Apache bRPC 官网:https://brpc.apache.org
+下载链接:https://brpc.apache.org/zh/download/
+GitHub Release Tag:https://github.com/apache/brpc/releases/tag/{VERSION}
+
+## {VERSION} 版本变更
+
+### 新功能
+
+- {新功能描述} by @github-id (#1234)
+- {新功能描述} by @github-id (#1235)
+
+### Bug 修复
+
+- {修复描述} by @github-id (#1236)
+- {修复描述} by @github-id (#1237)
+
+### 功能增强
+
+- {增强描述} by @github-id (#1238)
+
+### 其他
+
+- {文档、测试、构建或维护类描述} by @github-id (#1239)
+
+感谢所有关心和为 Apache bRPC 做出贡献的开发者!
+```
+
+写作要求:
+
+- 先阅读对应 PR 的标题、说明、讨论及必要代码上下文,理解变更动机、用户价值和影响范围后再写;
+- 保持与 GitHub Release 一致的四类分类和 PR 条目粒度;同一 GitHub ID 的不同功能或修复仍单独成条;
+- 中文条目以自然、准确的中文重新组织表达,不逐字直译、不照搬生硬的提交标题;
+- 每项保留 `by @GitHub-ID (#PR-id)`,不写 PR URL;
+- 保留协议名、API、类名和配置项等必要技术术语;无法确认含义时标记待确认,不得臆测。
diff --git a/community/skills/brpc-release/references/state_template.md
b/community/skills/brpc-release/references/state_template.md
new file mode 100644
index 00000000..f0ca687c
--- /dev/null
+++ b/community/skills/brpc-release/references/state_template.md
@@ -0,0 +1,73 @@
+# bRPC {VERSION} 发版状态
+
+复制到 `~/brpc_release/{VERSION}/STATE.md` 后逐项维护。每完成一步就回写——
+发版横跨数周,这个文件是跨会话唯一的事实来源。
+
+```
+Release Manager : {RM}
+Apache ID : {apache-id}
+版本号 : {VERSION}
+发版分支 : release-{MAJOR.MINOR}
+起始日期 : {YYYY-MM-DD}
+上一个版本 : {PREV_VERSION}
+```
+
+## 关键产物
+
+发出去之后再改代价很大,确定一个记一个。
+
+```
+Tag commit id :
+Tarball sha512 :
+GPG key id :
+dist/dev URL : https://dist.apache.org/repos/dist/dev/brpc/{VERSION}/
+投票邮件链接 :
+投票开始时间 :
+```
+
+## 当前状态
+
+```
+最后更新 : {YYYY-MM-DD HH:MM TZ}
+最近完成 :
+当前阻塞项 : 无
+建议下一步 :
+```
+
+每完成一个步骤立即更新本节;“建议下一步”可列多项,并标注是否可并行。用户选择暂不处理的工作应保留在下方进度清单中,不能删除。
+
+## 进度
+
+- [ ] **1. 发布范围** — 待发 PR 已合入,候选 commit 范围已冻结
+- [ ] **2. GPG** — 密钥可用,公钥已在 KEYS 中
+- [ ] **3. 分支与版本号** — `bump_version.sh --check` 通过,NOTICE 年份已确认
+- [ ] **4. 打包** — 本地 tag 已建、tarball/asc/sha512 已生成并自检通过
+- [ ] **4b. 推 tag** — `git push origin --tags`(RM 执行)
+- [ ] **5. 上传 SVN** — 三个文件已 `svn commit` 到 dist/dev(RM 执行)
+- [ ] **6. 验包** — `verify_package.sh` 全绿,与 GitHub tag diff 无差异
+- [ ] **7. Release Notes** — 最终 tag 范围的英文完整版和微信公众号中文版已定稿
+- [ ] **7a. 发起投票** — 邮件已发到 dev@(RM 执行)
+- [ ] **7b. 投票通过** — ≥72h 且 ≥3 张 PMC binding +1,RESULT 邮件已发
+- [ ] **8a. 移到 dist/release** — `svn mv`(需 PMC 成员执行)
+- [ ] **8b. GitHub Release** — 标题为 `Apache bRPC {VERSION}`
+- [ ] **8c. 更新官网下载页** — brpc-website 仓库,中英文都改
+- [ ] **8d. ANNOUNCE 邮件** — 发到 dev@ 和 announce@(RM 执行,纯文本)
+- [ ] **9. 收尾** — release 分支合回 master,更新 release_schedule.md 实际日期;已向 RM
提示可选的本地产物清理
+
+## 已完成记录
+
+每次完成步骤后追加一行,记录关键产物或 RM 对外操作的确认结果。
+
+| 时间 | 步骤 | 结果/产物 | 执行人 |
+|---|---|---|---|
+| | | | |
+
+## 投票记录
+
+| 投票人 | Binding | 票 | 备注 |
+|---|---|---|---|
+| | | | |
+
+## 遇到的问题
+
+<!-- 投票被 -1 或验包失败时记在这里,包括怎么修的,下次发版能省事 -->
diff --git a/community/skills/brpc-release/scripts/bump_version.sh
b/community/skills/brpc-release/scripts/bump_version.sh
new file mode 100755
index 00000000..125f962e
--- /dev/null
+++ b/community/skills/brpc-release/scripts/bump_version.sh
@@ -0,0 +1,183 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# Bump every hard-coded bRPC version string, then verify none was missed.
+#
+# Usage:
+# bump_version.sh <version> # rewrite all version files
+# bump_version.sh <version> --check # verify only, never write
+#
+# Idempotent: safe to re-run. Exits non-zero if any file ends up inconsistent.
+
+set -euo pipefail
+
+NEW_VERSION=${1:-}
+MODE=${2:-write}
+
+if [[ -z ${NEW_VERSION} || ${NEW_VERSION} == -* ]]; then
+ echo "usage: $(basename "$0") <version> [--check]" >&2
+ exit 2
+fi
+
+if ! [[ ${NEW_VERSION} =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$ ]]; then
+ echo "error: '${NEW_VERSION}' is not a valid version (expect 1.18.0 or
1.18.0-rc01)" >&2
+ exit 2
+fi
+
+case ${MODE} in
+ write|--check) ;;
+ *) echo "error: unknown option '${MODE}'" >&2; exit 2 ;;
+esac
+
+ROOT=$(git rev-parse --show-toplevel)
+cd "${ROOT}"
+
+if [[ ! -f RELEASE_VERSION || ! -f CMakeLists.txt || ! -d src/brpc ]]; then
+ echo "error: '${ROOT}' does not look like the Apache bRPC repository" >&2
+ exit 1
+fi
+
+if ! git remote -v | grep -Eq '(^|[/:])([^/]+/)?brpc(\.git)?([[:space:]]|$)';
then
+ echo "error: no recognizable bRPC git remote is configured" >&2
+ exit 1
+fi
+
+# Never rewrite version files before switching to the release branch. This is
+# deliberately enforced here rather than relying only on the release guide.
+if [[ ${MODE} == write ]]; then
+ EXPECTED_BRANCH="release-${NEW_VERSION%.*}"
+ CURRENT_BRANCH=$(git branch --show-current)
+ if [[ ${CURRENT_BRANCH} != "${EXPECTED_BRANCH}" ]]; then
+ echo "error: version files may only be rewritten on
'${EXPECTED_BRANCH}' (current: '${CURRENT_BRANCH:-detached HEAD}')" >&2
+ echo "create or switch to the release branch first" >&2
+ exit 1
+ fi
+
+ # Refuse to mix a version bump with unrelated edits. Untracked files are
+ # allowed because release tooling itself may not have been committed yet;
+ # tracked modifications are never overwritten or stashed automatically.
+ TRACKED_CHANGES=$(git status --short --untracked-files=no)
+ if [[ -n ${TRACKED_CHANGES} ]]; then
+ echo "error: tracked files have uncommitted changes; commit or restore
them before bumping the version:" >&2
+ printf '%s\n' "${TRACKED_CHANGES}" >&2
+ exit 1
+ fi
+fi
+
+OLD_VERSION=$(tr -d '[:space:]' < RELEASE_VERSION)
+# Escape dots so the string is safe inside a regex.
+OLD_RE=${OLD_VERSION//./\\.}
+NEW_RE=${NEW_VERSION//./\\.}
+
+failed=0
+changed=0
+
+note() { printf ' %s\n' "$*"; }
+fail() { printf ' FAIL %s\n' "$*" >&2; failed=1; }
+
+# Rewrite one file in place, portably (GNU and BSD sed disagree about -i).
+rewrite() {
+ local file=$1 expr=$2 tmp
+ [[ -f ${file} ]] || { fail "${file}: not found"; return; }
+ tmp=$(mktemp)
+ sed -E "${expr}" "${file}" > "${tmp}"
+ if cmp -s "${file}" "${tmp}"; then
+ rm -f "${tmp}"
+ else
+ cat "${tmp}" > "${file}" # preserve the original file mode
+ rm -f "${tmp}"
+ changed=$((changed + 1))
+ fi
+}
+
+# Assert the file now carries the new version.
+expect() {
+ local file=$1 pattern=$2
+ if grep -Eq -- "${pattern}" "${file}"; then
+ note "ok ${file}"
+ else
+ fail "${file}: no line matching /${pattern}/"
+ fi
+}
+
+if [[ ${MODE} == write ]]; then
+ echo "Bumping ${OLD_VERSION} -> ${NEW_VERSION}"
+
+ printf '%s\n' "${NEW_VERSION}" > RELEASE_VERSION
+
+ rewrite CMakeLists.txt \
+ "s/^set\(BRPC_VERSION[[:space:]]+[^)]*\)/set(BRPC_VERSION
${NEW_VERSION})/"
+
+ rewrite package/rpm/brpc.spec \
+ "s/^(Version:[[:space:]]*).*/\1${NEW_VERSION}/"
+
+ rewrite CLAUDE.md \
+ "s/(Current
version:[[:space:]]*)[^.[:space:]]+(\.[^.[:space:]]+){2}/\1${NEW_VERSION}/"
+
+ # Only the root module() block is the released module version. The example
+ # module deliberately keeps its own version and dependency declaration.
+ rewrite MODULE.bazel \
+ "s/^([[:space:]]*version = ')[^']*(')/\1${NEW_VERSION}\2/"
+
+ for doc in docs/cn/bazel_support.md docs/en/bazel_support.md; do
+ rewrite "${doc}" \
+ "s/(bazel_dep\(name = \"brpc\", version =
\")[^\"]*/\1${NEW_VERSION}/"
+ done
+
+ echo "Rewrote ${changed} file(s)"
+else
+ echo "Checking version files against ${NEW_VERSION}"
+fi
+
+echo "Verifying:"
+expect RELEASE_VERSION "^${NEW_RE}$"
+expect CMakeLists.txt "^set\(BRPC_VERSION
${NEW_RE}\)"
+expect package/rpm/brpc.spec
"^Version:[[:space:]]*${NEW_RE}$"
+expect CLAUDE.md "Current
version:[[:space:]]*${NEW_RE}\."
+expect MODULE.bazel "^[[:space:]]*version =
'${NEW_RE}',"
+expect docs/cn/bazel_support.md "bazel_dep\(name = \"brpc\",
version = \"${NEW_RE}\""
+expect docs/en/bazel_support.md "bazel_dep\(name = \"brpc\",
version = \"${NEW_RE}\""
+
+# Catch anything the table above does not know about.
+if [[ ${OLD_VERSION} != "${NEW_VERSION}" ]]; then
+ echo "Scanning for leftover '${OLD_VERSION}':"
+ leftover=$(git grep -n -E -- "${OLD_RE}" -- \
+ ':!community/release_schedule.md' ':!community/skills' ':!registry' \
+ ':!example/build_with_bazel_module/MODULE.bazel' || true)
+ if [[ -n ${leftover} ]]; then
+ printf '%s\n' "${leftover}" | sed 's/^/ /'
+ echo " ^ review these by hand; release_schedule.md and past release
notes are expected to keep old versions" >&2
+ else
+ note "none"
+ fi
+fi
+
+# NOTICE year is easy to forget in January releases.
+notice_year=$(sed -n 's/^Copyright [0-9]*-\([0-9]*\).*/\1/p' NOTICE | head -1)
+this_year=$(date +%Y)
+if [[ ${notice_year} != "${this_year}" ]]; then
+ echo "warning: NOTICE says 'Copyright ...-${notice_year}' but it is
${this_year}; update it if this release ships this year" >&2
+fi
+
+if (( failed )); then
+ echo "FAILED: version files are inconsistent" >&2
+ exit 1
+fi
+
+echo "All version files are at ${NEW_VERSION}"
diff --git a/community/skills/brpc-release/scripts/make_package.sh
b/community/skills/brpc-release/scripts/make_package.sh
new file mode 100755
index 00000000..475618cd
--- /dev/null
+++ b/community/skills/brpc-release/scripts/make_package.sh
@@ -0,0 +1,177 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# Build and sign an Apache bRPC source release in one RM-operated command:
+# local tag -> tarball -> GPG signature -> sha512 -> self-check.
+#
+# Usage:
+# BRPCUSERNAME=<apache-id> make_package.sh <version> [options]
+#
+# Options:
+# --user <apache-id> signing identity (default: $BRPCUSERNAME)
+# --repo <dir> release-branch checkout (default: current git root)
+# --outdir <dir> artifact directory (default: ~/brpc_release/<version>)
+#
+# The AI model must not execute this script because it invokes interactive GPG
+# signing. The RM runs it in a terminal and enters the private-key passphrase.
+# This script NEVER pushes a tag, commits SVN changes, or changes Git
+# user.name/user.email.
+
+set -euo pipefail
+
+VERSION=""
+APACHE_ID=${BRPCUSERNAME:-}
+REPO=""
+OUTDIR=""
+
+while (( $# )); do
+ case $1 in
+ --user) APACHE_ID=$2; shift 2 ;;
+ --repo) REPO=$2; shift 2 ;;
+ --outdir) OUTDIR=$2; shift 2 ;;
+ -h|--help) sed -n '20,35p' "$0"; exit 0 ;;
+ -*) echo "error: unknown option '$1'" >&2; exit 2 ;;
+ *) VERSION=$1; shift ;;
+ esac
+done
+
+if [[ -z ${VERSION} ]]; then
+ echo "usage: BRPCUSERNAME=<apache-id> $(basename "$0") <version> [--repo
dir] [--outdir dir]" >&2
+ exit 2
+fi
+if ! [[ ${VERSION} =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$ ]]; then
+ echo "error: '${VERSION}' is not a valid version" >&2
+ exit 2
+fi
+if [[ -z ${APACHE_ID} ]]; then
+ echo "error: no Apache ID; set BRPCUSERNAME or pass --user <apache-id>" >&2
+ exit 2
+fi
+if ! [[ ${APACHE_ID} =~ ^[A-Za-z0-9._-]+$ ]]; then
+ echo "error: invalid Apache ID '${APACHE_ID}'" >&2
+ exit 2
+fi
+
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+REPO=${REPO:-$(git rev-parse --show-toplevel)}
+OUTDIR=${OUTDIR:-${HOME}/brpc_release/${VERSION}}
+PREFIX="apache-brpc-${VERSION}-src"
+TARBALL="${PREFIX}.tar.gz"
+
+# GNU coreutils is not a given on macOS.
+if command -v sha512sum >/dev/null 2>&1; then SHA512=(sha512sum)
+elif command -v gsha512sum >/dev/null 2>&1; then SHA512=(gsha512sum)
+elif command -v shasum >/dev/null 2>&1; then SHA512=(shasum -a 512)
+else echo "error: need one of sha512sum, gsha512sum, shasum" >&2; exit 1
+fi
+
+step() { printf '\n==> %s\n' "$*"; }
+
+cd "${REPO}"
+
+step "Checking the working tree"
+echo " repo : ${REPO}"
+echo " branch : $(git rev-parse --abbrev-ref HEAD)"
+echo " head : $(git rev-parse --short HEAD)"
+echo " outdir : ${OUTDIR}"
+
+if [[ -n $(git status --porcelain) ]]; then
+ echo "error: working tree is dirty; commit or restore changes first" >&2
+ git status --short >&2
+ exit 1
+fi
+
+branch=$(git rev-parse --abbrev-ref HEAD)
+expected_branch="release-${VERSION%.*}"
+if [[ ${branch} != "${expected_branch}" ]]; then
+ echo "error: on '${branch}', expected '${expected_branch}'" >&2
+ exit 1
+fi
+
+# Do not change Git identity. An existing identity is needed only because the
+# local release tag is annotated; the RM owns repository configuration.
+if ! git config user.email >/dev/null || ! git config user.name >/dev/null;
then
+ echo "error: git identity is not configured; the RM must resolve this if
desired" >&2
+ echo " this script will not modify git user.name or user.email" >&2
+ exit 1
+fi
+
+step "Checking version files"
+"${SCRIPT_DIR}/bump_version.sh" "${VERSION}" --check
+
+step "Tagging ${VERSION} (local only)"
+if git rev-parse -q --verify "refs/tags/${VERSION}" >/dev/null; then
+ tagged=$(git rev-parse "${VERSION}^{commit}")
+ head=$(git rev-parse HEAD)
+ if [[ ${tagged} != "${head}" ]]; then
+ echo "error: tag ${VERSION} already exists at ${tagged:0:12} but HEAD
is ${head:0:12}" >&2
+ echo " if the tag was never pushed, the RM may drop it with: git
tag -d ${VERSION}" >&2
+ exit 1
+ fi
+ echo " tag already exists at HEAD, reusing"
+else
+ git tag -a "${VERSION}" -m "release ${VERSION}"
+ echo " created"
+fi
+COMMIT=$(git rev-parse "${VERSION}^{commit}")
+
+step "Building ${TARBALL}"
+mkdir -p "${OUTDIR}"
+git archive --format=tar.gz "${VERSION}" \
+ --prefix="${PREFIX}/" --output="${OUTDIR}/${TARBALL}"
+echo " $(cd "${OUTDIR}" && du -h "${TARBALL}" | cut -f1)"
+
+cd "${OUTDIR}"
+
+step "Signing as ${APACHE_ID}@apache.org"
+echo " GPG will ask the RM for the private-key passphrase"
+rm -f "${TARBALL}.asc"
+gpg -u "${APACHE_ID}@apache.org" --armor \
+ --output "${TARBALL}.asc" --detach-sign "${TARBALL}"
+
+step "Verifying GPG signature"
+gpg --verify "${TARBALL}.asc" "${TARBALL}"
+
+# Run from the tarball directory so the checksum records a bare filename.
+step "Generating ${TARBALL}.sha512"
+"${SHA512[@]}" "${TARBALL}" > "${TARBALL}.sha512"
+
+step "Checking SHA512"
+"${SHA512[@]}" --check "${TARBALL}.sha512"
+
+cat <<EOF
+
+==> Artifacts in ${OUTDIR}
+$(ls -1 "${PREFIX}"* | sed 's/^/ /')
+
+ Release Commit ID: ${COMMIT}
+ (this is what goes in the [VOTE] mail, not the branch HEAD)
+
+==> Next, run these yourself -- this script will not:
+
+ cd "${REPO}" && git push origin --tags
+
+ mkdir -p ~/brpc_release/svn/dev/brpc/${VERSION}
+ cp "${OUTDIR}/${TARBALL}"{,.asc,.sha512}
~/brpc_release/svn/dev/brpc/${VERSION}/
+ cd ~/brpc_release/svn/dev/brpc && svn add --force . \
+ && svn --username="${APACHE_ID}" commit -m "release ${VERSION}"
+
+ Then verify the uploaded candidate:
+ "${SCRIPT_DIR}/verify_package.sh"
https://dist.apache.org/repos/dist/dev/brpc/${VERSION}/
+EOF
diff --git a/community/skills/brpc-release/scripts/release_notes.sh
b/community/skills/brpc-release/scripts/release_notes.sh
new file mode 100755
index 00000000..aca3f153
--- /dev/null
+++ b/community/skills/brpc-release/scripts/release_notes.sh
@@ -0,0 +1,240 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# Draft Release Notes from GitHub pull requests represented by commits between
+# two refs. One merged PR produces at most one entry, even when it has many
+# commits. The script fetches the PR title and author through `gh api`.
+#
+# Usage:
+# release_notes.sh <from-ref> <to-ref> [--repo <owner/repo>]
+#
+# Example: <to-ref> is the local release tag, which need not be pushed to
GitHub.
+# release_notes.sh "$PREV_VERSION" "$VERSION" >
"$HOME/brpc_release/$VERSION/notes-draft.md"
+#
+# `gh auth login` must be completed before running this script. Buckets are
+# title-keyword guesses only; review every PR and reword it for users.
+
+set -euo pipefail
+
+FROM=""
+TO=""
+REPO=apache/brpc
+
+while (( $# )); do
+ case $1 in
+ --repo)
+ [[ $# -ge 2 ]] || { echo "error: --repo requires <owner/repo>"
>&2; exit 2; }
+ REPO=$2
+ shift 2
+ ;;
+ -h|--help)
+ sed -n '20,31p' "$0"
+ exit 0
+ ;;
+ -*)
+ echo "error: unknown option '$1'" >&2
+ exit 2
+ ;;
+ *)
+ if [[ -z ${FROM} ]]; then
+ FROM=$1
+ elif [[ -z ${TO} ]]; then
+ TO=$1
+ else
+ echo "error: too many positional arguments" >&2
+ exit 2
+ fi
+ shift
+ ;;
+ esac
+done
+
+if [[ -z ${FROM} || -z ${TO} ]]; then
+ echo "usage: $(basename "$0") <from-ref> <to-ref> [--repo <owner/repo>]"
>&2
+ exit 2
+fi
+if ! [[ ${REPO} =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
+ echo "error: '${REPO}' is not a valid GitHub owner/repository" >&2
+ exit 2
+fi
+if ! command -v gh >/dev/null 2>&1; then
+ echo "error: GitHub CLI (gh) is required to generate PR-based Release
Notes" >&2
+ exit 1
+fi
+if ! gh auth status --hostname github.com >/dev/null 2>&1; then
+ echo "error: authenticate GitHub CLI first: gh auth login --hostname
github.com" >&2
+ exit 1
+fi
+
+# The local release tag is the endpoint. It need not have been pushed to
GitHub:
+# GitHub is consulted only for PR metadata, while the commit range is local.
+for ref in "${FROM}" "${TO}"; do
+ if ! git rev-parse -q --verify "${ref}^{commit}" >/dev/null; then
+ echo "error: '${ref}' is not a commit. Fetch tags first: git fetch
--tags" >&2
+ exit 1
+ fi
+done
+
+RANGE="${FROM}..${TO}"
+commit_total=$(git rev-list --count "${RANGE}")
+if (( commit_total == 0 )); then
+ echo "error: no commits in ${RANGE}" >&2
+ exit 1
+fi
+
+feature=$(mktemp)
+bugfix=$(mktemp)
+enhance=$(mktemp)
+other=$(mktemp)
+unassociated=$(mktemp)
+trap 'rm -f "${feature}" "${bugfix}" "${enhance}" "${other}"
"${unassociated}"' EXIT
+
+# Extract PR numbers from commit subjects, then retrieve all closed PR metadata
+# with GitHub REST pagination and select the required merged PRs locally. This
+# avoids one request per commit and remains robust when a subject contains an
+# upstream issue number that is not a bRPC PR. GitHub-generated merge/squash/
+# rebase subjects preserve the final merged PR number. Any commit without one
is
+# listed separately for human review and is never made into a commit-level
note.
+# Do not use Bash associative arrays: macOS still ships Bash 3.2.
+prs=$(mktemp)
+all_prs=$(mktemp)
+pr_data_file=$(mktemp)
+trap 'rm -f "${feature}" "${bugfix}" "${enhance}" "${other}" "${unassociated}"
"${prs}" "${all_prs}" "${pr_data_file}"' EXIT
+while IFS=$'\t' read -r sha subject; do
+ # Example: "... (#2793) (#3491)". The final #number is the bRPC PR.
+ if [[ ${subject} =~ .*\#([0-9]+) ]]; then
+ printf '%s\n' "${BASH_REMATCH[1]}" >> "${prs}"
+ else
+ printf '%s %s\n' "${sha:0:12}" "${subject}" >> "${unassociated}"
+ fi
+done < <(git log --format='%H%x09%s' "${RANGE}")
+
+sort -u "${prs}" -o "${prs}"
+pr_total=$(wc -l < "${prs}" | tr -d ' ')
+if (( pr_total == 0 )); then
+ echo "error: no GitHub PR references found in ${RANGE}; Release Notes must
be PR-based" >&2
+ exit 1
+fi
+
+# GitHub returns up to 100 closed PRs per page. This is dozens of requests for
+# the whole repository rather than hundreds of commit/PR requests per release.
+echo "Loading merged PR metadata in pages from ${REPO}..." >&2
+if ! gh api --paginate "repos/${REPO}/pulls?state=closed&per_page=100" \
+ --jq '.[] | select(.merged_at != null) | [.number, .title, .user.login,
.merged_at] | @tsv' \
+ > "${all_prs}"; then
+ echo "error: failed to retrieve merged PR metadata from ${REPO}" >&2
+ exit 1
+fi
+
+# Both files use lexicographic PR-number ordering required by join, so it can
+# select only the PRs in the release range. If a subject's final #number is an
+# external issue rather than a bRPC PR, leave it in the review comment instead
+# of failing the entire draft.
+sort -k1,1 "${all_prs}" -o "${all_prs}"
+join -t $'\t' -1 1 -2 1 "${prs}" "${all_prs}" > "${pr_data_file}"
+metadata_total=$(grep -cve '^$' "${pr_data_file}" || true)
+if (( metadata_total != pr_total )); then
+ missing_prs=$(mktemp)
+ trap 'rm -f "${feature}" "${bugfix}" "${enhance}" "${other}"
"${unassociated}" "${prs}" "${all_prs}" "${pr_data_file}" "${missing_prs}"' EXIT
+ join -t $'\t' -v 1 -1 1 -2 1 "${prs}" "${all_prs}" > "${missing_prs}"
+ while IFS= read -r missing_pr; do
+ printf 'unresolved #PR reference: %s\n' "${missing_pr}" >>
"${unassociated}"
+ done < "${missing_prs}"
+ echo "warning: retrieved ${metadata_total} PR record(s), expected
${pr_total}; unresolved references were left for review" >&2
+ pr_total=${metadata_total}
+fi
+if (( pr_total == 0 )); then
+ echo "error: no merged bRPC PR metadata found for ${RANGE}" >&2
+ exit 1
+fi
+
+while IFS=$'\t' read -r number title author merged_at; do
+ if [[ -z ${merged_at} || ${merged_at} == null ]]; then
+ echo "error: #${number} is not a merged PR; refusing to include it" >&2
+ exit 1
+ fi
+
+ lower=$(printf '%s' "${title}" | tr '[:upper:]' '[:lower:]')
+ # GitHub Release automatically recognizes @user and #PR-id; do not emit
+ # explicit Markdown links. Keep one independent entry per PR: the same
+ # author may contribute multiple unrelated features or fixes.
+ entry="- ${title} by @${author} (#${number})"
+ case ${lower} in
+ fix*|*"fix "*|*fixes*|bug*|*"bugfix"*|revert*)
+ printf '%s\n' "${entry}" >> "${bugfix}"
+ ;;
+ feat*|add\ *|"support "*|*"add support"*|"new "*|"implement
"*|"introduce "*)
+ printf '%s\n' "${entry}" >> "${feature}"
+ ;;
+ perf*|refactor*|"improve "*|"optimize "*|"speed up"*|"reduce
"*|"enhance "*|"clean"*|"remove "*|"update "*|"upgrade "*)
+ printf '%s\n' "${entry}" >> "${enhance}"
+ ;;
+ doc*|test*|ci*|build*|chore*|style*)
+ printf '%s\n' "${entry}" >> "${other}"
+ ;;
+ *)
+ printf '%s\n' "${entry}" >> "${enhance}"
+ ;;
+ esac
+done < "${pr_data_file}"
+
+emit() {
+ local title=$1 file=$2
+ printf '\n%s:\n' "${title}"
+ if [[ -s ${file} ]]; then
+ cat "${file}"
+ else
+ printf -- '- (none)\n'
+ fi
+}
+
+cat <<EOF
+<!--
+ DRAFT Release Notes for ${RANGE}
+ ${pr_total} merged GitHub PR(s), derived from ${commit_total} commit(s).
+ Entries are PR-based: one entry per PR, with metadata loaded in paginated
batches.
+ Buckets are keyword-guessed -- re-file and reword every entry before
publishing.
+ Produce a Chinese version too; the two go to different channels.
+EOF
+if [[ -s ${unassociated} ]]; then
+ cat <<EOF
+
+ Commits without an associated GitHub PR (${unassociated}):
+$(sed 's/^/ /' "${unassociated}")
+ Review them manually; do not turn them into commit-level Release Notes.
+EOF
+fi
+cat <<EOF
+-->
+
+[Release Notes]
+EOF
+
+emit "Feature" "${feature}"
+emit "Bugfix" "${bugfix}"
+emit "Enhancement" "${enhance}"
+emit "Other" "${other}"
+
+cat <<EOF
+
+<!--
+Full comparison: https://github.com/${REPO}/compare/${FROM}...${TO}
+PR search: https://github.com/${REPO}/pulls?q=is%3Apr+is%3Amerged
+-->
+EOF
diff --git a/community/skills/brpc-release/scripts/verify_package.sh
b/community/skills/brpc-release/scripts/verify_package.sh
new file mode 100755
index 00000000..10cfb379
--- /dev/null
+++ b/community/skills/brpc-release/scripts/verify_package.sh
@@ -0,0 +1,253 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# Verify an Apache bRPC release candidate and print a summary that can be
+# pasted straight into a vote reply.
+#
+# Usage:
+# verify_package.sh <dist-url|local-dir> [--keys <url>] [--no-diff]
+#
+# Examples:
+# verify_package.sh https://dist.apache.org/repos/dist/dev/brpc/1.18.0/
+# verify_package.sh ~/brpc_release/1.18.0
+#
+# A portable companion to community/apache-package-validator.sh (which needs
+# wget and GNU coreutils); this one runs on macOS too and additionally diffs
+# the tarball against the GitHub tag.
+
+set -uo pipefail
+
+SOURCE=""
+KEYS_URL="https://downloads.apache.org/brpc/KEYS"
+DO_DIFF=1
+
+while (( $# )); do
+ case $1 in
+ --keys) KEYS_URL=$2; shift 2 ;;
+ --no-diff) DO_DIFF=0; shift ;;
+ -h|--help) sed -n '20,33p' "$0"; exit 0 ;;
+ -*) echo "error: unknown option '$1'" >&2; exit 2 ;;
+ *) SOURCE=$1; shift ;;
+ esac
+done
+
+if [[ -z ${SOURCE} ]]; then
+ echo "usage: $(basename "$0") <dist-url|local-dir> [--keys <url>]
[--no-diff]" >&2
+ exit 2
+fi
+
+if command -v sha512sum >/dev/null 2>&1; then SHA512=(sha512sum)
+elif command -v gsha512sum >/dev/null 2>&1; then SHA512=(gsha512sum)
+elif command -v shasum >/dev/null 2>&1; then SHA512=(shasum -a 512)
+else echo "error: need one of sha512sum, gsha512sum, shasum" >&2; exit 1
+fi
+
+r_link=' '; r_sum=' '; r_sig=' '; r_ver=' '; r_lic=' '; r_bin=' '; r_tag=' '
+failures=0
+
+pass() { printf ' PASS %s\n' "$*"; }
+fail() { printf ' FAIL %s\n' "$*" >&2; failures=$((failures + 1)); }
+warn() { printf ' WARN %s\n' "$*" >&2; }
+step() { printf '\n==> %s\n' "$*"; }
+
+WORK=$(mktemp -d)
+trap 'summary; rm -rf "${WORK}"' EXIT
+
+summary() {
+ cat <<EOF
+
+------------------------------------------------------------------------
+Paste-ready summary for the vote thread:
+
+I checked:
+- [${r_link}] the links of the package are valid;
+- [${r_sum}] the checksum of the package is valid;
+- [${r_sig}] the signature of the package is valid;
+- [${r_ver}] RELEASE_VERSION in the source code matches the current release;
+- [${r_lic}] LICENSE and NOTICE are present;
+- [${r_bin}] no compiled archives bundled in the source archive;
+- [${r_tag}] the source distribution matches the git tag.
+
+Still to check by hand: it builds, unit tests pass, and third-party
+license declarations are complete (see references/checklist.md).
+------------------------------------------------------------------------
+EOF
+ if (( failures )); then
+ echo "${failures} check(s) FAILED" >&2
+ fi
+}
+
+# ---------------------------------------------------------------- fetch
+
+step "Collecting artifacts"
+if [[ -d ${SOURCE} ]]; then
+ found=$(ls "${SOURCE}"/apache-brpc-*-src.tar.gz 2>/dev/null | head -1)
+ if [[ -z ${found} ]]; then
+ fail "no apache-brpc-*-src.tar.gz in ${SOURCE}"
+ exit 1
+ fi
+ TARBALL=$(basename "${found}")
+ VERSION=${TARBALL#apache-brpc-}; VERSION=${VERSION%-src.tar.gz}
+ for suffix in "" .asc .sha512; do
+ if [[ -f ${SOURCE}/${TARBALL}${suffix} ]]; then
+ cp "${SOURCE}/${TARBALL}${suffix}" "${WORK}/"
+ else
+ fail "missing ${TARBALL}${suffix}"
+ fi
+ done
+ (( failures == 0 )) && r_link='x'
+ echo " local: ${SOURCE} (version ${VERSION})"
+else
+ base=${SOURCE%/}
+ VERSION=${base##*/}
+ TARBALL="apache-brpc-${VERSION}-src.tar.gz"
+ ok=1
+ echo " from ${base}"
+ for suffix in "" .asc .sha512; do
+ # dist.apache.org has no CDN in front of it and routinely crawls along
+ # at a few KB/s, so show a progress bar for the tarball rather than
+ # leaving the RM staring at a silent terminal for ten minutes.
+ if [[ -z ${suffix} ]]; then
+ curl_opts=(--progress-bar)
+ echo " downloading ${TARBALL} (dist.apache.org can be very
slow)"
+ else
+ curl_opts=(-sS)
+ fi
+ if curl -fL "${curl_opts[@]}" --connect-timeout 20 --max-time 1800 \
+ -o "${WORK}/${TARBALL}${suffix}" "${base}/${TARBALL}${suffix}";
then
+ echo " got ${TARBALL}${suffix}"
+ else
+ fail "cannot download ${base}/${TARBALL}${suffix}"
+ ok=0
+ fi
+ done
+ (( ok )) && r_link='x'
+fi
+
+cd "${WORK}" || exit 1
+[[ -f ${TARBALL} ]] || { fail "no tarball to verify"; exit 1; }
+
+# ---------------------------------------------------------------- checksum
+
+step "sha512"
+if [[ -f ${TARBALL}.sha512 ]]; then
+ if grep -q '/' "${TARBALL}.sha512"; then
+ warn "${TARBALL}.sha512 records a path, not a bare filename; --check
may fail for others"
+ fi
+ if "${SHA512[@]}" --check "${TARBALL}.sha512"; then
+ r_sum='x'; pass "checksum matches"
+ else
+ fail "checksum mismatch"
+ fi
+else
+ fail "no .sha512 file"
+fi
+
+# ---------------------------------------------------------------- signature
+
+step "GPG signature"
+if [[ -f ${TARBALL}.asc ]]; then
+ if ! curl -fsSL "${KEYS_URL}" | gpg --import 2>/dev/null; then
+ warn "could not import KEYS from ${KEYS_URL}"
+ fi
+ # A candidate still in dist/dev may only have its key in the dev KEYS file.
+ if [[ ${KEYS_URL} == *"/release/"* || ${KEYS_URL} ==
*"downloads.apache.org"* ]]; then
+ curl -fsSL "https://dist.apache.org/repos/dist/dev/brpc/KEYS" \
+ | gpg --import 2>/dev/null || true
+ fi
+ if gpg --verify "${TARBALL}.asc" "${TARBALL}"; then
+ r_sig='x'; pass "signature is valid"
+ echo " (a 'no ultimately trusted keys' warning is expected and fine
--"
+ echo " it means the key is not in your web of trust, not that the
signature is bad)"
+ else
+ fail "signature does not verify"
+ fi
+else
+ fail "no .asc file"
+fi
+
+# ---------------------------------------------------------------- contents
+
+step "Unpacking"
+tar -xzf "${TARBALL}" || { fail "cannot unpack ${TARBALL}"; exit 1; }
+SRC="apache-brpc-${VERSION}-src"
+[[ -d ${SRC} ]] || { fail "tarball does not contain ${SRC}/"; exit 1; }
+
+step "Version consistency"
+in_release=$(tr -d '[:space:]' < "${SRC}/RELEASE_VERSION" 2>/dev/null)
+if [[ ${in_release} == "${VERSION}" ]] \
+ && grep -q "set(BRPC_VERSION ${VERSION})" "${SRC}/CMakeLists.txt"
2>/dev/null; then
+ r_ver='x'; pass "RELEASE_VERSION and CMakeLists.txt both say ${VERSION}"
+else
+ fail "version mismatch (RELEASE_VERSION='${in_release}', expected
'${VERSION}')"
+fi
+
+step "LICENSE and NOTICE"
+if [[ -f ${SRC}/LICENSE && -f ${SRC}/NOTICE ]]; then
+ r_lic='x'; pass "both present"
+ year=$(sed -n 's/^Copyright [0-9]*-\([0-9]*\).*/\1/p' "${SRC}/NOTICE" |
head -1)
+ [[ ${year} == "$(date +%Y)" ]] || warn "NOTICE copyright ends at ${year},
current year is $(date +%Y)"
+else
+ fail "LICENSE and/or NOTICE missing"
+fi
+
+step "Unexpected binaries"
+# `certificate` covers test cert/key fixtures whose `file` output varies by
+# platform (PEM on macOS, sometimes a bare "certificate"/DER blob elsewhere).
+nontext=$(find "${SRC}" -type f -print0 \
+ | xargs -0 file \
+ | grep -v
'GIF\|JPEG\|PNG\|SVG\|PowerPoint\|Git\|JSON\|PEM\|certificate\|empty\|text\|XML'
|| true)
+
+# Fuzzing seed corpora are opaque by design and are allowlisted in
+# .licenserc.yaml; flagging them sends the RM chasing a phantom -1.
+expected=$(printf '%s\n' "${nontext}" | grep
'test/fuzzing/fuzz_[^/]*_seed_corpus/' || true)
+suspicious=$(printf '%s\n' "${nontext}" | grep -v
'test/fuzzing/fuzz_[^/]*_seed_corpus/' | grep -v '^$' || true)
+
+if [[ -n ${expected} ]]; then
+ note_count=$(printf '%s\n' "${expected}" | grep -c . || true)
+ echo " ${note_count} fuzzing seed corpus file(s) are binary by design
(allowlisted in .licenserc.yaml)"
+fi
+if [[ -z ${suspicious} ]]; then
+ r_bin='x'; pass "no compiled archives or stray binaries"
+else
+ fail "suspicious files:"
+ printf '%s\n' "${suspicious}" | sed 's/^/ /' >&2
+fi
+
+# ---------------------------------------------------------------- vs tag
+
+if (( DO_DIFF )); then
+ step "Diff against the GitHub tag"
+ if curl -fsSL -o "tag-${VERSION}.tar.gz" \
+ "https://github.com/apache/brpc/archive/refs/tags/${VERSION}.tar.gz";
then
+ tar -xzf "tag-${VERSION}.tar.gz"
+ if diff -r "brpc-${VERSION}" "${SRC}" > tag.diff 2>&1; then
+ r_tag='x'; pass "identical to the tag"
+ else
+ fail "differs from the tag:"
+ head -40 tag.diff | sed 's/^/ /' >&2
+ fi
+ else
+ warn "tag ${VERSION} not published on GitHub yet; skipping"
+ fi
+else
+ warn "tag diff skipped (--no-diff)"
+fi
+
+exit $(( failures > 0 ))
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]