-
Notifications
You must be signed in to change notification settings - Fork 1
348 lines (311 loc) · 11 KB
/
ci.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
---
name: Continuous Integration
# Trigger this workflow manually, by pushing commits to any branch, or
# by filing a pull request.
on:
workflow_dispatch:
push:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
setup:
runs-on: ubuntu-latest
steps:
# Check out the repository as of this commit and cache the
# working directory for use in other jobs or for re-use if
# re-running the workflow (e.g., something outside of GitHub
# Actions broke).
- id: cache-workdir
uses: actions/cache@v4
with:
key: workdir-${{ github.sha }}
path: .
# Python Semantic Release needs the history of all branches/tags
# to calculate the next version number and build the change log.
- if: steps.cache-workdir.outputs.cache-hit != 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0
# Fingerprint the source code. Use this identifier instead of
# the commit ID to prevent non-code changes from altering
# builds.
- id: hash-source-code
run: |
echo "hash=$(find pyproject.toml src tests -type f -exec cat '{}' \; | sha512sum | awk '{print $1}')" >> $GITHUB_OUTPUT
shell: bash
outputs:
source-hash: ${{ steps.hash-source-code.outputs.hash }}
lint:
needs: setup
runs-on: ubuntu-latest
steps:
- uses: actions/cache/restore@v4
with:
key: workdir-${{ github.sha }}
path: .
# Install linter dependencies here; for example:
# - uses: opentofu/[email protected]
# - uses: terraform-linters/setup-tflint@v4
# Double-check code syntax/style. This ought to happen in a
# pre-commit hook, but not everyone may have that installed.
- uses: pre-commit/[email protected]
freeze:
needs:
- setup
- lint
runs-on: ubuntu-latest
steps:
# Only do this once. Testing (the next step) should reveal any
# compatibility issues. Source code changes, such as an updated
# version constraint in pyproject.toml, will automatically
# invalidate the list of pinned dependencies and trigger its
# regeneration since we're keying off the source hash.
- id: cache-requirements
uses: actions/cache@v4
with:
key: requirements-${{ needs.setup.outputs.source-hash }}
lookup-only: true
path: requirements.txt
- if: steps.cache-requirements.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
key: workdir-${{ github.sha }}
path: .
# Use the oldest supported version of Python when generating
# dependency pins to catch forward compatibility issues. Keep
# this in sync with the test-matrix and build-matrix jobs; cf.
# https://devguide.python.org/versions/.
- if: steps.cache-requirements.outputs.cache-hit != 'true'
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: pip
- if: steps.cache-requirements.outputs.cache-hit != 'true'
id: setup-pip-tools
run: pip install pip-tools
# Make builds reproducible by pinning every dependency as of
# this moment. That way, anyone can re-build this version of
# the project later and get the same result without tasking
# developers with version pin maintenance.
- if: steps.cache-requirements.outputs.cache-hit != 'true'
id: freeze-deps
run: pip-compile -o requirements.txt pyproject.toml
test-matrix:
needs:
- setup
- freeze
strategy:
# Keep this in sync with the freeze and build-matrix jobs; cf.
# https://devguide.python.org/versions/.
matrix:
python-version:
- '3.10'
- '3.11'
- '3.12'
runs-on: ubuntu-latest
steps:
# Tests take a long time to run, so only re-run them when (1)
# they fail or (2) when the source code changes. This step
# caches the output of a successful test run. Subsequent steps
# only run if that cache doesn't exist. (GitHub Actions jobs
# can only exit early with an error; cf.
# https://github.com/orgs/community/discussions/26885.)
- id: cache-pytest-results
uses: actions/cache@v4
with:
key: pytest-${{ matrix.python-version }}-${{ needs.setup.outputs.source-hash }}
lookup-only: true
path: pytest.out
# Test against all supported language runtimes using the
# dependency versions pinned above.
- if: steps.cache-pytest-results.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
key: requirements-${{ needs.setup.outputs.source-hash }}
path: requirements.txt
- if: steps.cache-pytest-results.outputs.cache-hit != 'true'
uses: actions/cache/restore@v4
with:
key: workdir-${{ github.sha }}
path: .
- if: steps.cache-pytest-results.outputs.cache-hit != 'true'
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
# Set up the testing environment.
- if: steps.cache-pytest-results.outputs.cache-hit != 'true'
run: |
sudo make build-deps
pip install -e .[psycopg2cffi,test]
USER_SITE=`python -m site --user-site`
mkdir -p "${USER_SITE}"
echo "from psycopg2cffi import compat" > "${USER_SITE}/psycopg2.py"
echo "compat.register()" >> "${USER_SITE}/psycopg2.py"
# Run the test suite and generate a code coverage report.
- if: steps.cache-pytest-results.outputs.cache-hit != 'true'
uses: pavelzw/pytest-action@v2
with:
custom-arguments: --cov=stuart --report-log=pytest.out
release:
needs:
- setup
- test-matrix
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
# Allow this job to log into GitHub and thereby update its
# permissions. (Think of this like adding someone to
# `sudoers`.) Then give the job permission to update the change
# log and to tag the release via Python Semantic Release.
id-token: write
contents: write
steps:
- uses: actions/cache/restore@v4
with:
key: requirements-${{ needs.setup.outputs.source-hash }}
path: requirements.txt
- uses: actions/cache/restore@v4
with:
key: workdir-${{ github.sha }}
path: .
- id: release
uses: python-semantic-release/[email protected]
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# The build process might download and run third-party code, so
# pass the now release-ready source code to an unprivileged job.
- uses: actions/cache/save@v4
with:
key: release-${{ github.sha }}
path: .
outputs:
released: ${{ steps.release.outputs.released }}
tag: ${{ steps.release.outputs.tag }}
build-matrix:
needs:
- release
if: needs.release.outputs.released == 'true'
strategy:
# Use the oldest supported version of Python when building pure
# Python packages. Otherwise, keep this in sync with the freeze
# and test-matrix jobs; cf.
# https://devguide.python.org/versions/.
matrix:
python-version:
- '3.10'
runs-on: ubuntu-latest
steps:
# Build the distribution.
- uses: actions/cache/restore@v4
with:
key: release-${{ github.sha }}
path: .
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- run: pip install build
- run: python -m build
# Third parties should be able to build the exact same
# distribution for verification purposes.
- run: cp requirements*.txt dist/
# The build process could run malicious third-party code in a
# supply chain attack, so this job doesn't run with elevated
# privileges. Instead, it uploads the built distribution into
# an immutable archive (a GitHub Actions Artifact) that
# subsequent, privileged jobs will publish on PyPI, GitHub, etc.
- uses: actions/upload-artifact@v4
with:
path: dist/*
if-no-files-found: error
testpypi:
needs:
- release
- build-matrix
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
environment:
name: testpypi
url: https://test.pypi.org/p/stuart
permissions:
# Allow this job to log into TestPyPI using the GitHub OIDC
# identity provider instead of static credentials; cf.
# https://docs.pypi.org/trusted-publishers/.
id-token: write
steps:
- uses: actions/cache/restore@v3
with:
key: release-${{ github.sha }}
path: .
- uses: actions/download-artifact@v4
with:
path: dist/
merge-multiple: true
# Publish built distributions to TestPyPI first. This will
# catch any problems before publishing to PyPI, which imposes
# strict limits on project names and package uploads; cf.
# https://pypi.org/help/#administration.
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
print-hash: true
verbose: true
pypi:
needs:
- release
- build-matrix
- testpypi
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
environment:
# Allow this job to log into PyPI using the GitHub OIDC identity
# provider instead of static credentials; cf.
# https://docs.pypi.org/trusted-publishers/.
name: pypi
url: https://pypi.org/p/stuart
permissions:
id-token: write
steps:
- uses: actions/cache/restore@v3
with:
key: release-${{ github.sha }}
path: .
- uses: actions/download-artifact@v4
with:
path: dist/
merge-multiple: true
# Publish built distributions to PyPI if the test publication
# succeeded.
- uses: pypa/gh-action-pypi-publish@release/v1
with:
print-hash: true
verbose: true
github:
needs:
- release
- build-matrix
if: needs.release.outputs.released == 'true'
runs-on: ubuntu-latest
permissions:
# Allow this job to log into GitHub and thereby update its
# permissions. Then give the job permission to create a release
# on GitHub.
id-token: write
contents: write
steps:
- uses: actions/cache/restore@v4
with:
key: release-${{ github.sha }}
path: .
- uses: actions/download-artifact@v4
with:
path: dist/
merge-multiple: true
# Publish built distributions to GitHub.
- uses: python-semantic-release/upload-to-gh-release@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ needs.release.outputs.tag }}