开源项目自动化发布到 PyPI:GitHub Actions 工作流实战与发布日志生成
1. 项目初始化与基本结构
2. 创建 GitHub 仓库
3. 设置 PyPI 凭据
4. 配置 GitHub Actions 工作流
5. 发布到 PyPI
6. 生成发布日志
7. 总结与最佳实践
在开源项目的维护过程中,持续集成和持续部署 (CI/CD) 至关重要。它能帮助我们自动化测试、构建、发布等流程,从而提高开发效率和代码质量。对于 Python 项目而言,PyPI (Python Package Index) 是官方的第三方软件包仓库,将项目发布到 PyPI 可以让更多的开发者方便地使用你的代码。本文将以一个模拟的开源项目为例,详细介绍如何使用 GitHub Actions 自动化发布 Python 包到 PyPI,并生成详细的发布日志,面向开源项目维护者,让发布流程更加高效、透明。
1. 项目初始化与基本结构
首先,我们需要创建一个 Python 项目,并设置好基本的项目结构。这里我们创建一个名为 my_package
的项目,其目录结构如下:
my_package/ ├── my_package/ │ ├── __init__.py │ └── my_module.py ├── tests/ │ ├── __init__.py │ └── test_my_module.py ├── setup.py ├── README.md ├── LICENSE └── .gitignore
my_package/
:存放项目源代码的目录。my_package/__init__.py
:将my_package
目录视为一个 Python 包。my_package/my_module.py
:一个简单的模块,包含一些函数或类。tests/
:存放测试代码的目录。tests/__init__.py
:将tests
目录视为一个 Python 包。tests/test_my_module.py
:包含针对my_module.py
的测试用例。setup.py
:用于构建、打包和安装项目的脚本。README.md
:项目的说明文档。LICENSE
:项目的许可证文件。.gitignore
:指定 Git 应该忽略的文件和目录。
my_package/my_module.py
示例:
# my_package/my_module.py def greet(name): """向指定的人打招呼。""" return f"Hello, {name}!"
tests/test_my_module.py
示例:
# tests/test_my_module.py import unittest from my_package import my_module class TestMyModule(unittest.TestCase): def test_greet(self): self.assertEqual(my_module.greet("World"), "Hello, World!") if __name__ == '__main__': unittest.main()
setup.py
示例:
# setup.py import setuptools with open("README.md", "r") as fh: long_description = fh.read() setuptools.setup( name="my-package-example", # 替换成你的项目名称 version="0.0.1", # 初始版本号 author="Your Name", # 替换成你的名字 author_email="your.email@example.com", # 替换成你的邮箱 description="A small example package", # 替换成你的项目描述 long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/your-username/my-package", # 替换成你的 GitHub 仓库地址 packages=setuptools.find_packages(), classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], python_requires='>=3.6', )
注意: setup.py
中的 name
字段必须是唯一的,否则无法成功发布到 PyPI。 建议使用 my-package-yourusername
这样的命名方式,避免与其他项目冲突。 替换 author
, author_email
和 url
为你自己的信息。
2. 创建 GitHub 仓库
将本地项目推送到 GitHub 仓库。如果没有 GitHub 账号,需要先注册一个。创建仓库后,按照 GitHub 提供的指引,将本地代码推送到远程仓库。
3. 设置 PyPI 凭据
为了让 GitHub Actions 能够自动发布包到 PyPI,我们需要在 GitHub 仓库中设置 PyPI 的凭据。有两种方式:
- 使用 PyPI API Token (推荐): 登录 PyPI,进入 “Account settings”,找到 “API tokens” 部分,创建一个新的 API token。设置 Token 的作用域为 “Entire account” 或 “Limited to a specific project”。 将 Token 添加到 GitHub 仓库的 Secrets 中,命名为
PYPI_API_TOKEN
。 - 使用 PyPI 用户名和密码 (不推荐): 虽然可以使用 PyPI 用户名和密码进行认证,但出于安全考虑,强烈建议使用 API Token。
进入 GitHub 仓库的 “Settings” -> “Secrets” -> “Actions”,点击 “New repository secret” 添加一个新的 Secret,Name 设置为 PYPI_API_TOKEN
,Value 设置为你的 PyPI API Token。
4. 配置 GitHub Actions 工作流
在项目根目录下创建 .github/workflows
目录,并在该目录下创建一个 YAML 文件,例如 python-publish.yml
,用于定义 GitHub Actions 工作流。
.github/workflows/python-publish.yml
示例:
# .github/workflows/python-publish.yml name: Publish Python Package on: release: types: [published] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.x uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install setuptools wheel twine - name: Build package run: python setup.py sdist bdist_wheel - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }}
配置详解:
name
:工作流的名称,显示在 GitHub Actions 页面上。on
:触发工作流的事件。这里设置为release
,并且types
为[published]
,表示当发布新的 Release 时触发该工作流。jobs
:定义一个或多个 Job。这里只定义了一个名为build
的 Job。runs-on
:指定 Job 运行的操作系统。这里设置为ubuntu-latest
,表示使用最新的 Ubuntu 系统。steps
:定义 Job 中的步骤。每个步骤执行一个特定的任务。actions/checkout@v3
:检出代码到工作流环境中。actions/setup-python@v4
:设置 Python 环境。python-version
设置为'3.x'
,表示使用 Python 3 的最新版本。Install dependencies
:安装构建和发布所需的依赖包,包括setuptools
、wheel
和twine
。Build package
:运行python setup.py sdist bdist_wheel
构建 Python 包。sdist
创建源码包,bdist_wheel
创建 wheel 包。pypa/gh-action-pypi-publish@release/v1
:使用 GitHub Actions 官方提供的 PyPI 发布 Action,将构建好的包发布到 PyPI。password
设置为${{ secrets.PYPI_API_TOKEN }}
,表示使用之前在 GitHub Secrets 中设置的 API Token。
5. 发布到 PyPI
当你在 GitHub 仓库中创建一个新的 Release 时,GitHub Actions 会自动触发 python-publish.yml
工作流,执行构建和发布操作。你可以在 GitHub 仓库的 “Actions” 页面上查看工作流的执行状态和日志。如果一切顺利,你的 Python 包就会被成功发布到 PyPI 上。
6. 生成发布日志
为了更好地记录和追踪每次发布的内容,我们需要生成详细的发布日志。这里我们可以使用 Git 的 commit history 和 tags 来生成发布日志。
修改 .github/workflows/python-publish.yml
:
# .github/workflows/python-publish.yml name: Publish Python Package on: release: types: [published] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 # 获取所有提交记录 - name: Set up Python 3.x uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install setuptools wheel twine - name: Build package run: python setup.py sdist bdist_wheel - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} - name: Generate Release Notes id: generate_release_notes run: | TAG_NAME=${GITHUB_REF#refs/*/} # 获取tag名称 PREVIOUS_TAG=$(git describe --abbrev=0 --tags $TAG_NAME^) echo "PREVIOUS_TAG=$PREVIOUS_TAG" >> $GITHUB_ENV echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV # 获取自上次发布以来的提交信息 CHANGELOG=$(git log --pretty=format:"- %s" ${PREVIOUS_TAG}..${TAG_NAME}) echo "changelog<<EOF" >> $GITHUB_ENV echo "$CHANGELOG" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Update Release with Changelog uses: actions/github-script@v6 with: script: | const github = require('@actions/github') const {PREVIOUS_TAG, TAG_NAME, changelog} = process.env const release_body = `## Release Notes for ${TAG_NAME}\n\nCommits since ${PREVIOUS_TAG}:\n${changelog}` const { owner, repo } = github.context.repo; const { data: release } = await github.getOctokit().rest.repos.getReleaseByTag({ owner, repo, tag: TAG_NAME, }); await github.getOctokit().rest.repos.updateRelease({ owner, repo, release_id: release.id, body: release_body });
修改详解:
fetch-depth: 0
: 在actions/checkout@v3
步骤中添加fetch-depth: 0
,确保获取完整的 Git 历史记录,以便后续生成发布日志。- Generate Release Notes: 添加一个名为
Generate Release Notes
的步骤,用于生成发布日志。- 使用
${GITHUB_REF#refs/*}
获取当前 Release 的 Tag 名称。 - 使用
git describe --abbrev=0 --tags $TAG_NAME^
获取上一个 Tag 名称。 - 使用
git log --pretty=format:"- %s" ${PREVIOUS_TAG}..${TAG_NAME}
获取自上次发布以来的所有提交信息,并格式化为 Markdown 列表。 - 将获取到的 Tag 名称、上一个 Tag 名称和提交信息存储到环境变量中,以便后续步骤使用。
- 使用
- Update Release with Changelog: 添加一个名为
Update Release with Changelog
的步骤,用于更新 GitHub Release 的描述信息。- 使用
actions/github-script@v6
Action,执行 JavaScript 代码。 - 从环境变量中获取 Tag 名称、上一个 Tag 名称和提交信息。
- 构建 Release 的描述信息,包括 Tag 名称和提交信息。
- 使用 GitHub API 获取当前 Release 的 ID。
- 使用 GitHub API 更新 Release 的描述信息。
- 使用
工作原理:
- 当发布一个新的 Release 时,GitHub Actions 会自动触发
python-publish.yml
工作流。 Generate Release Notes
步骤会获取当前 Release 的 Tag 名称、上一个 Tag 名称和提交信息,并将这些信息存储到环境变量中。Update Release with Changelog
步骤会从环境变量中获取 Tag 名称、上一个 Tag 名称和提交信息,构建 Release 的描述信息,并使用 GitHub API 更新 Release 的描述信息。
发布日志示例:
假设我们发布了 v0.0.2
版本,并且自 v0.0.1
版本以来有以下提交:
- Fix bug in greet function
- Add new feature: farewell function
- Update documentation
那么生成的发布日志如下:
## Release Notes for v0.0.2 Commits since v0.0.1: - Fix bug in greet function - Add new feature: farewell function - Update documentation
7. 总结与最佳实践
本文详细介绍了如何使用 GitHub Actions 自动化发布 Python 包到 PyPI,并生成详细的发布日志。通过使用 CI/CD,可以大大提高开发效率和代码质量。以下是一些最佳实践:
- 使用 API Token: 强烈建议使用 PyPI API Token 进行身份验证,而不是用户名和密码。
- 添加测试: 在发布之前,确保你的代码通过了所有测试。可以在 GitHub Actions 中添加测试步骤,例如使用
pytest
运行测试用例。 - 代码审查: 在合并代码之前,进行代码审查,以确保代码质量。
- 版本控制: 使用语义化版本控制 (Semantic Versioning),清晰地标识每个版本的功能和变更。
- 文档更新: 在发布新版本时,及时更新文档,以便用户了解最新的功能和用法。
- 自定义发布流程: 根据项目的实际情况,可以自定义发布流程,例如添加代码风格检查、安全扫描等步骤。
通过本文的学习,相信你已经掌握了如何使用 GitHub Actions 自动化发布 Python 包到 PyPI,并生成详细的发布日志。希望这些知识能帮助你更好地维护开源项目,提高开发效率和代码质量。