目录
GitHub Actions:简介
GH100 科学 Python 项目推荐的 CI 是 GitHub Actions (GHA),尽管其前身 Azure 也被广泛使用,并且在一些软件包中可能会发现其他流行的服务(Travis、Appveyor 和 Circle CI)。GHA 由于其灵活、可扩展的设计以及与 GitHub 权限模型(和 UI)的紧密集成而成为首选。以下是如何使用 GHA 设置新软件包的指南。
GHA 由 工作流 组成,工作流由操作组成。以下是一些您可能希望在软件包中使用的工作流。这些应该放在名为 .github/workflows/main.yml
或类似的文件中。
头部
您的主要 CI 工作流文件应该以类似以下的方式开头
name: CI
on:
pull_request:
push:
branches:
- main
jobs:
这为工作流提供了良好的名称 GH101,并定义了其运行条件。这将在所有拉取请求或对主分支的推送上运行。如果您使用开发分支,您可能希望将其包含在内。您还可以为拉取请求指定特定分支,而不是在所有 PR 上运行(仅在针对这些分支的 PR 上运行)。
提交前
如果您使用 pre-commit(您应该使用),并且您还不想/不能使用 pre-commit.ci,那么这是一个将为您检查 pre-commit 的作业
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.x"
- uses: pre-commit/action@v3.0.1
如果您确实使用了 pre-commit.ci,但您需要此作业运行手动检查,例如 check-manifest,那么您可以保留它,但只需使用 with: extra_args: --all-files --hook-stage manual check-manifest
来运行此检查。您也可以在其他作业中使用 needs: lint
来防止 lint 检查未通过时运行它们。
单元测试
实现单元测试也很容易。由于您应该遵循前面部分列出的最佳实践,因此无论软件包详细信息如何,这都变成了一个几乎可以直接复制粘贴的公式。您可能需要调整 Python 版本以满足您的口味;如果您愿意,您也可以在不同的操作系统上进行测试,方法是将它们添加到矩阵中并将其输入到 runs-on
中。
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version:
- "3.9"
- "3.11"
- "3.13"
name: Check Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Only needed if using setuptools-scm
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install package
run: python -m pip install -e .[test]
- name: Test package
run: python -m pytest
上面需要注意的一些事项
矩阵应包含您感兴趣的版本。如果您正在构建任何扩展或担心软件包在 macOS 或 Windows 上的情况,您也可以在其他操作系统上进行测试。快速失败是可选的。
此处用于安装的公式对于所有用户都应该相同;并且使用 PEP 517/518 构建,您甚至可以保证会生成与构建最终软件包时相同的、一致的轮子。
更新
GH200 GH210 如果您在存储库中使用非默认操作(您将在以下页面中看到一些),那么最好保持它们的最新状态。GitHub 提供了一种使用 dependabot 来做到这一点的方法。只需将以下文件添加为 .github/dependabot.yml
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns:
- "*"
这将每周检查操作是否有更新,并在有更新时创建一个 PR,包括 PR 中的变更日志和提交摘要。如果您选择一个像 v1
这样的名称,这应该只查找相同形式的更新(从 2022 年 4 月开始)——不再需要限制“移动标签”更新 GH211。您也可以使用 SHA,dependabot 也会尊重它。并且 groups
将组合操作更新 GH212,这既更简洁,有时也需要依赖操作,例如 upload-artifact
/download-artifact
。
您也可以将其用于其他生态系统,包括 Python。
常见需求
单个操作系统步骤
如果需要仅在特定操作系统上运行步骤,请使用该步骤上的 if 和 runner.os
if: runner.os != 'Windows' # also 'macOS' and 'Linux'
使用 runner.os
比 matrix.<something>
更好。您还有一个环境变量 $RUNNER_OS
。此处需要单引号。
更改步骤中的环境
如果需要更改后续步骤的环境变量,例如将 if 条件与仅适用于一个操作系统的条件组合,则将其添加到一个特殊文件中
- run: echo "MY_VAR=1" >> $GITHUB_ENV
后续步骤将看到此环境变量。
步骤之间通信
您还可以通过设置 id:
来直接在步骤之间进行通信。一些操作有输出,并且 bash 操作可以手动写入输出
- id: someid
run: echo "something=true" >> $GITHUB_OUTPUT
您现在可以使用 ${{ steps.someid.something }}
在后续步骤中引用此步骤。您也可以使用 ${{ needs.<jobname>.outputs.something }}
从另一个作业中获取它。 toJson()
函数对于输入 JSON 很有用——您甚至可以以此方式动态生成矩阵!
美观输出
您可以将 GitHub 风格的 Markdown 写入 $GITHUB_STEP_SUMMARY
,它将显示在摘要页面上。
您也可以输出注释;这些注释在 PR 中的代码中内联显示。这可以通过 设置特殊的双冒号输出 来完成,例如 echo "::error file=app.js,line=1::Missing semicolon"
。有关使用 pytest 执行此操作的插件,请参阅 pytest-github-actions-annotate-failures。
您也可以通过 提供匹配器 来执行此操作,匹配器会告诉 GitHub 查找某些模式。请记住,每个类型每个步骤最多只能看到 10 个匹配项,总共 50 个匹配器。
常用有用操作
有各种有用的操作。有 GitHub 提供的操作
- actions/checkout:几乎始终是第一个操作。v2+ 不会保留 Git 历史记录,除非包含
with: fetch-depth: 0
(对于 SCM 版本控制很重要)。v1 在非常旧的 docker 镜像上运行。 - actions/setup-python:v4+ 需要选择 Python 版本(但是
"3.x"
有效),还支持使用范围和allow-prereleases
的多个版本。 - actions/cache:可以存储文件并在将来的运行中恢复它们,并具有可设置的密钥。
- actions/upload-artifact:上传一个文件,以便从 UI 或后续作业中访问。
- actions/download-artifact:下载以前上传的文件,通常用于发布。匹配 upload-artifact 版本。
- actions/labeler:向 PR 等添加标签。
- actions/stale:将旧的问题/PR 标记为过时。
- actions/upload-pages-artifact、actions/configure-pages 和 actions/deploy-pages:提供直接部署到 GitHub Pages 的功能。请参阅本页面后面的指南。
以及许多其他有用的操作
- ilammy/msvc-dev-cmd:设置 MSVC 编译器。
- jwlawson/actions-setup-cmake:在几乎所有镜像上设置任何版本的 CMake。
- wntrblm/nox:设置所有版本的 Python 并提供 nox。
- pre-commit/action:使用内置缓存运行 pre-commit。
- conda-incubator/setup-miniconda:在 GitHub Actions 上设置 conda 或 mamba。
- mamba-org/setup-micromamba:在 GitHub Actions 上设置 micromamba。比 conda/mamba 更快、更简单,但 API 略有不同。
- prefix-dev/setup-pixi:设置 pixi 并安装您的环境。
- ruby/setup-ruby:如果需要,设置 Ruby。
- peter-evans/create-pull-request:使用当前更改创建新的 PR(比仅使用
gh
提供更多选项)。您甚至可以使用run: gh pr merge --merge --auto "1"
之后自动合并 PR。 - astral-sh/setup-uv:设置
uv
。经典 Python 打包解决方案的超快速替代方案。 - gautamkrishnar/keepalive-workflow:使 GitHub Actions 保持活动状态超过 60 天。如果您已在此处设置了其他建议(例如 dependabot 和 pre-commit),通常不需要这样做。
Python 开发人员还有几个;请注意,这些没有像官方操作和大多数其他操作那样提供 vX
移动标签,而是具有您可以使用的 release/vX
分支。
- pypa/gh-action-pypi-publish:将 Python 包发布到 PyPI。支持受信任的发布者部署。
- re-actors/alls-green:用于检查所有作业是否都已通过的工具(也支持允许失败)。
- sigstore/gh-action-sigstore-python:使用 sigstore 在 GitHub Actions 中签署文件。
还安装了一些有用的工具,可以真正简化您的工作流程或添加自定义操作。这包括系统包管理器(如 brew、chocolaty、NuGet、Vcpkg 等),以及一个很棒的跨平台工具
- pipx:所有运行器上都预安装了此工具(GitHub 用于设置其他内容),并且会保持最新状态。它使您能够使用
pipx run <app>
一行代码使用任何 PyPI 应用程序。
您也可以在本地运行 GitHub Actions
- act:在本地 Docker 镜像中运行 GitHub Actions。
高级用法
以下是一些您可能需要的内容。
取消现有运行
GH102 如果添加以下内容,您可以确保每次 PR/分支只运行一次,并在新的运行开始时取消旧的运行
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
任何具有匹配组名称的内容都将计入同一组 - ref 是 PR 的“来自”名称。如果需要,您可以将 github.ref
替换为 github.event.pull_request.number || github.sha
;这仍然会在 PR 推送时取消,但会在 main
上构建每个提交。
传递作业
如果希望支持 GitHub 的“通过时合并”功能,则应设置传递作业,而不是列出您想要要求的每个作业。除了使添加和删除作业变得更容易之外,它还意味着添加新的必需作业不会使您过去已合并的所有 PR 从绿色复选标记更改为橙色“挂起”符号(因为有一些它们未通过的新要求)。
例如,如果您有 lint
和 checks
作业,请使用以下代码
pass:
if: always()
needs: [lint, checks]
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
我们希望作业始终运行,因此我们设置 if: always()
。否则,如果它依赖的任何作业被跳过,它可能会被跳过,并且跳过的作业会被计为 GitHub 的自动合并的“通过”(糟糕!)。作业的重要部分是 needs:
列表;这告诉它需要什么。
我们使用 re-actors/alls-green
来评估必需的作业是否已通过。您需要告诉它哪些作业是必需的,您可以通过获取 needs
列表并将其作为 json 输入到 with: jobs:
中来实现,而无需重复 needs 列表。
这也将支持允许失败的作业(allowed-failures:
)和允许跳过的作业(allowed-skips:
)。
只需在主分支的必需检查中设置此 pass
作业。然后您将能够使用 GitHub 的自动合并功能。
自定义操作
您可以在本地或在共享的 GitHub 存储库中编写自己的操作,可以使用 GitHub Actions 语法本身(称为“复合”)、JavaScript 或 Docker。结合 pipx,复合操作非常容易编写!
要创建自定义操作,可以将其放在 .github/actions
中(仅供该存储库工作流程内部使用),或者如果要允许其他人将您的存储库用作操作,则放在 action.yml
中。文件开头如下所示
name: <some name>
description: <Some description>
您还可以设置输入,用户将在 with:
中放置这些输入
inputs:
some-input:
description: <Some description>
required: true
然后,您指定该操作是复合的,并为其提供要运行的步骤
runs:
using: composite
steps:
如果指定了 runs:
步骤,则必须指定 shell: <something>
。否则,它基本上与您习惯的相同;您可以使用 if:
等。
一个常见的用例是使用 Python。除非它是您操作的重点,否则理想情况下您不应该更改用户的环境;突然更改活动 Python 版本可能会令人意外。但是,您可以使用 setup-python
和 pipx
中的 update-environment: false
来实现。
- uses: actions/setup-python@v5
id: python
with:
python-version: "3.11"
update-environment: false
- name: Run some local program
shell: bash
run:
pipx run --python '${{ steps.python.outputs.python-path }}' '${{
github.action_path }}' ${{ inputs.some-input }}
您可以使用 setup-python
中的 python-path
输出获取您激活的 Python。您可以使用 github.action_path
获取检出的操作的路径。
自定义复合操作的示例包括
可重用工作流程
您还可以创建可重用的工作流程。这样做的一个原因是它允许您使用 needs
或在工作流程之间传递值。这是一种使一个工作流程(可以包含多个作业,甚至矩阵)依赖于另一个工作流程的简单方法。
要使用可重用的工作流程,请将触发器替换为
on:
workflow_call:
如果将 outputs:
表添加到工作流程调用表中,则可以指定其他工作流程读取的输出。请参阅文档中的其他选项。
条件工作流程
有时,您拥有的作业取决于存储库中的某些文件。也许您只希望在代码或测试文件更改时运行测试,在文档更改时运行文档等。虽然 GitHub 允许您在触发器中指定文件,但它与必需检查或跨多个工作流程的使用配合不佳。以下是一种与这些内容配合良好的设置方法
将您的工作流程编写为可重用的工作流程。这意味着它们以允许其他工作流程调用的触发器开头
# reusable-tests.yml, for example
on:
workflow_call:
否则,它们看起来像普通的工作流程。然后,您需要另一个可重用的工作流程文件来决定何时运行特定情况。
# reusable-change-detection.yml
on:
workflow_call:
outputs:
run-tests:
value: ${{ jobs.change-detection.outputs.run-tests || false }}
# More here if you have more situations to detect
您首先在运行此文件时指定输出。您需要为每个要检测的情况创建一个输出。该值将从我们下面的 change-detection
作业输出,如果我们没有输出任何内容,则默认为“false”。
现在,我们需要我们的作业
jobs:
change-detection:
runs-on: ubuntu-latest
outputs:
run-tests: ${{ steps.cookie-changes.outputs.run-tests || false }}
# more here if you have more situations to detect
steps:
- uses: actions/checkout@v4
- name: Changed test-related files
if: github.event_name == 'pull_request'
id: changed-tests-files
uses: Ana06/get-changed-files@v2.3.0
with:
format: "json"
filter: |
tests/**
src/**.py
.github/workflows/ci.yml
.github/workflows/reusable-tests.yml
- name: Set a flag for running the tests
if: >-
github.event_name != 'pull_request' ||
steps.changed-tests-files.outputs.added_modified_renamed != '[]'
id: tests-changes
run: echo "run-tests=true" >> "${GITHUB_OUTPUT}"
# Add 2 more steps per situation you have to detect
这有点样板代码(主要是在传递变量),但它所做的事情相当简单。与其逐步介绍它,不如看看它试图做什么。首先,您需要找到当前 PR 中所有已更改文件的列表。这是使用 Ana06/get-changed-files
完成的(它不提供 v2
或 v2.2
移动标签)。然后使用 filter:
过滤该列表。它以 json
格式返回(否则您将无法支持文件名中包含空格)。返回的列表实际上并不重要;在下一步中,我们只需检查它是否为空(json 中的 []
)。如果我们不在 PR 中或存在返回的文件,则设置 run-tests=true
;否则,我们不设置(如果我们在 PR 中且没有匹配项)。
作业中的其他所有内容都与将步骤 changed-tests-files
的输出获取到 tests-changes
,然后从那里输入到可重用工作流程输出作为 run-tests
有关。
可能有人可以编写一个操作(甚至可以使用 gh
或 shell: python
的复合操作),可以直接报告 true/false 更改而不是文件列表,从而节省两步过程并大大简化此操作。
如果有多种情况,只需使用不同的 id
和输入重复这两个步骤。
最后,您编写结合了可重用工作流程的总体 CI 工作流程,例如 ci.yml
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
jobs:
change-detection:
uses: ./.github/workflows/reusable-change-detection.yml
tests:
needs: change-detection
if: fromJSON(needs.change-detection.outputs.run-tests)
uses: ./.github/workflows/reusable-tests.yml
# more here if you need more
pass:
if: always()
needs:
- change-detection
- tests
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1
with:
allowed-skips: >-
${{
fromJSON(needs.change-detection.outputs.run-tests)
&& ''
|| '
tests,
'
}}
jobs: ${{ toJSON(needs) }}
如果有多种情况,请在第一个之后添加另一个 ${{ ... }}
,并将它们添加到 needs 列表中。这实际上只是在“tests”作业被跳过时将“tests”注入 allowed-skips
中。
使用此方法的一些存储库示例包括
GitHub Pages
GitHub 已完成将其页面构建基础设施迁移到 Actions 的工作,并且他们现在提供直接从 Actions 推送到 Pages 的功能。这取代了旧的解决方法(强制)将输出推送到分支或单独的存储库。
在开始之前,请确保在 Pages 设置中将源设置为“Actions”。
您可能希望此作业在您的主分支以及 workflow_dispatch
上运行,以防您想手动触发重建。您应该设置权限,以便内置的 GITHUB_TOKEN
可以写入页面
permissions:
contents: read
pages: write
id-token: write
GH103 您可能每次只希望部署一次,因此您可以使用
concurrency:
group: "pages"
cancel-in-progress: true
现在您需要在 steps:
中使用三个自定义操作。首先,您需要配置 Pages。
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
注意此操作设置了一个id:
;这将允许您稍后使用此操作的输出;具体来说,在构建时可能需要使用${{ steps.pages.outputs.base_path }}
(您也可以获取origin
、base_url
或host
- 请参阅操作配置)。
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
此操作默认为上传_site
,但如果需要,您可以提供任何with: path:
,包括"."
,即整个仓库。
最后,您需要将工件(名为github-pages
)部署到 Pages。您可以将其设置为一个自定义作业,使用needs:
指向您之前的作业(在本例中,之前的作业称为build
)。
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: [build]
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
deploy-pages 作业提供一个page_url
,它与配置步骤中的base_url
相同,并且可以在environment
中设置。如果您想在一个作业中完成所有操作,则只需要其中一个。
请参阅官方入门工作流以获取示例。其他一些示例包括
变更日志生成
不是 Actions 的直接一部分,但也在.github
中是.github/release.yml
,它允许您在发布时[配置变更日志生成][gh-changelog]按钮。以下配置将为您删除 dependabot 和 pre-commit-ci PR。
changelog:
exclude:
authors:
- dependabot
- pre-commit-ci
[gh-changelog]: https://githubdocs.cn/en/repositories/releasing-projects-on-github/automatically-generated-release-notes