frp 开发工作流详解:构建、测试、代码质量与发布流程(基于 AGENTS.md)
frp(fast reverse proxy)仓库在根目录维护了一份面向开发者与 AI Agent 的开发指引 AGENTS.md,系统性地约定了构建、测试、代码质量检查和资产打包等日常开发命令,并指向 doc/agents/ 下的运行手册。本文以该文档为骨架,结合 Makefile、hack/run-e2e.sh、.golangci.yml 与 e2e 测试框架源码,把每条命令背后的实际行为、参数与适用场景讲清楚,帮助你在本地完整复现 frp 的“构建 — 单测 — e2e — 静态检查 — 发布”全流程。
一、AGENTS.md 文档结构总览
AGENTS.md 是仓库为开发者与 Agent 提供的“操作入口”,内容分为四部分:
| 章节 | 核心内容 |
|---|---|
| Development Commands | 构建(Build)、测试(Testing)、代码质量(Code Quality)、Web 资产(Assets)、清理(Cleanup)五组 make 命令 |
| Testing | e2e 测试的技术栈(Ginkgo/Gomega)、mock 服务器位置、运行入口 |
| Agent Runbooks | 指向 doc/agents/ 目录下的运维流程文档,当前包含 doc/agents/release.md(发布流程) |
所有命令统一通过根目录 Makefile 驱动,因此理解 Makefile 中各 target 的实现是掌握整个开发工作流的关键。
二、构建系统:make build / make frps / make frpc / make all
AGENTS.md 定义了四个构建目标:
make build— 同时构建 frps 和 frpc 两个二进制;make frps— 只构建服务端二进制;make frpc— 只构建客户端二进制;make all— 带格式化的全量构建(等价于env fmt web build)。
对照 Makefile 源码,frps 与 frpc target 的实际执行语句是:
frps:
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags "frps$(NOWEB_TAG)" -o bin/frps ./cmd/frps
frpc:
env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -tags "frpc$(NOWEB_TAG)" -o bin/frpc ./cmd/frpc
其中有几个值得注意的细节:
- 纯静态编译:
CGO_ENABLED=0保证产物不依赖系统 C 库,-trimpath去除本地路径信息,LDFLAGS := -s -w则去掉符号表和 DWARF 调试信息以减小体积——这三者组合是典型的跨平台发布编译配置。 frps/frpc构建标签:两个二进制各自带独立 build tag(-tags "frps"/-tags "frpc"),即cmd/frps与cmd/frpc两套入口(见 cmd/frps/main.go 与 cmd/frpc/main.go)按标签条件编译,避免把对方的代码带进二进制。noweb标签的自动协商:Makefile 第 4 行的NOWEB_TAG会检测web/frps/dist与web/frpc/dist两个目录是否存在,任一缺失就追加,noweb标签。配合 web/frps/embed.go 与 web/frps/embed_stub.go 这类成对的构建约束文件:当先执行make web产出 dist 后,Web 控制台会被真正嵌入二进制;未构建前端时,嵌入逻辑退化为空实现(stub),Go 代码依然可编译。
因此推荐的标准构建顺序是 make all 或先 make web 再 make build。make all 的依赖链为 env fmt web build,即打印 Go 版本、格式化代码、构建 Web 资产、再构建二进制,一步到位。
Web 资产目标本身很简单,web/frps/Makefile 与 web/frpc 的 Makefile 一致:
install:
@cd .. && npm install
build: install
@npm run build
即 make frps-web / make frpc-web 实际是进入 web/frps、web/frpc 子目录执行 npm 安装与 Vite 构建(两个前端工程均为 Vue + Vite + npm workspace 组织,见 web/package.json)。
三、单元测试:make test
make test 依赖 gotest target,其实现为对五个顶层包分组执行带覆盖率的测试:
gotest:
go test -tags "$(NOWEB_TAG)" -v --cover ./assets/...
go test -tags "$(NOWEB_TAG)" -v --cover ./cmd/...
go test -tags "$(NOWEB_TAG)" -v --cover ./client/...
go test -tags "$(NOWEB_TAG)" -v --cover ./server/...
go test -tags "$(NOWEB_TAG)" -v --cover ./pkg/...
可以看到,单测覆盖 assets/、cmd/、client/、server/、pkg/ 五大模块——分别对应前端资产、命令行入口、客户端(代理、visitor、控制连接)、服务端(代理、组、注册表)和公共库(消息协议、配置解析、传输、限流等)。-tags "$(NOWEB_TAG)" 与构建保持同一套条件编译约定,保证无论 Web dist 是否存在,测试编译都能通过。
四、端到端测试:Ginkgo/Gomega 与 mock 服务器
AGENTS.md 在 Testing 章节明确了两点事实:
- e2e 测试基于 Ginkgo/Gomega 框架;
- mock 服务器位于
test/e2e/mock/,运行入口为make e2e或make alltest。
结合仓库源码可以进一步展开。
4.1 入口脚本 hack/run-e2e.sh
make e2e 实际执行 hack/run-e2e.sh,make e2e-trace 则是带 DEBUG=true LOG_LEVEL=trace 环境的同一脚本。脚本的关键逻辑:
# Check if ginkgo is available
if ! command -v ginkgo >/dev/null 2>&1; then
go install github.com/onsi/ginkgo/v2/ginkgo@v2.23.4
fi
...
frpcPath=${ROOT}/bin/frpc # 可被 FRPC_PATH 覆盖
frpsPath=${ROOT}/bin/frps # 可被 FRPS_PATH 覆盖
concurrency="16" # 可被 CONCURRENCY 覆盖
ginkgo -nodes=${concurrency} --poll-progress-after=60s ${ROOT}/test/e2e \
-- -frpc-path=${frpcPath} -frps-path=${frpsPath} -log-level=${logLevel} -debug=${debug}
从脚本结构看,有三个实用要点:
- ginkgo 自动安装:环境里没有 ginkgo 时会以固定版本 v2.23.4 自动
go install,降低环境准备成本; - 二进制路径可注入:
FRPC_PATH/FRPS_PATH环境变量允许指定非本仓库构建的二进制。这正是版本兼容测试的机制——Makefile 中的e2e-compatibility-last-frpc/e2e-compatibility-last-frpstarget 就是先用hack/download.sh拉取上一版本发布二进制到lastversion/,再以FRPC_PATH=... FRPS_PATH=...方式跑 e2e,验证新旧版本混跑; - 默认 16 并行:
-nodes=16对应 Ginkgo 的并行模式,可用CONCURRENCY调整。
4.2 测试框架与套件组织
测试入口 test/e2e/e2e.go 中,RunE2ETests 通过 ginkgo.RunSpecs 启动名为 "frp e2e suite" 的套件,并用 ginkgo.SynchronizedBeforeSuite / SynchronizedAfterSuite 管理并行节点间的初始化与清理——这是 Ginkgo 并行测试的标准做法,保证全局 setup 只执行一次。套件本身随机化所有 spec(RandomizeAllSpecs = true),避免用例间顺序依赖。
按目录组织,e2e 分为几层(见 test/e2e/suites.go):
- test/e2e/v1/:v1 协议的完整功能测试,按
basic/(TCP、HTTP、tcpmux、xtcp、token、OIDC 等基础场景)、features/(带宽限制、心跳、组、真实 IP、store、SSH 隧道等特性)、plugin/(客户端/服务端插件)分组; - test/e2e/legacy/:面向旧版本兼容场景的测试集;
- test/e2e/compatibility/:跨版本兼容套件;
- test/e2e/framework/:框架层,封装了进程管理、期望断言、mock 服务、请求与 RPC 等测试基建。
4.3 mock 服务器
AGENTS.md 提到的 /test/e2e/mock/ 目录在仓库中位于 test/e2e/mock/,其 server/ 子目录提供三类测试用服务:
httpserver/— 模拟 HTTP 流量目标;streamserver/— 模拟 TCP 流式服务;oidcserver/— 模拟 OIDC 身份认证服务,供 pkg/auth/oidc.go 相关的认证测试使用。
它们共同实现一个极简的 Server 接口(test/e2e/mock/server/interface.go):
type Server interface {
Run() error
Close() error
BindAddr() string
BindPort() int
}
框架层启动这些 mock 后,通过 BindPort() 拿到随机端口动态生成 frpc/frps 配置,避免测试间的端口冲突。
4.4 全部测试:make alltest
make alltest 是发布前的完整质量门禁,Makefile 中定义为:
alltest: vet gotest e2e
即依次执行 go vet、分组单测(含覆盖率)、e2e 套件三步,与 AGENTS.md 中 “Run all tests including vet, unit tests, and e2e” 的描述完全对应。
五、代码质量:格式化、import 组织、vet 与 golangci-lint
AGENTS.md 的 Code Quality 章节列出的命令与 Makefile 实现一一对应:
| 命令 | Makefile 实现 | 说明 |
|---|---|---|
make fmt |
go fmt ./... |
标准 Go 格式化 |
make fmt-more |
gofumpt -l -w . |
gofumpt 更严格格式化 |
make gci |
gci write -s standard -s default -s "prefix(github.com/fatedier/frp/)" ./ |
import 分组排序:标准库、第三方、frp 自身包 |
make vet |
go vet -tags "$(NOWEB_TAG)" ./... |
官方静态检查 |
golangci-lint run |
— | 综合 lint,配置见 .golangci.yml |
其中 gci 的三段式分组规则(standard / default / prefix(github.com/fatedier/frp/))与 .golangci.yml 中 formatters 部分的 gci 配置完全一致,保证手工排序与 CI 检查口径统一。
.golangci.yml 采用的是 golangci-lint v2 配置格式,启用的 linter 包括 errcheck、gocritic、gosec、govet、ineffassign、lll(行宽 160)、misspell、prealloc、revive、staticcheck、unparam、unused 等十余项,formatters 启用 gci、gofumpt、goimports;同时通过 exclusions 排除了生成文件(*.pb.go、*.gen.go)、vendor/、bin/、node_modules 等路径,并对测试文件放宽了 errcheck 要求。CI 侧由 .github/workflows/golangci-lint.yml 工作流执行同一配置。
六、清理:make clean
make clean 用于移除构建产物与缓存目录,对应实现:
clean:
rm -f ./bin/frpc
rm -f ./bin/frps
rm -rf ./lastversion
rm -rf ./.cache
rm -rf ./.compat
除常规二进制外,还会清理版本兼容测试的 lastversion/(上一版本二进制)以及 .cache/、.compat/ 缓存目录——后者用于缓存 e2e 兼容性测试下载的发布二进制(缓存路径为 .cache/e2e-compat/<version>/<os>_<arch>/,见 doc/agents/release.md)。
七、Agent Runbooks:发布流程与关键文件
AGENTS.md 最后一节指出运维流程文档位于 doc/agents/ 目录,目前包含 doc/agents/release.md。该 runbook 完整描述了 frp 的发布流程,核心要点:
- 更新 Release Notes:编辑根目录 Release.md,按 Features / Improvements / Fixes 三段组织,该文件会被 GoReleaser 用作 GitHub Release 的正文;
- 提升版本号:修改 pkg/util/version/version.go 中的版本字符串(当前仓库值为
0.71.0),提交并推送到dev分支; - 预发布验证:本地跑
make e2e;若改动触及登录、控制连接、工作连接、visitor、传输或 wire 协议等兼容敏感区域,还需执行make e2e-compatibility与make e2e-compatibility-floor。这两个 Makefile target 会解析近期稳定版作为基线、下载(或复用缓存的)历史版本二进制,与当前构建产物做混跑测试,基线数量由FRP_COMPAT_BASELINE_COUNT(默认 8)控制,兼容性下限版本由FRP_COMPAT_FLOOR_VERSION(当前 0.61.0)控制,也可用FRP_COMPAT_BASELINE_VERSIONS指定显式基线矩阵以保证结果可复现; - dev 合入 master:以 merge commit(而非 squash)方式合并;
- 打 tag 并触发 GoReleaser:
git tag -a vX.Y.Z后手动触发goreleaser工作流,其执行 package.sh 完成跨平台交叉编译与打包(跨平台矩阵定义于 Makefile.cross-compiles),最终创建包含全部平台的 GitHub Release; - 版本号策略:Minor 发布为
v0.X.0,Patch 发布为v0.X.Y。
runbook 中还以表格形式列出了发布关键文件:
| 文件 | 用途 |
|---|---|
| pkg/util/version/version.go | 版本字符串 |
| Release.md | 发布说明(GoReleaser 读取) |
| package.sh | 交叉编译与打包脚本 |
| .github/workflows/goreleaser.yml | 手动触发的 GoReleaser 工作流 |
八、本地快速上手清单
按 AGENTS.md 与 Makefile 的实际行为,一个从零开始的本地开发验证顺序可以是:
# 1. 构建(自动完成格式化、Web 资产、双二进制构建)
make all
# 2. 仅跑单元测试(含覆盖率)
make test
# 3. 代码质量检查
make vet
golangci-lint run
# 4. 端到端测试(依赖 bin/ 下已构建的二进制)
make e2e
# 5. 完整质量门禁
make alltest
# 6. 清理
make clean
需要注意的前提:make e2e 依赖先构建出 bin/frpc 与 bin/frps(Makefile 的 e2e-compatibility* target 已自动声明 build 依赖,但 e2e 本身不会触发构建,脚本只是引用 ./bin/ 路径);make web 需要 Node.js/npm 环境;ginkgo 缺失时脚本会自动安装固定版本。
九、小结
AGENTS.md 虽然篇幅不长,但给出了 frp 仓库完整的开发命令面:构建(make build/frps/frpc/all)、测试(make test、make e2e、make alltest)、代码质量(make fmt/fmt-more/gci/vet 与 golangci-lint)、Web 资产(make web)与清理(make clean),并通过 doc/agents/ 目录挂载了 发布流程 runbook。结合 Makefile、hack/run-e2e.sh、.golangci.yml 和 test/e2e/ 测试框架的实现细节可以看出,这套工作流的设计特点是:构建标签统一(frps/frpc/noweb)、测试分层(vet → 单测 → e2e → 跨版本兼容)、静态检查口径与 CI 完全一致。掌握这些命令及其底层机制,即可在本地完整复现 frp 的 CI 质量门禁与发布验证流程。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00