Skip to content

fix: run the HTML phase again when the analysis of its inputs changes - #418

Merged
hargoniX merged 7 commits into
leanprover:mainfrom
marcelolynch:marker-trace
Sep 30, 2026
Merged

hargoniX merged 7 commits into
leanprover:mainfrom
marcelolynch:marker-trace

Conversation

@marcelolynch

@marcelolynch marcelolynch commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

The Lake part of doc-gen4 uses marker files as build products. These files represent data that has been added to the database, because Lake fundamentally tracks files and their contents. Previously, these marker files were empty, which could cause changes upstream of them to not invalidate a build. Now, they contain the hash of everything involved in their production, invalidating them correctly.

Closes: #389

The `docInfo`, `coreDocs`, `docsHeader` and `docs` steps record their
completion in marker files. Lake computes the trace of a built file from
its content, and the markers were empty, so their traces never changed
and the `docs` step stayed up to date while the database changed under
it. A declaration added to a module reached the database and not the page.

`writeMarker` writes the hash of the dependency trace of the step into the
marker, so the marker changes exactly when the inputs of the step change,
and the steps that depend on it run again.

The multi-library test adds a declaration to a library after the first
build and checks that its page shows the declaration after the next build.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The test adds two phases. The first runs `lake build LibA:docs --no-build`
after a build, so a marker whose content changes while the inputs stay the
same makes the test fail. The second adds a declaration to `LibA.Basic`,
which `LibA` imports, so the change must reach the `docs` step through two
markers. `LibA` now imports `LibA.Basic`.

The docstring of `writeMarker` names `buildFileUnlessUpToDate'`, the Lake
function that gives a built file the trace of its content.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread test/test-multi-lib-docs.sh Outdated
Comment thread test/test-multi-lib-docs.sh Outdated
Comment thread test/test-multi-lib-docs.sh Outdated
@david-christiansen

Copy link
Copy Markdown
Contributor

We use squash merges here with the PR description becoming the commit message, so I'm moving Claude's message down to a comment for posterity:


Problem

The coreDocs, docInfo, docsHeader and docs steps record their completion in marker files. The comments in the lakefile say that a step which depends on a marker runs again when the trace of the marker changes. The markers are empty files. Lake gives a built file the trace of its content, and an empty file has the same content after every write. So the trace of a marker is the same after every rebuild. The docs step mixes the marker traces of its root modules into its dependency trace. So the docs step stays up to date while the database changes under it.

Example. A library Lib has a page. Add a declaration to a module of Lib and run lake build Lib:docs again.

  1. The docInfo step of the module runs, because the oleans of the module changed, and writes the declaration into the database.
  2. The step writes its marker again with the same empty content, so the trace of the marker does not change.
  3. The docs step sees no change in its inputs and does not run. The page of Lib does not show the new declaration.

Issue #389 reports this behavior: a rebuild after a change to a doc string leaves the page as it was. This PR fixes that part of #389. The other part, a deleted page is not written again, is a separate problem: Lake tracks the marker, not the page.

Change

The lakefile gains writeMarker. It writes the hash of the dependency trace of the step into the marker file, and the four steps call it. The content of a marker then changes exactly when the inputs of its step change. In the example, the marker of the module gets a new hash in step 2. The dependency trace of the docs step changes, and the step writes the page again. With unchanged inputs the content of the marker stays the same, and nothing runs again.

An existing build directory needs no cleanup. A marker gets its hash the first time its step runs again, and the steps that depend on it run at that point.

Test

LibA in test/test-multi-lib-docs.sh imports a second module, LibA.Basic, and the script gains phases 3 to 5.

  1. lake build LibA:docs LibB:docs produces the pages of both libraries.
  2. lake build LibC:docs produces the page of LibC and keeps the pages of LibA and LibB.
  3. The script adds a declaration to the library root LibA and runs lake build LibA:docs again. The page of LibA must show the declaration.
  4. lake build LibA:docs --no-build must succeed. A build with no change has nothing to do, so the hash in the marker does not make the HTML phase run every time.
  5. The script adds a declaration to LibA.Basic and runs lake build LibA:docs again. The page of LibA.Basic must show the declaration, and the pages of the other two libraries must remain. This change reaches the docs step through two markers.

Phases 3 and 5 fail on main. Phase 4 passes on main and guards against a marker whose content changes while the inputs stay the same.

🤖 Generated with Claude Code

@hargoniX
hargoniX merged commit 84a4657 into leanprover:main Sep 30, 2026
3 checks passed
marcelolynch added a commit to marcelolynch/docgen-action that referenced this pull request Oct 1, 2026
The comment now also holds for doc-gen4 versions that include
leanprover/doc-gen4#418.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Local build does not refresh after code change

3 participants