Skip to content

tex.mk: honest failures, quiet pattern rules, working bibtex rule - #88

Open
dbosk wants to merge 5 commits into
fix/noweb-peer-target-warningsfrom
fix/texmk-robustness
Open

dbosk wants to merge 5 commits into
fix/noweb-peer-target-warningsfrom
fix/texmk-robustness

Conversation

@dbosk

@dbosk dbosk commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Fixes #85. Fixes #86. Stacked on #84 (base branch of this PR); merge #84
first, then this.

  • COMPILE.tex discards latexmk's exit status, masking failed builds #85: COMPILE.tex ended the latexmk call with ;, so make reported
    success for failed builds — this masked dbosk/nytid's broken doc build for
    a long time. latexmk's exit status now propagates (also in the DVI rerun
    loop).
  • Stabilization policy (third commit): latexmk exits 12 for every
    failure (measured on TeX Live 2025: missing \input, undefined macro, and
    non-convergence all exit 12), so exit-code discrimination would re-mask
    broken documents. The recipe instead reads latexmk's verdict: real error →
    fail with latexmk's status; "Maximum runs … without getting stable files"
    with no collected errors and no failed sub-make recipe → loud greppable
    warning + success (motivating case: dbosk/nytid's 1100-page document has a
    provable two-state page-break oscillation, so no run budget ever
    satisfies the stability check); converged → silent. Validated against 8
    fixtures including mock traps (error-summary+oscillation and
    failed-recipe+oscillation both still fail; benign first-pass "No rule to
    make target" noise from -use-make is not mistaken for failure).
  • tex.mk: .dtx grouped pattern rules and mv-onto-itself in the bibtex rule #86: the .dtx multi-target pattern rules are split per target (GNU
    Make 4.4 peer-target warnings: 4 configurations before, 0 after,
    byte-identical outputs), and the bibtex rule's mv-onto-itself is deleted —
    empirically, bibtex writes .bbl/.blg beside the .aux it is handed
    (openout_any=p forbids ../), so the outputs already land on the target
    and the issue's suggested $(notdir …) would name files that don't exist.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AhWZp3q1126cvw9bW8VBB5

dbosk and others added 5 commits August 25, 2026 16:11
Commit e82e304 capped the LaTeX rerun loops at five extra passes, but it
edited the generated tex.mk only and left tex.mk.nw with the original
unbounded while-grep loops.  Every other .mk here tangles byte-for-byte
from its .nw, so tex.mk was the one file whose generated form no longer
matched its source.  That is a trap rather than a cosmetic drift:
noweb.mk supplies a %.mk: %.mk.nw rule and make remakes included
makefiles by itself, so the next build that touched tex.mk.nw would have
regenerated tex.mk and silently reverted #73.

Move the loop, and the comment explaining why it is bounded, into the
chunks they belong to.  tex.mk itself is unchanged: tangling tex.mk.nw
now reproduces the committed file exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rules producing a .pytxmcr, a .pdf and a .dvi each listed the plain
name and the ${TEX_OUTDIR}/ one as targets of a single pattern rule.
Those two names are alternatives -- two ways of asking for the same
output -- but that is not how make reads them: a pattern rule with
several targets means one recipe that produces all of them (GNU Make
manual, Sect. 10.5.1).  Since GNU Make 4.4 it says so, with
a "pattern recipe did not update peer target" warning for every listed
target the recipe did not write.  Building nytid's documentation hit it
once per build, from the PythonTeX rule, whose recipe only ever writes
inside ${TEX_OUTDIR}.

Write one single-target rule per name instead, sharing each recipe
through a chunk so the two copies cannot drift.  The .pytxmcr rules keep
their double colon: on a pattern rule that marks the rule terminal, and
splitting the targets must not quietly change that.

The rules make ends up with are the same in every configuration.  Where
TEX_OUTDIR names a separate directory -- the ltxobj default -- the two
patterns stay distinct and each name selects its own rule.  Where it is
set to ".", they coincide; since the recipes are identical the behaviour
is unchanged, just as before, when make saw the same pattern listed
twice within one rule.

Fixes #83.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
COMPILE.tex ran latexmk, then the bounded rerun loop, with a ";" between
them.  A recipe's exit status is its last command's, and the last command
is the loop -- which ends in a break as soon as the log stops asking for
another run, and break succeeds.  latexmk's status was therefore thrown
away, and make called every failed build a success.

None of that is cosmetic: a build that cannot fail cannot be observed to
have failed.  nytid's documentation build exited zero for a long time
with an "Emergency stop" and a bibtex "Error 2" standing in its log, so
neither its author nor its continuous integration ever learnt that the
document had stopped building (dbosk/nytid#446).  Every kind of LaTeX
breakage short of one that removes an already-built file was hidden from
make in the same way.

Join the compilation to the loop with "&&" instead.  The loop's grep only
has a log to read when the run got far enough to write one, so a failed
run has nothing to enter the loop for and its own status becomes the
recipe's.  The reruns inside the loop each end with "|| exit $?" for the
same reason: a for loop runs its body to the end no matter what fails
inside it, so a rerun that died would still be followed by a successful
break.  What make reports is latexmk's own status rather than a flattened
1 -- a document with a missing \input now fails with "Error 12".

The DVI rerun loop had the same hole and gets the same guard.  Its first
run is a recipe line of its own, so make already stopped on that one.

Fixes #85.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two leftovers: one from #83, one from the move to an output directory.

The .dtx rules producing a .pdf and a .dvi still listed the plain name
and the ${TEX_OUTDIR}/ one as targets of a single pattern rule -- the
shape 55a6efb took out of the .tex and .pytxmcr rules.  Give each name
its own rule here as well, sharing each recipe through a chunk so the
copies cannot drift; the .dvi pair reuses the document recipe outright,
since compiling a class to DVI is the same work.  These rules carry
a single colon, so unlike the .pytxmcr pair there is no terminal-rule
property for the split to preserve.  Under GNU Make 4.4 a package
project asking for the ${TEX_OUTDIR}/ name got one "pattern recipe did
not update peer target" warning from each rule, and so did one asking
for the plain name with TEX_OUTDIR set to the empty string; it now gets
none, and byte-identical output in every TEX_OUTDIR configuration.

The bibtex rule ended with "mv $@ ${@:.bbl=.blg} ${TEX_OUTDIR}".  bibtex
has no output-directory option; what it does is write its .bbl and .blg
beside the .aux it was handed, and the .aux we hand it is
${TEX_OUTDIR}/%.aux -- so both already are where the target says they
should be.  With $@ carrying the ${TEX_OUTDIR}/ prefix, the line asked
mv to move each file onto itself; mv declines that ("are the same
file"), and as the recipe's last command the refusal failed the rule.
Drop it.  Repairing the names with $(notdir ...) would not help, as it
would name files that are no longer anywhere to be moved from.

Fixes #86.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d8dc836 made every non-zero latexmk status fail the rule.  That turns out
to be too blunt.  latexmk spends a single status -- 12 -- on a document
with an error in it and on a document that simply never settles, and the
two want opposite answers.  Measured on TeX Live 2025: a missing \input,
an undefined control sequence and an oscillating layout all exit 12.

Sort the outcomes into three instead.  A clean run succeeds silently.
A run latexmk itself blames on running out of reruns -- it says "Maximum
runs ... reached without getting stable files" and reports nothing else
wrong -- prints a loud, greppable warning and carries on into the bounded
rerun loop of #73, which exists for exactly this case and which d8dc836
had made unreachable.  Anything else still fails, with latexmk's status.

"Nothing else wrong" is two greps, and they are what keeps this from
being the old masking in a new disguise.  A run whose bibliography failed
leaves a "Collected error summary"; one whose -use-make delegation ran
a rule that failed leaves a "make[1]: *** [...] Error" behind.  Either
fails the rule even when latexmk also ran out of reruns -- and that
combination is exactly the shape of dbosk/nytid#455.

The second grep asks for a failed *recipe*, not merely for make having
been unhappy.  On a first pass make is routinely unhappy and rightly so:
latexmk hands it the name of every file LaTeX reported missing, the ones
LaTeX is about to write itself included, and make answers "No rule to
make target 'nytid.toc'" with nothing at all wrong.  A rule that ran and
came back non-zero is the different thing, and it reads differently.

Telling the cases apart needs latexmk's words rather than its status, so
each run is teed to a file next to the .log with its status put aside
separately: a pipeline reports tee's status, not latexmk's.  clean-tex
removes both files, since latexmk's own -C does not know about them.

The motivating document is nytid's, which contains a genuine two-cycle
page-break oscillation: a break moves one table-of-contents entry between
two pages and drags some thirty noweb sublabels after it, so latexmk
cannot converge whatever its rerun budget -- nine runs were not enough.
Under d8dc836 that correct document could not be built at all.

The DVI path gets the same treatment and becomes one recipe line, because
exit ends only the shell of the line it is written in.

Co-Authored-By: Claude Fable 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.

1 participant