gitlab ci中python包构建必须严格限定在打tag时触发,使用only: - tags规则;基础镜像应选python:3.11并安装build/twine;pypi上传需配置掩码变量pypi_api_token,且版本号须与pyproject.toml一致。

GitLab CI里怎么触发Python包构建
关键不是“写个脚本就行”,而是让.gitlab-ci.yml在正确条件下自动运行构建流程。默认情况下,GitLab CI只响应push和merge_request事件,但发布PyPI包必须严格限定在打tag时——否则每次提交都尝试上传,会失败或污染仓库。
所以第一件事是加only规则,只让tags触发构建:
build-wheel:
stage: build
only:
- tags
别用branches: [main]或不写only,否则pip install .可能成功,但twine upload会因重复版本被PyPI拒绝。
-
tags匹配的是 Git tag(如v0.1.0),不是分支名 - 确保tag名和
pyproject.toml或setup.py里声明的version一致,否则build生成的wheel文件名和上传时用的版本号对不上 - 如果想支持预发布(如
v0.2.0a1),PyPI允许,但需确认twine没加--skip-existing之类跳过逻辑
构建Python包该用什么基础镜像和工具链
别直接用python:3.11-slim就开干。PyPI上传要求wheel必须带正确的ABI标签(比如cp311),而slim镜像缺编译工具,pip wheel --no-deps --no-cache-dir .可能退化成源码分发(.tar.gz),丧失二进制兼容性优势。
推荐明确使用带build-essential和python-dev的镜像:
image: python:3.11
然后在before_script里装build和twine:
before_script: - pip install --upgrade pip build twine
-
build比setuptools+wheel更现代,自动处理PEP 517构建后端,且默认生成manylinux兼容wheel(只要不调用C扩展) - 避免用
python -m pip wheel,它不读pyproject.toml里的[build-system]配置,容易漏掉依赖 - 若项目含C扩展(如用
cffi或pybind11),得换quay.io/pypa/manylinux2014_x86_64镜像,并挂载/opt/python多版本环境
如何安全地把API token传给twine upload
twine upload不能硬编码token,也不能放.pypirc明文文件里。GitLab CI提供CI_JOB_TOKEN和自定义变量两种方式,但PyPI只认API token,且必须以pypi-开头。
正确做法是:在GitLab项目设置 → CI/CD → Variables里新增变量:
- Key:
PYPI_API_TOKEN - Value:
pypi-xxxxx...(从https://pypi.org/manage/account/token/生成) - 勾选“Mask variable”和“Protect variable”
然后在job里用:
script: - twine upload --repository pypi dist/*.whl -u __token__ -p $PYPI_API_TOKEN
注意三点:
-
-u __token__是PyPI强制要求的用户名,不能写__token__以外的值 -
$PYPI_API_TOKEN会被GitLab自动注入,且不会出现在CI日志中(因启用了Mask) - 别用
twine upload --config-file指向含token的文件——GitLab CI变量无法跨job传递,build和upload通常是两个job,文件路径也不安全
为什么上传后PyPI上看不到新版本
最常见原因是dist/目录下根本没有生成wheel文件,或者生成了但twine upload命令没匹配到路径。CI日志里出现No files found in dist/或403 Forbidden(token无效)只是表象。
排查顺序很实际:
- 先确认
build阶段是否真执行了python -m build,看日志里有没有creating 'dist/xxx-py3-none-any.whl' - 检查
twine upload前加ls -la dist/,确保wheel存在且权限可读 - 如果wheel名含
linux_x86_64但你用Mac开发,说明本地构建环境污染了CI——CI必须完全依赖.gitlab-ci.yml定义的镜像,不能靠本地build产物 - PyPI对重复版本零容忍:
v0.1.0一旦上传,再推同名tag会返回400 Bad Request,此时只能删tag重打,或升版(如v0.1.1)
真正的难点不在语法,而在版本号、构建环境、token作用域三者必须咬合。漏掉任意一环,CI看起来跑通了,但PyPI上永远空着。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











