Skip to content

ci(MIC-100): cache text/build to skip unchanged Sphinx pages#482

Merged
mtiller merged 6 commits into
masterfrom
michaeltiller/mic-100-sphinx-build-cache
Jun 29, 2026
Merged

ci(MIC-100): cache text/build to skip unchanged Sphinx pages#482
mtiller merged 6 commits into
masterfrom
michaeltiller/mic-100-sphinx-build-cache

Conversation

@mtiller

@mtiller mtiller commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Result

generate_results drops from ~12m to ~3m 28s on a no-source-change rerun (most of the remaining time is GitHub-runner container init, not work).

Step Before After
Make PDFs 3m 33s 41s
Generate JSON 7m 34s 14s
Total ~12m ~3m 28s

Verified on commit 16453b57 (run 28406940388).

What was actually wrong

The first cache I tried (Sphinx text/build) saved <30s because the real bottleneck wasn't Sphinx. Drilling into the logs showed dvc repro dvc.yaml was eating 2m 43s in Make PDFs and ~6m in Generate JSON, just walking 30+ already-cached stages to confirm they're up to date.

Then it turned out CI_BUILD=1 (which exists in text/Makefile precisely to skip this) didn't work for two reasons:

  1. text/Makefile had CI_BUILD=0 instead of CI_BUILD ?= 0 — clobbering any env-var override. The comment promised "to enable the CI build do make CI_BUILD=1 pdf", but the line below unconditionally reset it.
  2. The root Makefile's json: target depended on results: unconditionally, so even when text/Makefile honored CI_BUILD, the root invocation forced a full DVC run.

Fixing both, then keeping images (svg→pdf) in the build chain (the latex builder needs the converted PDFs, which aren't checked in), gives the speedup.

Changes

  1. text/Makefile: CI_BUILD=0CI_BUILD ?= 0; keep images in SPHINXDEPS even under CI_BUILD=1.
  2. Makefile (root): mirror the CI_BUILD switch on the json: and dirhtml: targets so they drop the results dep when set.
  3. .github/workflows/build.yaml:
    • Cache text/results, text/plots, text/docs-dir keyed on dvc.lock + .mo files + specs.py + workflow.
    • Set CI_BUILD: 1 env on Make PDFs / Generate JSON when that cache hits; CI_BUILD: 0 when it misses (preserves correctness for source changes).
    • Also kept a smaller Sphinx text/build cache; modest extra win on xelatex.

Behavior matrix

Change kind ci-deps cache CI_BUILD What runs
Code/CI tweak (no source change) hit 1 Sphinx + xelatex only (~3m total)
Single .mo edit miss 0 full DVC repro (only the affected stage recomputes thanks to DVC); cache then re-saves
specs.py / dvc.lock change miss 0 same as above
Builder-image bump in workflow miss 0 full rebuild (correct)

Risks

  • The ci-deps cache assumes text/results + text/plots + text/docs-dir are the complete set of dvc repro outputs Sphinx consumes. If a future Sphinx extension reaches into another DVC-produced location, it'll silently get a stale file. Mitigation is the hash-key invalidation on dvc.lock.
  • Cache size measured at ~35MB for Sphinx + a few hundred MB for ci-deps (DVC outputs). Within the 10GB GHA quota.

Closes MIC-100.

Sphinx writes its build outputs to text/build/. Both 'make pdfs' (latex
builder + xelatex) and 'make json' (json + json_kr builders) share that
directory and the doctrees pickle inside it. Restoring it lets Sphinx
skip pages whose source content hasn't changed.

Cache key invalidates on any change to text/source/**, the KR locale
catalog, text/Makefile, or the workflow file (which captures the
builder-image bump).
@mtiller mtiller added the Full Build Trigger the full build+deploy workflow on this PR label Jun 29, 2026
mtiller added 5 commits June 29, 2026 12:20
The Makefile already has a CI_BUILD mode that drops the specs/results/images
deps from the Sphinx targets. The original design uploaded those outputs to
S3 ('uploadCIdeps' target) and downloaded them in CI to skip 'dvc repro'.

Replicate with actions/cache: cache text/results, text/plots, text/docs-dir
keyed on dvc.lock + .mo files + specs.py. When the key hits, set CI_BUILD=1
so Sphinx targets stop triggering 'dvc repro' (which today walks 30+
already-cached stages and burns ~8 min on overhead alone, per per-step
measurement on this PR).

When the key misses (e.g. .mo source changed), CI_BUILD=0 and 'dvc repro'
runs normally, then the new cache is saved for future runs.
Two bugs were preventing CI_BUILD from doing what its comment promised:

1. text/Makefile used 'CI_BUILD=0' (unconditional), which clobbered any
   env-var override. Switched to 'CI_BUILD ?= 0' so callers can actually
   set it via env or sub-make.

2. The root Makefile's json/dirhtml targets always pulled in the 'results'
   target (which runs 'dvc repro'). text/Makefile's CI_BUILD optimization
   only short-circuited deps INSIDE text/, so the root forced full
   regeneration regardless. Mirrored the same CI_BUILD switch in root
   Makefile so 'make json' / 'make dirhtml' skip results when CI_BUILD=1.

With both fixes, CI_BUILD=1 + a populated cache should let generate_results
skip 'dvc repro' entirely (the ~8 min cost identified by per-step
measurement on earlier runs in this PR).
Last run skipped too much: with SPHINXDEPS empty, the latex builder hit
WARNINGs for every SVG (no .pdf companion present), and the build failed
because the converted PDFs aren't checked in (only the SVGs are).

svg->pdf via rsvg-convert is cheap, so keep 'images' in SPHINXDEPS even
under CI_BUILD=1. The expensive deps we actually want to skip are 'specs'
and 'results' (dvc repro).
@mtiller
mtiller merged commit 56e1102 into master Jun 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Full Build Trigger the full build+deploy workflow on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant