Repository navigation
242 lines (238 loc) · 10.9 KB
/
Copy pathpr_code_changes.yaml
File metadata and controls
242 lines (238 loc) · 10.9 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
230
231
232
233
234
235
236
237
238
239
240
241
242
# Workflow that runs on code changes to a pull request.
#
# It runs on every pull request to main, with no path filter, so its checks,
# "Test / Result" above all, report on every pull request and a branch rule
# can require them: GitHub leaves a required check pending forever when a
# path filter skips its workflow, and a second workflow on the other paths
# cannot stand in, because a pull request that touches both kinds of path
# runs both. The Changes job decides instead. When no code changed, the jobs
# below are skipped, which a required check counts as passing, and the test
# suite reports Result as a pass without running.
#
# It also runs for merge queue groups (merge_group): with a merge queue on
# main, each pull request is tested again merged onto the latest main, with
# any pull requests queued ahead of it, and main moves only to a commit that
# passed. Each pull request was green on its own while #2251 and #2270
# together broke main on 11 October 2026 (7343f948).
name: Code changes
on:
pull_request:
branches:
- main
merge_group:
workflow_dispatch:
# A new push to a pull request supersedes the run on its previous head, so
# cancel that run instead of letting it hold runners. Merge groups and manual
# dispatches are grouped by ref (each merge group has its own) and never
# cancelled.
concurrency:
group: pr-code-changes-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
Changes:
runs-on: ${{ vars.CI_RUNNER_ARM != '' && (vars.CI_RUNNER_EVENTS == '' || contains(format(',{0},', vars.CI_RUNNER_EVENTS), format(',{0},', github.event_name))) && vars.CI_RUNNER_ARM || 'ubuntu-24.04-arm' }}
permissions:
contents: read
timeout-minutes: 5
outputs:
code: ${{ steps.diff.outputs.code }}
steps:
- name: Checkout repo
uses: actions/checkout@v6
with:
# The pull request's merge commit and its first parent, the base.
fetch-depth: 2
- name: Did the pull request change code?
id: diff
env:
EVENT: ${{ github.event_name }}
# The paths that make a change "code": the same list as the push
# filter in code_changes.yaml (a test keeps them equal).
CODE_PATHS: |
policyengine_uk/**
tests/**
.github/**
pyproject.toml
uv.lock
hatch_build.py
Makefile
conftest.py
.gitignore
README.md
run: |
set -euo pipefail
# Only a pull request can skip its tests. A merge queue group
# always runs them: main moves to the group's commit, and its
# base_sha can be the group queued ahead of it rather than main,
# so a diff against it could call a code change "no code".
if [ "$EVENT" != pull_request ]; then
echo "code=true" | tee -a "$GITHUB_OUTPUT"
exit 0
fi
# The merge commit's first parent is the base branch.
base=HEAD^1
# NUL-separated and unquoted, with a rename listed as its old and
# new paths, so a file moved out of policyengine_uk/ still counts.
git -c core.quotePath=false diff --name-only --no-renames -z "$base" HEAD \
> "$RUNNER_TEMP/changed.txt"
python3 .github/changed_code.py "$RUNNER_TEMP/changed.txt" | tee -a "$GITHUB_OUTPUT"
Python-Compat:
name: Install + smoke-import (py${{ matrix.python-version }})
needs: Changes
if: needs.Changes.outputs.code == 'true'
runs-on: ${{ vars.CI_RUNNER_X64 != '' && (vars.CI_RUNNER_EVENTS == '' || contains(format(',{0},', vars.CI_RUNNER_EVENTS), format(',{0},', github.event_name))) && vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
permissions:
contents: read
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
- name: Install package
run: uv pip install --system .
- name: Smoke-import
run: python -c "import policyengine_uk; print('import OK')"
- name: Check the installed package reports this commit
# Outside the checkout, so Python imports the installed wheel, which
# hatch_build.py built from this clean checkout.
working-directory: ${{ runner.temp }}
run: |
python - "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD)" <<'EOF'
import sys
from policyengine_uk import build_metadata
head = sys.argv[1]
git_sha = build_metadata.get_runtime_metadata()["git_sha"]
print(f"{build_metadata.PACKAGE_ROOT}: git_sha={git_sha}")
assert "site-packages" in build_metadata.PACKAGE_ROOT.parts
assert git_sha == head, f"expected {head}"
EOF
Release-Build:
name: Release build records the commit
needs: Changes
if: needs.Changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
# The constrained artifact build used by versioning.yaml, without publishing.
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Build package
run: >-
uv build --python 3.13 --sdist --wheel --out-dir dist
--build-constraint .github/build-constraints.txt
- name: Build a wheel from the sdist
# make builds the release wheel from the checkout. Check the sdist
# still builds the same package, as building the wheel from it once did.
run: >-
uv build --python 3.13 --wheel --out-dir "$RUNNER_TEMP/from-sdist"
--build-constraint .github/build-constraints.txt dist/*.tar.gz
- name: Check the wheel records this commit
run: >-
python .github/check_release_build.py dist "$(git rev-parse HEAD)"
"$RUNNER_TEMP/from-sdist"
Lint:
# On every pull request, whatever it changes: ruff formats and checks
# notebooks under docs/ and scripts/ too, so a documentation-only change
# can break Lint for every later pull request (#1696 landed a notebook
# that failed the format check; #1743 fixed it).
runs-on: ${{ vars.CI_RUNNER_X64 != '' && (vars.CI_RUNNER_EVENTS == '' || contains(format(',{0},', vars.CI_RUNNER_EVENTS), format(',{0},', github.event_name))) && vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
permissions:
contents: read
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
- name: Check formatting
# With the ruff that uv.lock pins, so a new ruff release cannot change
# what Lint accepts until the lock is updated.
run: |
set -euo pipefail
ruff=$(uv export --frozen --extra dev --no-hashes --no-header | { grep -E '^ruff==' || true; } | cut -d' ' -f1)
if [ "$(printf '%s' "$ruff" | grep -c '^ruff==')" != 1 ]; then
echo "::error::uv.lock should pin exactly one ruff; found: ${ruff:-none}"
exit 1
fi
echo "RUFF=$ruff" >> "$GITHUB_ENV"
uvx --from "$ruff" ruff format --check .
- name: Lint
# The ruff rules pyproject.toml selects, then two pyflakes rules it
# ignores, checked in the package: F811, a definition that a later
# one of the same name silently replaces (two PRs that each add a
# Simulation.set_input merge cleanly, and the second wins; uk hub
# notice 16), and F821, an undefined name. Neither has a finding
# in the package today.
run: |
uvx --from "$RUFF" ruff check .
uvx --from "$RUFF" ruff check --select F811,F821 policyengine_uk
- name: No agent working notes at the repository root
# Agent jobs keep their working notes at the root of their
# worktree, and #2376's REPORT.md and PROGRESS.md reached main by
# mistake on 12 October 2026. Their evidence belongs on the pull
# request. The check is in Lint because Lint runs on every pull
# request: these files are not code paths, so a pull request that
# adds only them skips Test. It runs even when ruff failed, so one
# run reports both. An explicit bash shell adds pipefail, so a git
# failure fails the step instead of passing it.
if: ${{ !cancelled() }}
shell: bash
run: |
notes=$(git ls-files -- ':(top,icase)REPORT.md' ':(top,icase)PROGRESS.md' \
':(top,icase)HANDOFF.md' ':(top,icase)handoffs.jsonl' | tr '\n' ' ')
if [ -n "$notes" ]; then
echo "::error::Agent working notes at the repository root: ${notes}- post their evidence on the pull request and remove them."
exit 1
fi
Docs:
# The documentation, built against a pull request's code changes. A
# pull request that changes only docs/ builds it in pr_docs_changes.yaml.
name: Documentation build
needs: Changes
if: needs.Changes.outputs.code == 'true'
runs-on: ${{ vars.CI_RUNNER_X64 != '' && (vars.CI_RUNNER_EVENTS == '' || contains(format(',{0},', vars.CI_RUNNER_EVENTS), format(',{0},', github.event_name))) && vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
permissions:
contents: read
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v6
# The environment the tests use: uv.lock with the dev extra (which
# includes jupyter-book), so a new release cannot change the build.
- name: Install the locked environment
uses: ./.github/actions/test-environment
- name: Test documentation builds
run: make documentation
# The YAML tests, the Python tests on five runners, and the check that
# every test ran once and passed, in the locked environment. As one job
# they took 59 minutes on 10 October 2026 (run 38075384588), and runs were
# cancelled at a 60-minute limit (issues #2099 and #2257). The release
# workflow runs the same suite on the wheel it publishes. Require
# "Test / Result".
Test:
needs: Changes
uses: ./.github/workflows/test-suite.yaml
permissions:
contents: read
id-token: write
with:
# Skip only on an explicit "false", so a missing output runs the tests.
skip: ${{ needs.Changes.outputs.code == 'false' }}
# A merge group decides what main becomes: it must run the
# microsimulation tests, never accept them as skipped for want of
# the dataset credentials.
require-data: ${{ github.event_name == 'merge_group' }}
secrets:
HUGGING_FACE_TOKEN: ${{ secrets.HUGGING_FACE_TOKEN }}