Repository navigation
119 lines (108 loc) · 6.34 KB
/
Copy pathci.yml
File metadata and controls
119 lines (108 loc) · 6.34 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
name: CI
# The Actions list titles every run with the COMMIT MESSAGE, so four different workflows
# triggered by one push read as four identical entries -- which is how a merge came to look
# like a job running four times. `run-name` replaces that title with what the run IS.
#
# Deliberately NOT `github.event.head_commit.message`: this repository writes long,
# multi-paragraph commit messages, the title would swallow the list, and GitHub already
# shows the commit beside every run. The workflow's identity is the part that was missing.
run-name: "CI · ${{ github.event_name }} · ${{ github.head_ref || github.ref_name }}"
# ON EVERY MERGE, and on demand. A push to `main` is what a merge looks like from Actions' side,
# so every merged PR now re-runs lint, the suite, and the build-identity check against the tree
# that actually resulted from the merge.
#
# That last phrase is the point, and it is why this is NOT the redundant run it was once called.
# A `pull_request` run tests the PR head; a `push` run tests `main` after the merge landed. Those
# differ whenever two independently-green branches conflict semantically -- each passes alone, the
# merge of them does not. Only the post-merge run can see that.
#
# `pull_request` is here for a specific, load-bearing reason: it is what makes this job report a
# status named `test` on a PR, and the `main` ruleset REQUIRES that context before a merge is
# allowed (issue #236). The two are a matched pair and must be changed together -- removing this
# trigger while the ruleset still requires `test` would leave every PR waiting forever on a check
# that can never report, which is precisely the broken state #236 was filed to fix. That is not a
# hypothetical: the ruleset previously required "Lint, Unit tests, Build" and "Integration Tests",
# names no job here has ever produced.
#
# The context string is `test` because the job below is `test:` and declares no `name:`. Renaming
# that job, or giving it a `name:`, silently changes the context and breaks the ruleset the same
# way. If you rename it, update the ruleset in the same change.
#
# So the two triggers do different jobs: `pull_request` GATES the merge, `push` VERIFIES the result
# of it. `release.yml` remains the gate that matters for money -- it re-runs `ruff` + `mypy` + `pytest`
# itself before it will build an artifact, so a red `main` cannot become a wheel, a tag, or a
# release asset even if both of these were removed.
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
# `github.ref` is `refs/pull/<n>/merge` for PR runs, `refs/heads/main` for the post-merge push, and
# `refs/heads/<branch>` for a dispatch -- distinct keys, so a PR run never cancels the post-merge
# run. Pushes to the same PR do cancel each other, and back-to-back merges cancel the older `main`
# run -- correct in both cases: only the newest result matters.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
# ONE leg, because the floor and the development interpreter are now the same version.
#
# This was two legs -- "3.11" and "3.14" -- the two ends of the `>=3.11` floor #283 chose.
# With `requires-python = ">=3.14"` there is no span left to test across: the floor leg and
# the development leg would be the same interpreter, and a matrix of one is a matrix that
# costs a second checkout to prove nothing.
#
# It stays a matrix rather than a bare `python-version:` deliberately. When a second
# supported version arrives -- 3.15, or a floor drop -- adding it here is one list entry,
# and the required status context stays `test` either way, because the matrix fans the
# context out without renaming it. Collapsing to a scalar would make that a workflow edit.
#
# "3.14" is named by major.minor only; `.python-version` pins the exact patch (3.14.4) for
# local work, and "whatever .python-version says" is not addressable per-leg.
strategy:
matrix:
python: ["3.14"]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # tags -- tests/test_python_floor.py reads the latest release's
# requires-python from its tag (#557: it must never be main's floor)
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Python
run: uv python install "${{ matrix.python }}"
- name: Sync dependencies
run: uv sync --all-extras --dev --python "${{ matrix.python }}"
- name: Lint
run: uv run --python "${{ matrix.python }}" ruff check keel tests packages
# WHOLE TREE, unlike `ruff check` above, and that difference is deliberate. `ruff check`
# is scoped to the three trees whose lint debt has been paid; formatting has no such debt
# after #783's one-shot pass, so scoping this would just re-open the drift in `docs/` and
# `scripts/` that the pass exists to close.
#
# It runs as its own step so a formatting failure reads as one. Folded into the lint step,
# `ruff check` would mask it on any run where both are red.
- name: Format
run: uv run --python "${{ matrix.python }}" ruff format --check .
# A STEP in this job, deliberately not a job of its own. The `main` ruleset requires the
# status context `test`, which this job produces; a separate `typecheck:` job would report
# a context nothing requires, so a red mypy would not block a merge -- the same failure
# shape the header above describes for a renamed job. As a step it inherits that gate.
#
# Paths come from `[tool.mypy]`'s `files` in pyproject.toml and are deliberately NOT
# repeated here: `keel.*` came off the `ignore_errors` list in #266, and the point of this
# step is that it cannot drift back silently. A package moving in or out of the checked
# set is then one edit there, not one edit mirrored across two workflows.
- name: Type-check
run: uv run --python "${{ matrix.python }}" mypy
- name: Test
run: uv run --python "${{ matrix.python }}" pytest -q
# A build that cannot identify itself must not reach a release. This also catches an
# import-time break in the CLI, which `pytest` alone would not surface as sharply.
- name: Build identity
run: uv run --python "${{ matrix.python }}" keel --version