Conversation
The prose examples like [[<<module_name.py>>=]] made noweave parse the inner <<...>> as a real chunk reference: the raw underscore in the chunk name reached LaTeX unescaped (breaking compilation with "Missing $ inserted") and the reference linked to a never-defined chunk. Examples quoting the [[...]] notation itself, like [[[[...]]]], truncated at the first ]] and left stray bracket text in the PDF. Literal chunk syntax is now written with noweb's @<<...@>> escape inside [[...]], and literal double brackets with \texttt and split brackets, so the document compiles again and the examples render as intended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
NOWEAVE.tex now weaves through noweb's autolang and tominted filters (dbosk noweb fork): each chunk is syntax-highlighted with minted using a per-chunk lexer, and -autodefs python3 fills the identifier index with -autolang gating the autodefs filters so chunks in other languages stay out of the index. The chunk-name-equals-filename convention our tangling rules already enforce is what drives the language inference. The bundled Pygments lexer keeps chunk references hyperlinked inside Python strings; noweb.mk provides a rule copying it from noweb's library directory (read from the LIB= line of the noweave script), and documents declare a dependency on it, as makefiles.pdf now does. Documents must load minted and compile with -shell-escape, which our own build already did. NOWEAVE.pdf keeps the classic rendering: its preamble is generated by noweave and cannot load minted. Everything is set with ?=, so projects without the fork override NOWEAVEFLAGS.tex. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The autodefs filters now index make targets such as upload and autocommit, which turns every [[...]] quote of those names into a hyperlinked identifier use --- including the ones in section headings, where hyperref cannot survive a link inside the moving argument (the PDF bookmark breaks with "Undefined control sequence \hyper@@link"). Headings now write \texttt where body prose keeps the quoted, linked form. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
noweb now ships autodefs filters for shell and make (and Haskell, Rust and Java), gated by the same @language annotations -autolang provides, so stacking them indexes every chunk by exactly the filter that understands it. The default stack --- Python, shell, make --- covers what our projects mix into one document: program modules, helper scripts and their build files; our own woven documentation now indexes its make variables and targets. The stack stays at three because noweave has seven filter slots and -autolang, each -autodefs and tominted occupy one each; the default leaves two slots free for project filters, and other languages remain a one-line NOWEAVEFLAGS.tex override. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The caution claimed the default stack leaves two of noweave's seven filter slots free. It missed that -index inserts two filters (finduses + noidx); with -autolang and tominted also taking one each, the three-autodefs default fills all seven exactly. Adding a fourth -autodefs or a project -filter overflows unless something is dropped. Prose-only change in a documentation chunk; the tangled noweb.mk is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Replace the seven-filter-slot caution with the dbosk fork's unbounded
pipeline; keep the old ceiling only as a note for pre-fork noweave.
- Note that the preamble loads minted through the noweb package
(\usepackage[minted]{noweb}) rather than a hand-added \usepackage{minted}.
- Record that noweave -minted can load minted into the generated preamble,
so the standalone .pdf weave stays classic by choice, not necessity.
Documentation only; the tangled noweb.mk is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hand-added \usepackage{minted} (plus a separate
\usepackage{noweb}) with \usepackage[minted]{noweb}, so the minted
dependency rides the noweb package the way tominted weaves expect.
\setminted{autogobble} still applies, as minted is loaded (via
\AtEndOfPackage) before it runs.
Requires the noweb fork whose noweb.sty declares the minted option;
to pass minted options such as outputdir, load minted directly before
\usepackage{noweb} instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dbosk fork's noweave, with -index, runs -autolang's inference as a pre-pass to discover which languages a document contains and stacks the matching autodefs filter for each. The default weave therefore no longer lists -autodefs python3/sh/make: discovery covers them, and an explicit -autodefs is still merged without duplication for chunks whose language the name does not reveal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Test chunks follow the convention <<test [[foo.py]]>>, labelling the file rather than being it, and autolang deliberately does not infer a language from such a labelled name. Left unclassified, a test chunk is scanned by every autodefs filter and salts the index with cross-language false matches. Add one -langrule per supported language (python, shell, make) so each test chunk is classified, and hence indexed, by the same filter as the module it exercises. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A pattern rule with several targets is one rule whose recipe must produce all of them, not a shorthand for several rules. The general tangling rule listed every supported suffix as a target, but notangle(1) only ever extracts the chunk named by $@, so GNU make 4.3 and later warned noweb.mk:99: warning: pattern recipe did not update peer target 'X.c'. once per unmade peer. Building the canvaslms CLI modules emitted 238 such warnings, drowning out real errors. Generate one independent pattern rule per suffix instead, the same way the suffixed-source rules are already generated. The tangled output is unchanged: all 17 CLI modules tangle byte-identically, with zero warnings.
The PythonTeX custom dependency in latexmkrc invoked pythontex3. That wrapper existed only to select the Python 3 interpreter while pythontex still defaulted to Python 2; TeX Live no longer ships it (TeX Live 2025 installs pythontex alone), so the cus_dep could only ever fail with "command not found". Call pythontex and pass --interpreter python:python3 explicitly, matching the default of PYTHONTEXFLAGS in tex.mk.
COMPILE.tex ran latexmk and then a bounded rerun loop, joined with ";". A recipe reports the status of its last command, and that is the loop, which ends by running "break" -- so every latexmk failure was discarded and "make" reported success on a document that had not compiled. The same held for the reruns on the DVI path. End every latexmk invocation with "|| exit $?" so the first failing pass ends the recipe with its own status, instead of spending the remaining passes on a document already known to be broken. Verified with a minimal document containing an undefined control sequence: "make bad.pdf" exited 0 before this change and exits 2 after it, while a well-formed document still exits 0.
PythonTeX was wired in with
ifneq (${TEX_PYTHONTEX},)
${TEX_OUTDIR}/%.pdf: ${TEX_OUTDIR}/%.pytxmcr
endif
which does nothing: a recipe-less *pattern* rule cannot build anything, so
make never selects it and never attaches its prerequisites to the pattern
rule that does have a recipe. PythonTeX therefore ran only through
latexmk's -use-make fallback, which fires only while the .pytxmcr file is
missing. On every later build an edited pycode block kept the previous
run's output.
Attaching the prerequisite to the rule with the recipe would not have been
enough either: the chain from .pytxmcr bottoms out in the master .tex file,
while the executed code normally lives in a chapter the master inputs --- and
those inputs are declared as prerequisites of the PDF, not of the .aux file.
Make it a phase of COMPILE.tex instead, between the pass that writes the
.pytxcode file and the pass that reads the macros back, so PythonTeX is
consulted whenever the PDF is rebuilt for any reason the project's makefile
knows about. This is cheap: pythontex re-executes only the sessions whose
hash changed, and latexmk skips the extra pass unless the .pytxmcr file
actually changed.
Verified with a document whose \input'ed chapter holds the pycode block:
editing the block and rebuilding incrementally kept the old output before
this change and produces the new output after it. A pycode block that exits
nonzero now fails the build (exit 2) instead of passing silently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four build-infrastructure fixes, found while canvaslms's manual quietly typeset broken examples (dbosk/canvaslms#459):
COMPILE.tex's rerun loop ended withbreak, so the recipe's status discarded every latexmk failure — a broken document built "green". Every latexmk call now ends|| exit $?. Verified: a deliberately broken document goes from exit 0 to exit 2, a good one stays 0.ifneq (${TEX_PYTHONTEX},) ${TEX_OUTDIR}/%.pdf: ${TEX_OUTDIR}/%.pytxmcr endifblock was a complete no-op — a recipe-less pattern rule adds no prerequisites in GNU make, so PythonTeX only ever ran via latexmk's missing-file fallback and edited pycode blocks kept stale output on incremental builds. PythonTeX is now a phase ofCOMPILE.tex(newPYTHONTEX_PASS, active only whenTEX_PYTHONTEXis set): pythontex + one extra latexmk pass between pass 1 and the rerun loop. Verified on a synthetic document and on canvaslms's 888-page manual: an edited pycode block now reaches the PDF on an incremental rebuild, and a failing block fails the build.pythontex3does not exist in TeX Live 2025; nowpythontex --interpreter python:python3.$(addprefix %,${NOWEB_SUFFIXES}): %.nwmade GNU make warn "pattern recipe did not update peer target" for every tangle (238 warnings per canvaslms CLI build). Replaced with one independent single-target pattern rule per suffix (samedefine/foreach/evalidiom aswith_suffix_target); not grouped targets (&:), since one tangle genuinely does not produce the peers. Verified: 0 warnings, all tangled outputs byte-identical.Note on #82: its suggested deletion of the rule would break the plain
%.py: %.nwform —with_suffix_targetonly covers%.py: %.py.nw. The independent-rules-per-suffix alternative it mentions is what this implements.Test plan
Fixes #85
Fixes #82
🤖 Generated with Claude Code
https://claude.ai/code/session_01LP19qHRs8y2afoFxiefNBj