Skip to content

Commit 8fb06fa

Browse files
committed
test: refactor CI
per request from #1432 (comment) closes #1366 ### Changes - remove all test jobs from test.yml workflow except the one that runs on s390x platform. - rename the test.yml Display name accordingly: "Tests (s390x)" - skip building ppc64le wheels when not triggered on master branch. - reconfigure wheels.yml workflow triggers. The wheels.yml CI now runs for 1. any push to master branch 2. any change in a PR that targets master branch (excluding when PR changes only affect docs/ path) 3. any tag (starting with "v") is pushed - added `skip-existing` param in case deployment to PyPI suffers a network error and the CI just needs to be re-run. - cherry pick changes from PR #1366 (about CI triggers for lint and spell-check CI workflows) - auto-cancel wheels CI if new run triggered on non-default branch - use pytest.ini in cibuildwheel isolated env - added step to job that deploys wheels to PyPI. This new step creates a GitHub Release for the tag that was pushed. Uses release notes extracted from CHANGELOG.md (during sdist job).
1 parent d16e2f3 commit 8fb06fa

File tree

6 files changed

+147
-51
lines changed

6 files changed

+147
-51
lines changed

.github/workflows/codespell.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ name: Codespell
44

55
on:
66
pull_request:
7+
branches: [master]
78
push:
9+
branches: [master]
810

911
permissions:
1012
contents: read

.github/workflows/lint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ name: Lints
22

33
on:
44
pull_request:
5+
branches: [master]
56
push:
7+
branches: [master]
68
paths-ignore:
79
- '**.rst'
810

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Parse the latest release notes from CHANGELOG.md.
2+
3+
If running in GitHub Actions, set the `release_title` output
4+
variable for use in subsequent step(s).
5+
6+
If running in CI, write the release notes to ReleaseNotes.md
7+
for upload as an artifact.
8+
9+
Otherwise, print the release title and notes to stdout.
10+
"""
11+
12+
import re
13+
import subprocess
14+
from os import environ
15+
from pathlib import Path
16+
17+
18+
class ChangesEntry:
19+
def __init__(self, version: str, notes: str) -> None:
20+
self.version = version
21+
title = notes.splitlines()[0]
22+
self.title = f'{version} {title}'
23+
self.notes = notes[len(title) :].strip()
24+
25+
26+
H1 = re.compile(r'^# (\d+\.\d+\.\d+)', re.MULTILINE)
27+
28+
29+
def parse_changelog() -> list[ChangesEntry]:
30+
changelog = Path('CHANGELOG.md').read_text(encoding='utf-8')
31+
parsed = H1.split(changelog) # may result in a blank line at index 0
32+
if not parsed[0]: # leading entry is a blank line due to re.split() implementation
33+
parsed = parsed[1:]
34+
assert len(parsed) % 2 == 0, (
35+
'Malformed CHANGELOG.md; Entries expected to start with "# x.y.x"'
36+
)
37+
38+
changes: list[ChangesEntry] = []
39+
for i in range(0, len(parsed), 2):
40+
version = parsed[i]
41+
notes = parsed[i + 1].strip()
42+
changes.append(ChangesEntry(version, notes))
43+
return changes
44+
45+
46+
def get_version_tag() -> str | None:
47+
if 'GITHUB_REF' in environ: # for use in GitHub Actions
48+
git_ref = environ['GITHUB_REF']
49+
else: # for local use
50+
git_out = subprocess.run(
51+
['git', 'rev-parse', '--symbolic-full-name', 'HEAD'],
52+
capture_output=True,
53+
text=True,
54+
check=True,
55+
)
56+
git_ref = git_out.stdout.strip()
57+
version: str | None = None
58+
if git_ref and git_ref.startswith('refs/tags/'):
59+
version = git_ref[len('refs/tags/') :].lstrip('v')
60+
else:
61+
print(
62+
f"Using latest CHANGELOG.md entry because the git ref '{git_ref}' is not a tag."
63+
)
64+
return version
65+
66+
67+
def get_entry(changes: list[ChangesEntry], version: str | None) -> ChangesEntry:
68+
latest = changes[0]
69+
if version is not None:
70+
for entry in changes:
71+
if entry.version == version:
72+
latest = entry
73+
break
74+
else:
75+
raise ValueError(f'No changelog entry found for version {version}')
76+
return latest
77+
78+
79+
def main() -> None:
80+
changes = parse_changelog()
81+
version = get_version_tag()
82+
latest = get_entry(changes=changes, version=version)
83+
if 'GITHUB_OUTPUT' in environ:
84+
with Path(environ['GITHUB_OUTPUT']).open('a') as gh_out:
85+
print(f'release_title={latest.title}', file=gh_out)
86+
if environ.get('CI', 'false') == 'true':
87+
Path('ReleaseNotes.md').write_text(latest.notes, encoding='utf-8')
88+
else:
89+
print('Release notes:')
90+
print(f'# {latest.title}\n{latest.notes}')
91+
92+
93+
if __name__ == '__main__':
94+
main()

.github/workflows/tests.yml

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,14 @@
1-
name: Tests
1+
name: Tests (s390x)
22

33
on:
44
pull_request:
5+
branches: [master]
56
push:
7+
branches: [master]
68
paths-ignore:
79
- '**.rst'
810

911
jobs:
10-
linux:
11-
runs-on: ${{ matrix.os }}
12-
strategy:
13-
matrix:
14-
include:
15-
- os: ubuntu-24.04
16-
python-version: '3.10'
17-
- os: ubuntu-24.04
18-
python-version: '3.13'
19-
- os: ubuntu-24.04
20-
python-version: 'pypy3.10'
21-
- os: ubuntu-24.04-arm
22-
python-version: '3.13'
23-
24-
steps:
25-
- name: Checkout pygit2
26-
uses: actions/checkout@v5
27-
28-
- name: Set up Python
29-
uses: actions/setup-python@v6
30-
with:
31-
python-version: ${{ matrix.python-version }}
32-
33-
- name: Linux
34-
run: |
35-
sudo apt install tinyproxy
36-
LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh test
37-
3812
linux-s390x:
3913
runs-on: ubuntu-24.04
4014
if: github.ref == 'refs/heads/master'
@@ -53,19 +27,3 @@ jobs:
5327
run: |
5428
LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh test
5529
continue-on-error: true # Tests are expected to fail, see issue #812
56-
57-
macos-arm64:
58-
runs-on: macos-latest
59-
steps:
60-
- name: Checkout pygit2
61-
uses: actions/checkout@v5
62-
63-
- name: Set up Python
64-
uses: actions/setup-python@v6
65-
with:
66-
python-version: '3.13'
67-
68-
- name: macOS
69-
run: |
70-
export OPENSSL_PREFIX=`brew --prefix openssl@3`
71-
LIBSSH2_VERSION=1.11.1 LIBGIT2_VERSION=1.9.1 /bin/sh build.sh test

.github/workflows/wheels.yml

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
name: Wheels
22

3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}
5+
cancel-in-progress: ${{ github.ref_name != 'master' }}
6+
37
on:
48
push:
5-
branches:
6-
- master
7-
- wheels-*
9+
branches: [master]
810
tags:
9-
- 'v*'
11+
- 'v*'
12+
pull_request:
13+
branches: [master]
14+
paths-ignore:
15+
- 'docs/**'
1016

1117
jobs:
1218
build_wheels:
@@ -54,6 +60,7 @@ jobs:
5460

5561
build_wheels_ppc:
5662
name: Wheels for linux-ppc
63+
if: github.ref == 'refs/heads/master'
5764
runs-on: ubuntu-24.04
5865

5966
steps:
@@ -86,6 +93,8 @@ jobs:
8693

8794
sdist:
8895
runs-on: ubuntu-latest
96+
outputs:
97+
release_title: ${{ steps.parse_changelog.outputs.release_title }}
8998
steps:
9099
- uses: actions/checkout@v5
91100
with:
@@ -104,6 +113,17 @@ jobs:
104113
name: wheels-sdist
105114
path: dist/*
106115

116+
- name: parse CHANGELOG for release notes
117+
id: parse_changelog
118+
run: python .github/workflows/parse_release_notes.py
119+
120+
- name: Upload Release Notes
121+
uses: actions/upload-artifact@v4
122+
with:
123+
name: release-notes
124+
path: ReleaseNotes.md
125+
126+
107127
twine-check:
108128
name: Twine check
109129
# It is good to do this check on non-tagged commits.
@@ -123,7 +143,9 @@ jobs:
123143

124144
pypi:
125145
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
126-
needs: [build_wheels, build_wheels_ppc]
146+
needs: [build_wheels, build_wheels_ppc, sdist]
147+
permissions:
148+
contents: write # to create GitHub Release
127149
runs-on: ubuntu-24.04
128150

129151
steps:
@@ -140,3 +162,21 @@ jobs:
140162
with:
141163
user: __token__
142164
password: ${{ secrets.PYPI_API_TOKEN }}
165+
skip-existing: true
166+
167+
- uses: actions/download-artifact@v5
168+
with:
169+
name: release-notes
170+
- name: Create GitHub Release
171+
env:
172+
GITHUB_TOKEN: ${{ github.token }}
173+
TAG: ${{ github.ref_name }}
174+
REPO: ${{ github.repository }}
175+
TITLE: ${{ needs.sdist.outputs.release_title }}
176+
# https://cli.github.com/manual/gh_release_create
177+
run: >-
178+
gh release create ${TAG}
179+
--verify-tag
180+
--repo ${REPO}
181+
--title ${TITLE}
182+
--notes-file ReleaseNotes.md

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ environment = {LIBGIT2_VERSION="1.9.1", LIBSSH2_VERSION="1.11.1", OPENSSL_VERSIO
1212

1313
before-all = "sh build.sh"
1414
test-command = "pytest"
15-
test-sources = ["test"]
15+
test-sources = ["test", "pytest.ini"]
1616
before-test = "pip install -r {project}/requirements-test.txt"
1717
# Will avoid testing on emulated architectures (specifically ppc64le)
1818
test-skip = "*-*linux_ppc64le"

0 commit comments

Comments
 (0)