- 开发工具
- CI/CD
- 构建工具
【免费下载链接】goreleaser
Release engineering, simplified
本文聚焦 GoReleaser 自 v2.9 起引入的
uvbuilder:它让你直接把 Python 项目的wheel与sdist构建纳入 GoReleaser 的发布流水线,与 checksums、签名、Docker 镜像等既有能力无缝衔接。读完本文,你将掌握builder: uv的完整配置项、双构建模式(wheel/sdist)的组织方式、PyPI 发布路径,以及该 builder 在源码层面的实现细节与限制。
GoReleaser 的 builder 体系原本以 Go 语言构建为主,而uvbuilder(实现于 internal/builders/uv/build.go)把 Python 打包工具 uv 的uv build能力接入了进来。它通过pyproject.toml读取项目名与版本号,调用uv build产出.whl或.tar.gz文件,并将其注册为 GoReleaser 的标准 artifact——这意味着这些 Python 发行包可以像二进制文件一样被签名、生成校验和、塞进 Docker 镜像、上传到各类发布目标。
快速开始:最小配置
在.goreleaser.yaml中,把builds的builder设为uv,并指定buildmode:
version: 2 builds: - builder: uv buildmode: wheel运行goreleaser release(或goreleaser build)后,GoReleaser 会在dist/<build-id>-all-all/目录下产出形如my_pkg-0.1.0-py3-none-any.whl的 wheel 文件。随后这个.whl(或 sdist 的.tar.gz)即可参与 GoReleaser 后续的所有发布环节:签名、checksum、Docker 镜像内嵌等。
从源码看,uvbuilder 在 init 函数 中通过api.Register("uv", Default)注册进 builder 工厂,GoReleaser 因此在解析配置时能够识别builder: uv。
Options:完整配置项详解
uvbuilder 支持以下配置项(完整示例来自 uv.md):
builds: # 可以定义多个 build,构成一个 yaml 列表 - # # build 的 ID。 # # 默认: 项目名。 id: "my-build" # 使用 uv。 builder: uv # 项目(子)目录路径,即 uv build 命令的工作目录。 # # 默认: "."。 dir: my-app # 构建模式。 # # 合法值: "wheel", "sdist"。 # 默认: "wheel"。 buildmode: sdist # 指定构建时使用的 uv 可执行文件。 # 大多数情况下可以安全忽略该选项。 # # 默认: "uv"。 # 支持模板。 tool: uv # 设置要执行的构建命令。 # 例如构建测试时可设为 "test"。 # 大多数情况下可以安全忽略该选项。 # # 默认: build。 command: build # 自定义 flags。 # # 支持模板。 flags: - --offline # 构建期间设置的自定义环境变量。 # 无效的环境变量会被忽略。 # # 默认: os.Environ() ++ env 配置段。 # 支持模板。 env: - FOO=bar # hooks 可用于自定义最终产物, # 例如运行代码生成器。 # # 支持模板。 hooks: pre: ./foo.sh post: ./script.sh {{ .Path }}下面逐项结合源码说明其行为。
buildmode:wheel 还是 sdist
buildmode决定uv build的产物类型,取值只能是wheel或sdist(留空等同于wheel)。在 Build 方法 中,GoReleaser 依据该值追加不同的 uv 参数并构造对应 artifact:
wheel(或空字符串)→ 追加--wheel,产物为PyWheel类型;sdist→ 追加--sdist,产物为PySdist类型;- 其他任何值 → 返回错误
uv: invalid buildmode %q。
在配置层面,pkg/config/config.go 中Buildmode字段的 JSON Schema 枚举为c-archive、c-shared、pie、wheel、sdist与空值,其中c-archive/c-shared/pie是 Go 构建的既有模式,wheel/sdist即为 uv builder 新增的取值。
dir:uv build 的工作目录
dir是执行uv build命令的工作目录(默认.)。源码在构建时会用filepath.Join(build.Dir, "pyproject.toml")定位项目元数据(见 Build 方法),并把dir作为base.Exec的执行目录(见 build.go#L177)。因此你的 Python 项目根目录下必须存在有效的pyproject.toml。
tool 与 command:可替换的可执行文件与子命令
tool(默认uv):可执行文件路径或命令名,支持模板渲染(tpl.Apply(build.Tool))。比如你可以指向uv的自定义安装路径。command(默认build):传给 uv 的子命令。文档特别提到,若想“构建测试”,可设为test,GoReleaser 会执行uv test --out-dir ...。这得益于命令是直接拼装的(见 build.go#L158-L163):<tool> <command> --out-dir <root> [--wheel|--sdist] [flags]。
flags 与 env:模板化扩展
flags(支持模板,追加在命令末尾):例如--offline让uv build完全离线工作;空字符串 flag 会被tmpl.NonEmpty()过滤掉(见 build.go#L171-L175)。env(支持模板):构建时注入的自定义环境变量。源码中实际执行环境为ctx.Env(项目配置的 env 段)叠加 build 级env,即文档所述的os.Environ() ++ env 配置段语义(见 build.go#L145-L169)。
hooks:构建前后挂钩
hooks.pre与hooks.post分别在uv build执行前后运行,支持模板(例如post中可通过{{ .Path }}拿到产物路径)。典型用法包括在打包前运行代码生成器、在打包后改写产物等。这一能力由 GoReleaser 的构建 hooks 机制统一提供。
重要限制:target 固定为 none-any
[!WARNING] 目前该 builder 仅支持 GoReleaser target
none-any。py3-none-any是 wheel 文件名中的平台标签,并非 GoReleaser target。
这句话有两层含义,源码中体现得很清楚:
- GoReleaser 侧:
uvbuilder 的默认与唯一合法 target 是none-any(常量defaultTarget,见 build.go#L35)。WithDefaults 会拒绝多个 target 或非none-any的 target(错误uv: only target supported is 'none-any');Parse 对非默认 target 仅打印警告。产出的 artifact 中Goos: "all"、Goarch: "all"、Target: "none-any"(见 wheel/sdist 构造)。 - wheel 文件名侧:wheel 的标准命名是
<规范化包名>-<版本>-<python 标签>-<abi 标签>-<平台标签>.whl。纯 Python 包通常得到...-py3-none-any.whl,其中py3-none-any描述的是 Python 解释器/ABI/平台兼容性,与 GoReleaser 的 target 概念无关,二者不要混淆。
同时构建 wheel 和 sdist
uv build一次构建只产出一种类型,因此要同时拿到 wheel 与 sdist,需要声明两个 build,各用一种buildmode:
version: 2 builds: - id: wheel builder: uv buildmode: wheel - id: sdist builder: uv buildmode: sdist这正是仓库内置示例 internal/static/config.uv.yaml 的写法,也是 TestBuild 所验证的场景:测试中分别以wheel与sdist两种 buildmode 各构建一次,最终断言产生proj-0.1.0-py3-none-any.whl与proj-0.1.0.tar.gz两个 artifact。两个 build 使用不同的id,后续发布阶段即可按 ID 区分、单独引用。
发布到 PyPI
{{< g_featpro >}}
PyPI 发布属于 GoReleaser Pro 的能力:可以在配置里通过 global after hooks 调用uv publish完成上传:
# global after hooks after: hooks: - cmd: "uv publish" if: "{{ .IsRelease }}"if: "{{ .IsRelease }}"确保仅在正式发布(而非快照/预发布流程)时执行。如果你使用的是 GoReleaser OSS 版本,则需要在自己 CI 工作流里另起一个发布步骤调用uv publish(例如上传dist/中的 wheel 与 sdist 文件)。
源码原理:从 pyproject.toml 到 artifact
理解 uv builder 的内部流程,有助于排查构建与命名问题。
1. 解析 pyproject.toml
构建前,GoReleaser 会通过 internal/pyproject/pyproject.go 解析pyproject.toml,读取[project]段的name与version;若二者为空,还会回退读取 Poetry 风格[tool.poetry]的name/version。解析失败时构建直接报错:uv: could not open pyproject.toml: ...。
2. 生成产物文件名与 artifact
文件名由解析出的项目信息拼接而成(见 wheel/sdist 函数):
- wheel:
<规范化包名>-<版本>-py3-none-any.whl; - sdist:
<规范化包名>-<版本>.tar.gz。
其中“规范化包名”会把名字转为小写,并将-、_、.统一归一化为_(连续分隔符合并为一个),见 normalizeName。所以测试中名为My..Pkg的项目会生成my_pkg-0.1.0-py3-none-any.whl(TestArtifactNames)。
这些文件以PyWheel/PySdist类型进入 GoReleaser 的 artifact 列表(类型定义见 internal/artifact/artifact.go),并携带ExtraBuilder: "uv"、ExtraExt(.whl/.tar.gz)、ExtraID等元数据,供下游 pipe 使用。
3. 组装并执行 uv 命令
实际命令形如:
uv build --out-dir dist/<build-id>-all-all [--wheel|--sdist] [flags...]--out-dir被固定为当前 build 的输出目录,以保证产物落在 GoReleaser 预期的位置(见 build.go#L156-L163)。执行通过base.Exec完成,它对环境变量做了脱敏处理,避免令牌等敏感信息出现在日志中(相关测试见 internal/builders/base/build_test.go)。
4. 默认值与参数校验
WithDefaults 会补齐默认值并做严格校验:
targets为空时填入none-any;tool默认uv;command默认build;dir默认.;- 设置
main会报错(main is not used for uv); - 设置
binary会报错(uv: binary name is set by uv itself,产物文件名由 uv 按规范生成,无需也不能自定义); ldflags、goos、goarch、tags等 Go 专属配置会被base.ValidateNonGoConfig拒绝(见 internal/builders/base/build_test.go#L19-L80)。
另外,该 builder 目前在首次使用时会在日志中提示you are using the experimental UV builder,说明它仍处于实验性阶段,配置项可能在后续版本调整。
5. 可复现构建:ModTimestamp
与其他 builder 一致,uv builder 支持mod_timestamp:构建完成后通过base.ChTimes把产物 mtime 统一设置为指定时间(见 build.go#L181-L183),从而让 wheel/sdist 的时间戳可复现,利于可复现构建与缓存校验。
依赖要求与验证方式
uvbuilder 声明了运行时依赖uv(Dependencies() 返回["uv"]),因此执行环境需要预先安装 uv(例如在 CI 中通过pip install uv或官方安装脚本安装)。仓库的集成测试 TestBuild 展示了最小验证路径:先uv init --name proj --vcs none初始化一个标准 Python 项目,再分别以 wheel/sdist 模式构建,最后断言两个产物文件存在且 mtime 与mod_timestamp一致。
常见问题速查
| 现象 | 原因与对策 |
|---|---|
报错main is not used for uv | uv 项目没有“main 入口”概念,删除 build 中的main配置即可。 |
报错binary name is set by uv itself | wheel/sdist 文件名由 uv 命名规范决定,不要设置binary。 |
报错only target supported is 'none-any' | uv builder 不支持多 target 或自定义 target,保持targets为空(默认none-any)。 |
报错could not open pyproject.toml | dir指向的目录下缺少pyproject.toml,或dir配置有误。 |
报错invalid buildmode | buildmode只能取wheel或sdist。 |
| 想要同时产出 wheel 和 sdist | 声明两个 build(两个id),分别设置buildmode: wheel与buildmode: sdist。 |
| 日志提示 experimental | 该 builder 处于实验性阶段,属预期提示,不影响使用。 |
综上,builder: uv把 Python 打包纳入了 GoReleaser 统一的发布管线:一份.goreleaser.yaml同时管理 Go 二进制与 Python 发行包,产物共享签名、校验、容器化与上传能力。结合本文的配置说明与源码分析,你可以直接在自己的仓库中落地 wheel/sdist 的自动化发布。
- 开发工具
- CI/CD
- 构建工具
【免费下载链接】goreleaser
Release engineering, simplified
相关推荐
uv 包构建与发布实战:uv build、uv version 与 uv publish 完整工作流
uv 包构建与发布实战:uv build、uv version 与 uv publish 完整工作流 本文围绕 uv 官方指南 Building and pub
包管理器开发工具CLI发布前检查清单
发布前检查清单 所有测试通过 文档已更新 变更日志已更新 版本号已递增 包构建成功 本地安装测试通过 安全扫描无严重问题 元数据验证通过 认证配置正确 发布环境
包管理器开发工具CLIXGBoost Python 包打包详解:构建二进制 Wheel 与源码发行版(sdist)
XGBoost Python 包打包详解:构建二进制 Wheel 与源码发行版(sdist) 本文基于仓库中的 doc/contrib/python_packa
人工智能机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考