Repository navigation
287 lines (244 loc) · 10.8 KB
/
Copy pathci.yml
File metadata and controls
287 lines (244 loc) · 10.8 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
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
name: CI
# Default behavior, on every push to master and every pull request: build the
# static site and (on master) publish it to GitHub Pages. That is all that
# runs by default, because the full example/test/type/lint suite is already
# run on the local machine before pushing, and GitHub Actions can be slow.
# The link-preview test (`tip preview-check`) left this path on 2026-10-24,
# when it took 4m40s of a 5m44s run: pandoc is pinned to the local
# version below, so the local run in `tip release` and `tip everything`
# tests the same HTML.
#
# The full gates (examples match the Markdown, every example runs clean, pytest
# passes, ty-clean, ruff-clean) are opt-in, as are the prose checks (codespell
# spelling and Vale house style). Run them only when you want a second opinion
# in CI, in either of two ways:
# * From the Actions tab, choose this workflow and click "Run workflow"
# (the "run_gates" input defaults to true), or run
# gh workflow run ci.yml -f run_gates=true
# * Include the marker [full-ci] anywhere in a push commit message.
#
# Chapter 44's four Effect programs (Koka, Flix, ZIO, TypeScript Effect)
# have a job of their own, "effects", on a separate switch: the
# "run_effects" input (default false) or the marker [effects-ci] in a push
# commit message. It installs four toolchains, so it stays off the default
# path; locally, `tip effects-all` does the same with whatever is installed.
#
# Tooling is managed by uv.
on:
push:
branches: [master]
pull_request:
workflow_dispatch:
inputs:
run_gates:
description: "Run the full gates plus the spelling/house-style checks"
type: boolean
default: true
run_effects:
description: "Build and run chapter 44's Koka, Flix, ZIO, and TypeScript programs"
type: boolean
default: false
permissions:
contents: read
# The pandoc every job installs. Keep it equal to the local pandoc
# (`pandoc --version`), so the deployed site is the one tested locally;
# `tip tools-check-full` prints a note when they differ.
env:
PANDOC_VERSION: "3.12.1"
jobs:
# Default work: build the static site. Fast, because it skips running the
# examples, type-checking, and linting. On a push to master it hands the
# built site to the deploy job below.
site:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.15"
enable-cache: true
# The release .deb at PANDOC_VERSION (top of this file), not
# Ubuntu's apt pandoc (3.1.3 on 2026-10-24), so the site the
# runner builds is the HTML the local pandoc writes.
- name: Install pandoc
run: |
curl -fsSL -o "$RUNNER_TEMP/pandoc.deb" \
"https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-1-amd64.deb"
sudo dpkg -i "$RUNNER_TEMP/pandoc.deb"
pandoc --version | head -1
- name: Install tooling
run: uv sync --locked
- name: Build the static site
run: uv run python -m tools.build_site
# On master only, hand the built site to the deploy job below.
- name: Upload Pages artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
uses: actions/upload-pages-artifact@v3
with:
path: build/site
# The full gates, opt-in only (manual dispatch with run_gates=true, or a push
# commit message containing [full-ci]). These run on the local machine on
# every change, so they are not part of the default CI path.
gates:
if: >-
(github.event_name == 'workflow_dispatch' && inputs.run_gates) ||
(github.event_name == 'push' &&
contains(github.event.head_commit.message, '[full-ci]'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.15"
enable-cache: true
# The release .deb at PANDOC_VERSION (top of this file), not
# Ubuntu's apt pandoc (3.1.3 on 2026-10-24), so the site the
# runner builds is the HTML the local pandoc writes.
- name: Install pandoc
run: |
curl -fsSL -o "$RUNNER_TEMP/pandoc.deb" \
"https://github.com/jgm/pandoc/releases/download/${PANDOC_VERSION}/pandoc-${PANDOC_VERSION}-1-amd64.deb"
sudo dpkg -i "$RUNNER_TEMP/pandoc.deb"
pandoc --version | head -1
- name: Install tooling
run: uv sync --locked
# Hard gate: the book's code blocks must match the committed Examples/
# tree (no drift, no conflicting duplicates).
- name: Check examples match the Markdown
run: uv run python -m tools.extract_examples
- name: Extract examples for running
run: uv run python -m tools.extract_examples --write
# Hard gate: every extracted example must run cleanly (the book is fully
# modernized, so the baseline is empty; use --baseline to gate only
# regressions during future bulk work).
- name: Run examples
run: uv run python -m tools.run_examples
# Hard gate: the book's pytest examples (test_*.py) must pass.
- name: Run the book's pytest examples
run: uv run pytest build/examples
# Hard gate: the examples must stay ty-clean (zero diagnostics).
- name: Type-check examples with ty
run: uv run ty check build/examples
# Hard gate: PEP 8 lint (naming, imports, modernization) must pass.
# See pyproject [tool.ruff]; deliberate exceptions live in
# per-file-ignores.
- name: Lint examples with ruff
run: uv run ruff check build/examples
# The same five checks, applied to Solutions/ (worked exercise
# answers), which go through a parallel extract/validate/ty/ruff/pytest
# pipeline (tools/extract_solutions.py). See tools/README.md.
- name: Check solutions match Solutions/*/README.md
run: uv run python -m tools.extract_solutions
- name: Extract solutions for running
run: uv run python -m tools.extract_solutions --write
- name: Verify solutions #: output markers
run: uv run python -m tools.validate_output --tree "$GITHUB_WORKSPACE/build/solutions" Solutions
- name: Type-check solutions with ty
run: uv run ty check build/solutions
- name: Lint solutions with ruff
run: uv run ruff check build/solutions
- name: Run the solutions' pytest examples
run: uv run pytest build/solutions
# Prose checks, opt-in only (same trigger as the gates job). codespell fails
# on spelling errors; Vale fails on em-dashes (an error-level rule) and prints
# the warning-level house-style findings. These also run locally via
# `tip spell` and `tip prose`.
prose:
if: >-
(github.event_name == 'workflow_dispatch' && inputs.run_gates) ||
(github.event_name == 'push' &&
contains(github.event.head_commit.message, '[full-ci]'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.15"
enable-cache: true
# Spelling: codespell is a uv-managed dev tool. Config lives in
# pyproject [tool.codespell]; the ignore list in tools/data/codespell-ignore.txt.
- name: Install tooling
run: uv sync --locked
- name: Spell-check prose with codespell
run: uv run codespell Chapters
# Vale is a standalone binary, not a Python package. Install the latest
# release; pin "version" below if you want fully reproducible runs.
- name: Install Vale
run: |
version=$(curl -fsSL https://api.github.com/repos/errata-ai/vale/releases/latest | grep -oP '"tag_name": "v\K[^"]+')
curl -fsSL "https://github.com/errata-ai/vale/releases/download/v${version}/vale_${version}_Linux_64-bit.tar.gz" | sudo tar -xz -C /usr/local/bin vale
vale --version
# Download the third-party styles referenced in .vale.ini (write-good,
# proselint). They are not committed, so CI fetches them each run.
- name: Sync Vale packages
run: vale sync
# House style: fails on em-dashes (error level); filler findings are
# warnings and do not fail the job. Config in .vale.ini.
- name: House-style lint with Vale
run: vale Chapters
# Chapter 44's greeting program in four other languages, each built and
# run with its own toolchain and held to one expected transcript
# (effects/README.md). Opt-in on its own switch, since four toolchains
# take minutes to install and the programs change only with chapter 44.
# The gate's own read of effects/ (drift against the Markdown) needs no
# toolchain and is part of `tip check` in the gates job above.
effects:
if: >-
(github.event_name == 'workflow_dispatch' && inputs.run_effects) ||
(github.event_name == 'push' &&
contains(github.event.head_commit.message, '[effects-ci]'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
python-version: "3.15"
enable-cache: true
- name: Install tooling
run: uv sync --locked
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Install scala-cli
uses: VirtusLab/scala-cli-setup@v1
# The install script at a release's own URL pins that release.
- name: Install Koka
run: |
curl -sSL https://github.com/koka-lang/koka/releases/download/v3.2.9/install.sh | sh -s -- --quiet
koka --version --console=raw
# flix.jar at the pinned version, and npm ci against the lockfile.
- name: Fetch the Flix compiler and the TypeScript packages
run: uv run tip effects-fetch
# REQUIRE=1: with every toolchain installed, a skip is a failure.
- name: Build and run the four programs
run: uv run tip effects-test REQUIRE=1
# Publishes the site to GitHub Pages (bruceeckel.github.io/ThinkingInPython)
# on a push to master. It depends only on the site build, not on the opt-in
# gates, so publishing is not blocked by them.
deploy:
needs: site
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
concurrency:
group: pages
cancel-in-progress: true
steps:
- name: Deploy to GitHub Pages
id: deploy
uses: actions/deploy-pages@v4