-
-
Notifications
You must be signed in to change notification settings - Fork 124
229 lines (216 loc) · 8.82 KB
/
Copy pathpython-ci.yml
File metadata and controls
229 lines (216 loc) · 8.82 KB
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
name: python-ci
# Tiered triggering keeps PR feedback fast while still doing a full
# cross-platform sweep on every master push, weekly, and on demand.
# Feature-branch pushes get the fast tier (same as PRs) so contributors
# see CI feedback before opening a PR.
on:
push:
pull_request:
workflow_dispatch:
schedule:
# Monday 06:00 UTC: catches drift on runner image / Python rolling
# bugfix releases without humans needing to remember to push.
- cron: '0 6 * * 1'
# Default to read-only for every job; neither static analysis nor pytest
# needs anything more.
permissions:
contents: read
# Use bash for every run: step so that single-quoted pip extras work
# consistently on all platforms (PowerShell on Windows handles them
# differently in some edge cases) and inside Linux containers.
defaults:
run:
shell: bash
jobs:
# -------------------------------------------------------------------------
# Static analysis (ruff + mypy + bandit). One job, fast, fails fast.
# -------------------------------------------------------------------------
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: install lint extras
run: |
python -m pip install --upgrade pip
python -m pip install -e '.[lint]'
- name: ruff
run: ruff check .
- name: mypy
run: mypy
- name: bandit
run: bandit -r src/keychain -c pyproject.toml
# -------------------------------------------------------------------------
# Fast tier — runs on every event (PR, push, cron, dispatch).
#
# Floor + ceiling on Linux + Windows/macOS-on-ceiling. Rationale:
# - 3.9 (floor) catches usage of features added later.
# - 3.13 (ceiling) catches deprecations and stdlib drift.
# - Windows × 3.13 catches POSIX-only assumptions; OS-portability
# bugs almost never differ between Python minor versions, so one
# Windows job is enough for PR-time feedback.
# - macOS × 3.13 catches Darwin-specific POSIX edges, especially
# AF_UNIX socket path limits that Linux does not share.
# -------------------------------------------------------------------------
test-fast:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
python-version: "3.9"
- os: ubuntu-latest
python-version: "3.13"
- os: windows-latest
python-version: "3.13"
- os: macos-latest
python-version: "3.13"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: install
run: |
python -m pip install --upgrade pip
python -m pip install -e '.[dev]'
- name: pytest
run: python -m pytest -v --tb=short --cov=src/keychain --cov-branch --cov-report=term-missing:skip-covered
# -------------------------------------------------------------------------
# Production-target authenticity: Rocky Linux 8 + python3.9 module.
#
# This is the documented install path for RHEL 8 / Rocky 8 users
# (see docs/python-version-modernization.md). Validating it on every
# commit keeps three risks contained:
# - The 3.9-floor guard in src/keychain/__init__.py wiring.
# - The polyglot sh/python multi-version probing shebang baked
# into keychain.pyz by the Makefile.
# - Subtle stdlib differences between AppStream's python3.9 build
# and the upstream cpython binary that setup-python ships.
# -------------------------------------------------------------------------
test-rocky8-py39:
runs-on: ubuntu-latest
container:
image: rockylinux:8
steps:
- name: install python3.9 module + build prerequisites
# git: needed by actions/checkout
# python39: AppStream's RHEL-shipped 3.9 -- our actual target
# gcc + python39-devel: allow C-extension wheels (psutil) to
# build from source if no manylinux wheel matches; not
# strictly required today (no C deps) but cheap insurance
# against transitive deps growing one later.
# make: builds keychain.pyz for the smoke test below.
run: |
dnf install -y git make gcc
dnf module install -y python39
dnf install -y python39-devel
- uses: actions/checkout@v4
- name: python version
run: python3.9 --version
- name: install package
run: |
python3.9 -m pip install --upgrade pip
python3.9 -m pip install -e '.[dev]'
- name: pytest
run: python3.9 -m pytest -v --tb=short --cov=src/keychain --cov-branch --cov-report=term-missing:skip-covered
- name: build polyglot zipapp & smoke-test shebang resolution
# /usr/bin/python3 on Rocky 8 is 3.6.8; the shebang must still
# find python3.9 via the polyglot probe. If the probe regresses
# to /usr/bin/env python3, the version-floor guard fires and
# this step exits 2.
run: |
make keychain.pyz
./keychain.pyz version
./keychain.pyz help >/dev/null
# -------------------------------------------------------------------------
# Full sweep — runs on master pushes, tags, weekly cron, and manual
# dispatch. *Skipped on PRs and feature-branch pushes* to keep PR and
# day-to-day feedback under a few minutes. If a branch needs the full
# sweep before merge (e.g. touching subprocess wiring, signal handling,
# path resolution), a maintainer can trigger it via the Actions tab →
# "Run workflow".
# -------------------------------------------------------------------------
test-full-sweep:
if: >-
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/master') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: install
run: |
python -m pip install --upgrade pip
python -m pip install -e '.[dev]'
- name: pytest
run: python -m pytest -v --tb=short --cov=src/keychain --cov-branch --cov-report=term-missing:skip-covered
# -------------------------------------------------------------------------
# Synthetic aggregate check for branch/ruleset protection.
#
# GitHub branch rules can require this single stable check instead of every
# matrix expansion above. Keep this job last and include every CI job in
# ``needs`` so a green ``ci-passing`` means the relevant CI tier passed.
# -------------------------------------------------------------------------
ci-passing:
name: ci-passing
if: ${{ always() }}
needs:
- static-analysis
- test-fast
- test-rocky8-py39
- test-full-sweep
runs-on: ubuntu-latest
env:
STATIC_ANALYSIS_RESULT: ${{ needs.static-analysis.result }}
TEST_FAST_RESULT: ${{ needs.test-fast.result }}
TEST_ROCKY8_RESULT: ${{ needs.test-rocky8-py39.result }}
TEST_FULL_SWEEP_RESULT: ${{ needs.test-full-sweep.result }}
TEST_FULL_SWEEP_REQUIRED: >-
${{
github.event_name == 'schedule' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'push' && github.ref == 'refs/heads/master') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
}}
steps:
- name: Verify aggregate CI status
run: |
echo "static-analysis: ${STATIC_ANALYSIS_RESULT}"
echo "test-fast: ${TEST_FAST_RESULT}"
echo "test-rocky8-py39: ${TEST_ROCKY8_RESULT}"
echo "test-full-sweep: ${TEST_FULL_SWEEP_RESULT} (required: ${TEST_FULL_SWEEP_REQUIRED})"
failed=0
for result in \
"${STATIC_ANALYSIS_RESULT}" \
"${TEST_FAST_RESULT}" \
"${TEST_ROCKY8_RESULT}"
do
if [ "${result}" != "success" ]; then
failed=1
fi
done
if [ "${TEST_FULL_SWEEP_REQUIRED}" = "true" ]; then
if [ "${TEST_FULL_SWEEP_RESULT}" != "success" ]; then
failed=1
fi
elif [ "${TEST_FULL_SWEEP_RESULT}" != "success" ] && [ "${TEST_FULL_SWEEP_RESULT}" != "skipped" ]; then
failed=1
fi
if [ "${failed}" -ne 0 ]; then
echo "CI did not pass."
exit 1
fi
echo "CI passed."