I have been using GitHub actions for a while and they are great! With them you can define your CI/CD along your code and review any changes to CI/CD through your normal PR processes. I’m intending to write more about great workflows and best practices, especially for Python packages, although that will be in a future post. This post addresses a pain point I have had and you may have had as well. Specifically, in PRs, it is possible on GitHub to enforce things like approvals from reviewers before being able to merge. GitHub also has a setting to require status checks to pass before being able to merge, although you have to define exactly which status checks you want to pass. I usually find the names of my status checks change a lot, especially if I’m using matrix strategies for multiple Python versions or package versions. Below we discuss a simple technique to reduce how often you have to update the required status checks!
The solution is relatively straight forward. My workflows usually consist of something like:
name: CI-CD
on:...
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- ...
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
steps:
- ...
release:
runs-on: ubuntu-latest
if: ...
needs:
- test
- lint
steps:
- ...
Usually, PRs only run the lint
and test
jobs. The name of the test jobs end up being combined with the matrix strategy to something like Test (3.11)
, Test (3.10)
, …, which means that whenever I add something new to the matrix, such as a new Python version or a package, I also have to go and update the required status checks before merging with those new combinations. If your matrix becomes complicated, this can get quite extensive and difficult to maintain, which has meant that I usually don’t bother with it. What I now do instead is I just add a new job called required_status_checks
that depends on all the jobs that I want to pass before being able to merge a PR and making that job the required status check:
name: CI-CD
on:...
jobs:
lint: ...
test: ...
required_status_checks:
name: Required Status Checks
runs-on: ubuntu-latest
if: always()
needs:
- test
- lint
steps:
- run: |
[ '${{ needs.test.result }}' = 'success' ] || (echo test failed && false)
[ '${{ needs.lint.result }}' = 'success' ] || (echo lint failed && false)
release: ...
This way, if a new required job is added, it just needs to be added to the needs
list. Since PRs usually also require at least 1 approval from a reviewer, they can spot any changes to the required_status_checks
job that would interfere with the required status checks, such as test
being removed. Now you can kiss goodbye to endless updates of the required status checks!