From 5c31ef1b969f5464183001427cf2d63059bd3720 Mon Sep 17 00:00:00 2001 From: Luca Toniolo <10792599+grandixximo@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:55:07 +1000 Subject: [PATCH] docs(build): name the real target in the generated dependencies asciideps derives the target it emits by stripping a `.txt` suffix, which is what the docs were written in when it landed in 2011. Since the sources became `.adoc` nothing is stripped, so it generates rules for targets like docs/build/adoc/en/hal/tutorial.adoc.html that nothing builds: every include:: and image: dependency it has extracted since then has been inert. Editing hal/halshow.adoc therefore never re-rendered hal/tutorial.html, which include::s it, and editing a chapter never rebuilt the Master PDF pulling it in. Master_Documentation.adoc alone include::s 119 files, so all four English PDFs stayed stale until docclean. The target is not derivable from the source path, so pass it in: a page is consumed by its .html, a Master document is never rendered as HTML and its English PDF lands in objects/. The unused `.dep` line went with the rewrite. Reconnecting the dependencies exposed three latent problems, fixed here: - Commented-out image: macros counted as dependencies. The macro is matched anywhere on the line so inline refs are caught, which also matched a ref behind a `//`; one in gui/gmoccapy.adoc points at a since-deleted image, stopping the build. - Images referenced inline were never staged into the translated adoc tree, because .adoc-images-stamp anchored its scan at the start of the line. The translated PDFs embed images from that tree, so they have been missing all 39 of them. Both extractors now key on the macro shape rather than a bare `image:`, so prose mentioning one is not taken for a path. - Staged translated images had no rule of their own. Named as prerequisites, a clean -j build reached one before the bulk stamp had written it and fell through to the "Required image file" error rule. Each now stages on demand; the bulk pass stays, since asciideps records only the last macro on a line and cannot be the guarantee that asciidoctor-pdf finds every image. Verified with HTML, PDF and the seven translations enabled: a clean build renders 5210 pages and 32 Master PDFs with no unresolved directives, the two builds after it do no work, and touching a page, a partial, a chapter or an image rebuilds exactly what depends on it in a single `make`. --- docs/src/Submakefile | 59 ++++++++++++++++++++++++++++++++++++-------- docs/src/asciideps | 30 ++++++++++++++++------ 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/docs/src/Submakefile b/docs/src/Submakefile index cf81738decf..62567b56916 100644 --- a/docs/src/Submakefile +++ b/docs/src/Submakefile @@ -1088,21 +1088,29 @@ $(DOC_OUT_HTML)/en/pdf/LinuxCNC_Manual_Pages_en.pdf: objects/LinuxCNC_Manual_Pag || (X=$$?; rm -f $@ $@.raw; exit $$X) @test -f $@ +# Target a page's include:: and image: prerequisites hang off. A page is +# consumed by its .html; a Master document has no HTML, only the PDF that +# pulls its chapters in, and the English ones render into objects/. Not +# derivable from the source path, so asciideps is told. +EN_DEP_TARGET = $(if $(filter Master_%,$*),objects/$*.pdf,$(DOC_OUT_ADOC)/en/$*.html) + depends/%.d: $(DOC_OUT_ADOC)/en/%.adoc $(DOC_SRCDIR)/asciideps .include-stamp | stage-en $(ECHO) Depending $< @mkdir -p $(dir $@) - $(Q)$(DOC_SRCDIR)/asciideps $< > $@.tmp + $(Q)$(DOC_SRCDIR)/asciideps $< $(EN_DEP_TARGET) > $@.tmp @mv $@.tmp $@ # Translated .adoc lives in $(DOC_OUT_ADOC)//; same recipe, just a # different source dir so the per-language depends//X.d targets # resolve. The pattern only matches paths starting with a known language # tag, so the English depends/%.d above wins for non-translated entries. +# Translated Master PDFs render beside their source, so unlike English the +# target is the sibling .pdf. define TRANSLATED_DEP_RULE depends/$(1)/%.d: $(DOC_OUT_ADOC)/$(1)/%.adoc $(DOC_SRCDIR)/asciideps .include-stamp | stage-en $$(ECHO) Depending $$< @mkdir -p $$(dir $$@) - $$(Q)$$(DOC_SRCDIR)/asciideps $$< > $$@.tmp + $$(Q)$$(DOC_SRCDIR)/asciideps $$< $$(if $$(filter Master_%,$$*),$(DOC_OUT_ADOC)/$(1)/$$*.pdf,$(DOC_OUT_ADOC)/$(1)/$$*.html) > $$@.tmp @mv $$@.tmp $$@ endef $(foreach L,$(LANGUAGES),$(eval $(call TRANSLATED_DEP_RULE,$(L)))) @@ -1191,6 +1199,11 @@ $(foreach L,$(LANGUAGES),$(eval $(call HTML_COPY_RULE,$(L)))) # has no work to do (filter-out yields empty) so we skip the dep too. HTML # image placement is no longer done here: the image_resolver rewrites HTML src # to the shared pool and .html-images-stamp materialises it. +# +# Matched anywhere on the line, like asciideps, so both see the same set. +# Anchored at the start it missed the 39 inline refs, leaving those images +# out of the translated PDFs that embed them from here. Keyed on the macro +# shape, or prose mentioning image:: is taken for a path and the copy fails. ifeq ($(BUILD_DOCS_TRANSLATED),yes) ADOC_IMAGES_STAMP_DEPS := $(DOC_DIR)/.translateddocs-stamp endif @@ -1198,7 +1211,7 @@ endif set -e; for ADOC_FILE in $(addprefix $(DOC_OUT_ADOC)/, $(filter-out $(DOC_SRCS_EN), $(DOC_SRCS))); do \ ADOC_DIR=$$(echo $$(dirname $$ADOC_FILE) | sed s%$(DOC_OUT_ADOC)/%% ); \ echo Processing $$ADOC_FILE, dir $$ADOC_DIR; \ - for IMAGE_FILE in $$(grep -E ^image:[^[:space:]] $$ADOC_FILE | sed -E "s/image:+([^[]+)\[/\nimage:\1\n/g" | grep image: | cut -d: -f2-); do \ + for IMAGE_FILE in $$(grep -v '^[[:space:]]*//' $$ADOC_FILE | grep -oE 'image::?[^[:space:][]+\[' | sed -E 's/^image::?//; s/\[$$//'); do \ IMAGE_DIR=$$(dirname $$IMAGE_FILE); \ IMAGE_PATH=$$(echo $(DOC_SRCDIR)/$$ADOC_DIR/$$IMAGE_FILE | sed -E 's%/src/($(LANGUAGES_MATCH))/%/src/%'); \ if [ ! -e $$IMAGE_PATH ] ; then \ @@ -1214,6 +1227,24 @@ endif done; \ done > $@.new && mv $@.new $@ +# Same staging, one file at a time. The dependency files name these images, +# so a clean -j build reaches one before the bulk pass has written it and +# falls through to the "Required image file" error at the end of this file. +# Source lookup as above: the English original, else the English build tree +# for images generated there, such as the .dot-rendered SVGs. +ifeq ($(BUILD_DOCS_TRANSLATED),yes) +define TRANSLATED_IMAGE_RULE +$(DOC_OUT_ADOC)/$(1)/%.$(2): | $(DOC_DIR)/.translateddocs-stamp + @mkdir -p $$(@D) + $$(Q)S=$(DOC_SRCDIR)/$$*.$(2); \ + [ -e "$$$$S" ] || S=$(DOC_OUT_ADOC)/en/$$*.$(2); \ + cp -f "$$$$S" $$@ +endef +$(foreach L,$(LANGUAGES), \ + $(foreach E,png jpg jpeg gif svg, \ + $(eval $(call TRANSLATED_IMAGE_RULE,$(L),$(E))))) +endif + # Relative path from this html target back to $(DOC_OUT_HTML)/, used to # point the lcnc-overrides.css in docinfo.html at the right place. # Every output now lives under a $(DOC_OUT_HTML)// subtree, so depth @@ -1282,24 +1313,32 @@ endef # components_gen.adoc generates straight into build/adoc/en/hal; svgs_made_from_dots # runs first. # -# One rule per staged file, not a bulk find|tar behind a stamp with the staged copies hanging off it order-only. -# make stats a prerequisite once per run and re-stats it only if it decided to remake it, and an order-only edge never triggers a remake. -# A bulk copy that rewrote the staged .adoc mid-run was therefore invisible to the renderer, which had already compared the .html against the pre-copy mtime, so editing a page took two `make` runs to reach the HTML: the first re-staged, the second rendered. -# With a real recipe per file make knows the staged copy changed and the render fires in the same run. +# One rule per staged file, not a bulk copy behind a stamp with the staged +# copies hanging off it order-only. make stats a prerequisite once per run +# and re-stats it only if it decided to remake it, which an order-only edge +# never triggers, so a bulk copy rewriting the staged .adoc mid-run stayed +# invisible and editing a page took two `make` runs to reach the HTML. EN_STAGE_TYPES := -name '*.adoc' -o -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' -o -name '*.gif' -o -name '*.svg' -o -name '*.py' EN_STAGE_SRCS := $(shell find $(DOC_SRCDIR) \( $(EN_STAGE_TYPES) \)) EN_STAGED := $(patsubst $(DOC_SRCDIR)/%,$(DOC_OUT_ADOC)/en/%,$(EN_STAGE_SRCS)) -# cp -p keeps the source mtime to the nanosecond; tar rounded it down to the whole second, which could leave a staged copy a fraction older than a .html rendered from it in the same second and lose that edit entirely. +# cp -p keeps the source mtime to the nanosecond. tar rounded down to the +# whole second, which could leave a staged copy a shade older than a .html +# rendered from it in that same second and lose the edit. $(EN_STAGED): $(DOC_OUT_ADOC)/en/%: $(DOC_SRCDIR)/% | svgs_made_from_dots @mkdir -p $(@D) $(Q)cp -p $< $@ -# Stage the whole set before anything reads it, the way the bulk copy did. On-demand staging is not enough: asciidoctor resolves include:: and image: against the staged tree at render time, and asciideps recurses into included files there, but only a fraction of those are make prerequisites. Leave it to demand and a partial like hal/halshow.adoc never lands, so the page that includes it renders an "Unresolved directive" placeholder instead of its content. +# Stage the whole set up front, as the bulk copy did. On demand is not +# enough: asciidoctor resolves include:: and image: against this tree at +# render time and asciideps recurses into it, yet few of those files are make +# prerequisites. Left to demand, a partial like hal/halshow.adoc never lands +# and the page including it renders an "Unresolved directive" placeholder. .PHONY: stage-en stage-en: $(EN_STAGED) -# Staged files are used only as prerequisites: mark .SECONDARY so make does not delete them as intermediates mid -j build (like the .SECONDARY above). +# Staged files are used only as prerequisites: mark .SECONDARY so make does +# not delete them as intermediates mid -j build (like the .SECONDARY above). .SECONDARY: $(EN_STAGED) # English now renders from build/adoc/en, the same model as the translations. diff --git a/docs/src/asciideps b/docs/src/asciideps index b1bd54dcd85..0a78a0856cf 100755 --- a/docs/src/asciideps +++ b/docs/src/asciideps @@ -2,8 +2,18 @@ set -e -test -z "$1" && exit 0 -test -f "$1" || exit 1 +# usage: asciideps SOURCE.adoc [TARGET...] +# +# Emit a make rule hanging SOURCE's include:: and image: prerequisites off +# TARGET. The caller names the target because it is not derivable from the +# source path: a Master document has no .html and its English PDF lands in +# objects/. Defaults to the source stem, for a standalone run. +SRC=$1 +[ -z "$SRC" ] && exit 0 +[ -f "$SRC" ] || { echo "asciideps: '$SRC' not a file" >&2; exit 1; } +shift +TARGETS="$*" +[ -n "$TARGETS" ] || TARGETS="${SRC%.adoc}.html" includestack=( ) @@ -31,15 +41,19 @@ includes () { images() { DIR=$(dirname "$1") - sed -ne "s|^.*image:\{1,2\}\([^[]*\)\[.*\].*$|$DIR/\1|p" "$1" | tr '\n' ' ' + # Drop line comments first: the macro is matched anywhere on the line to + # catch inline refs, which otherwise also matches a commented-out one. + # That is no dependency, and requiring it breaks the build once the file + # it names is removed. + grep -v '^[[:space:]]*//' "$1" \ + | sed -ne "s|^.*image:\{1,2\}\([^[]*\)\[.*\].*$|$DIR/\1|p" \ + | tr '\n' ' ' } -INCLUDES=$(includes "$1" | tr '\n' ' ') -IMAGES=$(images "$1") +INCLUDES=$(includes "$SRC" | tr '\n' ' ') +IMAGES=$(images "$SRC") for f in $INCLUDES; do IMAGES="$IMAGES $(images "$f")" done -echo "${1%%.txt}.dep: $INCLUDES" -echo "${1%%.txt}.html: $INCLUDES $IMAGES" -echo "${1%%.txt}.pdf: $INCLUDES $IMAGES" +echo "$TARGETS: $INCLUDES $IMAGES"