Skip to content

Commit c161557

Browse files
committed
Revamp release CI pipeline with TestPyPI support and strict checking of tags
1 parent fa3c575 commit c161557

File tree

4 files changed

+99
-40
lines changed

4 files changed

+99
-40
lines changed

.github/workflows/release.yml

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@ concurrency:
88
on:
99
push:
1010
tags:
11-
- '[0-9]*.[0-9]*.[0-9]*' # Push events to every tag that looks like a semver
12-
- '[0-9]*.[0-9]*.[0-9]*rc[0-9]*' # Push events to every tag that looks like a release candidate
11+
- '[0-9]+.[0-9]+.[0-9]+*' # Push events for official release tags
12+
- 'test-release/[0-9]+.[0-9]+.[0-9]+*' # Push events for test release tags
1313

1414
jobs:
15-
build:
15+
build-dist-artifacts:
1616
# This job uses vanilla Python tools rather than Poetry, so we don't have to use third party GitHub actions
1717
# e.g. pip, build, twine
1818
# If we even want to, we could switch to using something like actions/setup-poetry (but do a search for current
1919
# best implementations)
20-
name: Build distribution 📦
20+
name: Build distribution artifacts 📦
2121
runs-on: ubuntu-latest
2222

2323
steps:
24-
- name: Checkout repo
24+
- name: Checkout repository
2525
uses: actions/checkout@v4
2626

2727
- name: Install Python 🐍
2828
uses: actions/setup-python@v5
2929
with:
3030
python-version: '3.11'
3131

32-
- name: Install dependencies
32+
- name: Install project dependencies
3333
run: python -m pip install build twine
3434

3535
- name: Build wheel and source distribution
@@ -40,21 +40,24 @@ jobs:
4040
run: twine check dist/*
4141

4242
# Save ("upload") the distribution artifacts for use by downstream Actions jobs
43-
- name: Upload sdist artifacts 📦
43+
- name: Upload distribution artifacts 📦
4444
uses: actions/upload-artifact@v4 # This allows us to persist the dist directory after the job has completed
4545
with:
4646
name: python-package-distributions
4747
path: dist/
4848
if-no-files-found: error
4949

50-
publish-to-pypi:
51-
name: Upload release to PyPI
52-
if: startsWith(github.ref, 'refs/tags/') # Belt and suspenders, only ever publish based on a tag
53-
needs: build
50+
# Job that pushes dist artifacts to public PyPI for official release tags
51+
official-pypi-publish:
52+
name: Upload official release to PyPI
53+
# Prevent running on any PEP 440 suffixed tags or on test-release tags
54+
if: startsWith(github.ref, 'refs/tags/test-release') == false
55+
needs:
56+
- build-dist-artifacts
5457
runs-on: ubuntu-latest
5558
environment:
56-
name: pypi-publish
57-
url: https://pypi.org/p/space_packet_parser
59+
name: official-pypi-publish-environment
60+
url: https://pypi.org/p/space_packet_parser # Public PyPI
5861
permissions:
5962
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
6063

@@ -66,24 +69,52 @@ jobs:
6669
name: python-package-distributions
6770
path: dist/
6871

69-
- name: Publish distribution 📦 to PyPI
72+
- name: Publish distribution artifacts 📦 to PyPI
7073
uses: pypa/[email protected]
7174

75+
# Job that pushes dist artifacts to TestPyPI for test release tags
76+
# This will fail if the version (according to package metadata) has already been pushed
77+
test-pypi-publish:
78+
name: Upload testing release to TestPyPI
79+
# Only run on test-release tags
80+
if: startsWith(github.ref, 'refs/tags/test-release')
81+
needs:
82+
- build-dist-artifacts
83+
runs-on: ubuntu-latest
84+
environment:
85+
name: test-pypi-publish-environment
86+
url: https://test.pypi.org/p/space_packet_parser # TestPyPI
87+
permissions:
88+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
89+
90+
steps:
91+
# This downloads the build artifacts from the build job
92+
- name: Download all the dists 📦
93+
uses: actions/download-artifact@v4
94+
with:
95+
name: python-package-distributions
96+
path: dist/
97+
98+
- name: Publish distribution artifacts 📦 to TestPyPI
99+
uses: pypa/[email protected]
100+
with:
101+
repository-url: https://test.pypi.org/legacy/
102+
103+
# Job that publishes an official Release to GitHub after pushing to PyPI
104+
# This only runs if we have pushed to public PyPI (not TestPyPI)
72105
create-github-release:
73-
name: >-
74-
Sign the Python 🐍 distribution 📦 with Sigstore
75-
and upload them to GitHub Release
106+
name: Upload dist artifacts to GitHub Release
76107
needs:
77-
- publish-to-pypi
108+
- official-pypi-publish
78109
runs-on: ubuntu-latest
79110
environment:
80-
name: pypi-publish
111+
name: create-github-release-environment
81112
permissions:
82-
contents: write # IMPORTANT: mandatory for making GitHub Releases
83113
id-token: write # IMPORTANT: mandatory for sigstore
114+
contents: write # IMPORTANT: mandatory for making GitHub Releases
84115

85116
steps:
86-
- name: Download all the dists 📦
117+
- name: Download the artifacts 📦
87118
uses: actions/download-artifact@v4
88119
with:
89120
name: python-package-distributions
@@ -96,14 +127,15 @@ jobs:
96127
./dist/*.tar.gz
97128
./dist/*.whl
98129
99-
- name: Determine if it's a pre-release
100-
# Dynamically sets the --prerelease option passed to the release create CLI based on matching the *rc*
101-
# substring in the git tag. If rc not present, does not pass --prerelease to the CLI.
130+
- name: Determine if the release is a prerelease
131+
# Checks the regex form of the tag.
132+
# Marks final releases only for tags matching the regex (no version suffixes)
133+
# All other releases are marked as prereleases
102134
run: |
103-
if [[ "${{ github.ref_name }}" == *rc* ]]; then
104-
echo "PRE_RELEASE_OPTION=--prerelease" >> $GITHUB_ENV
135+
if [[ "${{ github.ref_name }}" =~ '^[0-9]*\.[0-9]*\.[0-9]*$' ]]; then
136+
echo "PRE_RELEASE_OPTION=''" >> $GITHUB_ENV # Not a prerelease
105137
else
106-
echo "PRE_RELEASE_OPTION=''" >> $GITHUB_ENV
138+
echo "PRE_RELEASE_OPTION='--prerelease'" >> $GITHUB_ENV # Is a prerelease
107139
fi
108140
109141
- name: Get latest non-prerelease release
@@ -125,9 +157,11 @@ jobs:
125157
# Uses the GitHub CLI to generate the Release and auto-generate the release notes. Also generates
126158
# the Release title based on the annotation on the git tag.
127159
run: >-
160+
RELEASE_NAME=$(basename "${{ github.ref_name }}")
128161
gh release create
129162
'${{ github.ref_name }}'
130163
--repo '${{ github.repository }}'
164+
--title "$RELEASE_NAME"
131165
${{ env.PRE_RELEASE_OPTION }}
132166
--generate-notes
133167
--notes-start-tag '${{ env.LATEST_RELEASE_TAG }}'

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cff-version: 1.2.0
22
title: 'space_packet_parser'
33
type: software
4-
version: '5.0.0rc6'
4+
version: '5.0.0rc9'
55
description: A CCSDS telemetry packet decoding library based on the XTCE packet format description standard.
66
license: BSD-3-Clause
77
abstract: The Space Packet Parser Python library is a generalized, configurable packet decoding library for CCSDS telemetry

docs/source/developers.md

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ Feel free to fork this repo and submit a PR!
3535
## Release Process
3636
Releases are automatically created using a GitHub Actions workflow that responds to pushes of annotated git tags.
3737

38+
### Versioning
39+
Version numbers must be PEP440 strings: https://peps.python.org/pep-0440/
40+
41+
That is,
42+
```
43+
[N!]N(.N)*[{a|b|rc}N][.postN][.devN]
44+
```
45+
3846
### Preparing for Release
3947
1. Create a release candidate branch named according to the version to be released. This branch is used to polish
4048
the release but is fundamentally not different from any other feature branch in trunk-based development.
@@ -59,26 +67,43 @@ Releases are automatically created using a GitHub Actions workflow that responds
5967

6068
### Automatic Release Process
6169
GitHub Actions has an automatic release process that responds to pushes of annotated git tags. When a tag matching
62-
a semantic version (`[0-9]*.[0-9]*.[0-9]*` or `[0-9]*.[0-9]*.[0-9]*rc[0-9]*`) is pushed, a workflow runs that builds
63-
the package, pushes the artifacts to PyPI, and creates a GitHub Release from the distributed artifacts. Release notes
64-
are automatically generated from commit history and the Release name is taken from the annotation on the tag.
70+
a semantic version (`[0-9]+.[0-9]+.[0-9]+*` or `test-release/[0-9]+.[0-9]+.[0-9]+*`) is pushed,
71+
a workflow runs that builds the package, pushes the artifacts to PyPI or TestPyPI,
72+
and creates a GitHub Release from the distributed artifacts. Release notes
73+
are automatically generated from commit history and the Release name is taken from the tag.
74+
75+
#### Official Releases
76+
Official releases are published to the public PyPI (even if they are release candidates like `1.2.3rc1`). This differs
77+
from test releases, which are only published to TestPyPI and are not published to GitHub at all.
78+
If the semantic version has any suffixes (e.g. `rc1`), the release will be marked as
79+
a prerelease in GitHub and PyPI.
6580

66-
To trigger a release, push a tag reference to the commit you want to release, like so:
81+
To trigger an official release, push a tag referencing the commit you want to release. The commit _MUST_ be on
82+
the `main` branch. Never publish an official release from a commit that hasn't been merged to `main`!
6783

6884
```bash
85+
git checkout main
86+
git pull
6987
git tag -a X.Y.Z -m "Version X.Y.Z"
7088
git push origin X.Y.Z
7189
```
7290

73-
To tag and publish a Release Candidate, your tag should look like the following:
91+
#### Test Releases
92+
Test releases are published to TestPyPI only. Official releases are also published to TestPyPI.
93+
94+
To publish a test release, prefix the tag with `test-release`. This will prevent any publishing to the public PyPI
95+
and will prevent the artifacts being published on GitHub.
7496

7597
```bash
76-
git tag -a X.Y.Zrc1 -m "Release Candidate X.Y.Zrc1"
77-
git push origin X.Y.Zrc1
98+
git checkout <ref-to-test-release-from>
99+
git pull
100+
git tag -a test-release/X.Y.Zrc1 -m "Test Release Candidate X.Y.Zrc1"
101+
git push origin test-release/X.Y.Zrc1
78102
```
79103

80-
Release candidate tags are always marked as Prereleases in GitHub and release notes are generated from the latest
81-
non-prerelease Release.
104+
#### Prereleases
105+
Unless the pushed tag matches the regex `^[0-9]*\.[0-9]*\.[0-9]*`, the release will be marked as a
106+
prerelease in GitHub. This allows "official" prereleases of suffixed tags.
82107

83-
**For production releases, tags should always reference commits in the `main` branch. Release candidates are less
84-
important and tags can reference any commit.**
108+
#### Release Notes Generation
109+
Release notes are generated based on commit messages since the latest non-prerelease Release.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "space_packet_parser"
3-
version = "5.0.0rc6"
3+
version = "5.0.0rc9"
44
description = "A CCSDS telemetry packet decoding library based on the XTCE packet format description standard."
55
license = "BSD-3-Clause"
66
readme = "README.md"

0 commit comments

Comments
 (0)