Repository navigation
116 lines (109 loc) · 4.03 KB
/
Copy pathpython-ci.yml
File metadata and controls
116 lines (109 loc) · 4.03 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
# Reusable CI for the Python repositories that use dev-standards.
# Callers pick optional jobs through inputs. See the README for an example caller.
name: Python CI
on:
workflow_call:
inputs:
python-version:
description: The Python version used by every job.
type: string
default: "3.14"
forward-python-version:
description: A newer Python version that runs the tests once more. Empty skips it.
type: string
default: ""
mypy:
description: Whether to run the typecheck job.
type: boolean
default: false
mypy-target:
description: The path mypy checks.
type: string
default: src
docker:
description: Whether to build the Docker image.
type: boolean
default: false
smoke-test:
description: A script in the repository that gets the built image's name and checks that it starts. Empty skips it.
type: string
default: ""
lfs:
description: Whether to check out Git LFS files.
type: boolean
default: false
permissions:
contents: read
# Each job takes a few minutes at most, so a job past its timeout has hung, as when a download stops answering.
# Without a timeout, GitHub waits 6 hours before it cancels a job, and a private repository is billed for every minute.
jobs:
# One job runs every check, because GitHub bills each job of a private repository by the minute, rounded up.
# Separate jobs would each install the project again and round up again.
checks:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
lfs: ${{ inputs.lfs }}
- uses: astral-sh/setup-uv@v10.3.3
with:
python-version: ${{ inputs.python-version }}
enable-cache: true
- uses: actions/cache@v6
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- name: Lint
run: uvx pre-commit run --all-files --show-diff-on-failure
- run: uv sync --frozen
- name: Test
run: uv run pytest
- name: Typecheck
if: ${{ inputs.mypy }}
run: uv run mypy ${{ inputs.mypy-target }}
# The tests run on a newer Python too, so an upgrade's breakage shows up before the upgrade.
# A failure only warns, because a dependency may not support the newer Python yet.
# Only public repositories run it, where Actions minutes are free.
# A private repository shares its dependencies with a public template, which warns as early.
test-forward:
if: ${{ inputs.forward-python-version != '' && !github.event.repository.private }}
name: test (Python ${{ inputs.forward-python-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
continue-on-error: true
steps:
- uses: actions/checkout@v7
with:
lfs: ${{ inputs.lfs }}
- uses: astral-sh/setup-uv@v10.3.3
with:
python-version: ${{ inputs.forward-python-version }}
enable-cache: true
- run: uv sync --frozen
- run: uv run pytest
docker:
if: ${{ inputs.docker }}
runs-on: ubuntu-latest
# A build with an empty cache takes longer than one that reuses its layers.
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
lfs: ${{ inputs.lfs }}
- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
with:
context: .
push: false
# The smoke test needs the image in the local Docker, which a plain build leaves in the cache.
load: ${{ inputs.smoke-test != '' }}
tags: smoke-test:ci
cache-from: type=gha
cache-to: type=gha,mode=max
# A build passes even when the program inside the image cannot start, such as after a base image change.
- name: Start the image
if: ${{ inputs.smoke-test != '' }}
env:
SMOKE_TEST: ${{ inputs.smoke-test }}
run: bash "$SMOKE_TEST" smoke-test:ci