# Makefile for Hanzo Papers # Compiles every standalone .tex paper in the repo to a PDF under pdfs/. # # Usage: # make -k all # Build every paper, report all failures # make pdfs/hanzo-aso.pdf # Build one paper # make list # Every paper, one path per line # make clean # Remove aux files # make clean-all # Remove aux + PDFs # # A paper that does not compile fails. Every recipe line here used to end in # `|| true` and the rule printed "FAIL" instead of exiting, so `make all` # returned 0 over any number of broken papers — the gate in # .hanzo/workflows/ci.yml was green for as long as it existed. Use -k to build # the rest after one breaks; that is what -k is for, and it is the only reason # the old rule had to swallow the status. # # Docker (no local TeX install needed): # docker run --rm -v "$$PWD:/workdir" -w /workdir texlive/texlive:latest make -k all # A paper is any .tex with a \documentclass, wherever it lives. This is the one # definition of the paper set; scripts/gen-index.sh reads it from here rather # than restating it. Excluded: sections/ and shared/ hold fragments with no # \documentclass of their own, site/ is the website, pdfs/ is output, and # defense/logo-convert.tex is a helper that rasterises the logo through inkscape. # # The previous discovery was three wildcards over the root, zen/ and defense/, # which is 150 of the 219 papers in the repo. The other 69 live in per-paper # directories and were compiled by nothing. ALL_TEX := $(shell grep -rl '^\\documentclass' --include='*.tex' . \ | sed 's|^\./||' \ | grep -v -e '^sections/' -e '/sections/' -e '^shared/' -e '^site/' \ -e '^pdfs/' -e 'node_modules/' -e '^defense/logo-convert\.tex$$' \ | sort) # Map to output PDFs under pdfs/ ALL_PDFS := $(patsubst %.tex,pdfs/%.pdf,$(ALL_TEX)) # A recipe that exits nonzero leaves no PDF behind, so a stale artifact can # never stand in for a build that did not happen. .DELETE_ON_ERROR: # Default: build everything .PHONY: all all: $(ALL_PDFS) @echo "" @echo "=== Build complete ===" @echo "Papers compiled: $(words $(ALL_PDFS))" # One paper, one rule, wherever it lives. latexmk decides how many passes are # needed and runs bibtex; its exit status is the answer to "did this paper # compile?", which is the whole gate. # # -halt-on-error stop at the first error rather than nonstopmode's "carry on # and emit a PDF anyway" — a broken paper leaves no PDF # -bibtex run bibtex whenever a paper declares a bibliography, so # naming a .bib that does not exist fails instead of being # quietly skipped (latexmk's default) # -Werror an undefined citation or cross-reference is a defect in a # paper, not a note in a log nobody reads # -cd compile each paper from its own directory, so a paper's # relative paths mean the same thing to make as they do to a # human opening the file: zen/ finds references.bib, defense/ # finds hanzo-defense.sty, the root finds shared/ # -outdir absolute, because -cd moved us: PDF and aux land together # under pdfs/, never at the repo root # # A paper is more than its .tex. A figure, a section fragment, a generated # table, a .bib, a local .sty or the shared preamble all change the PDF, so all # of them have to change the build. Without them `make` answers "up to date" # over a stale PDF after a figure edit, which is the same silent success the old # `|| true` produced. # Secondary expansion is what makes $* usable in a pattern rule's prerequisites. # The sibling-.tex line is skipped for a paper that lives at the repository # root, where it would make all 150 of them depend on each other. # # latexmk keeps its own dependency record and skips a paper whose inputs have # not changed in content, leaving the PDF's timestamp behind a prerequisite # whose mtime moved but whose bytes did not. The touch after a successful run # records what latexmk just established, so make and latexmk agree on what is # current instead of one of them re-asking forever. .SECONDEXPANSION: pdfs/%.pdf: %.tex \ $$(wildcard $$(dir $$*)figures/*.tex) \ $$(wildcard $$(dir $$*)sections/*.tex) \ $$(wildcard $$(dir $$*)tables/*.tex) \ $$(if $$(filter ./,$$(dir $$*)),,$$(wildcard $$(dir $$*)*.tex)) \ $$(wildcard $$(dir $$*)*.bib) \ $$(wildcard $$(dir $$*)*.sty) \ $$(wildcard shared/*) @mkdir -p $(dir $@) @echo "Compiling $< ..." @latexmk -pdf -halt-on-error -bibtex -Werror -interaction=nonstopmode -quiet \ -cd -outdir=$(CURDIR)/$(dir $@) $< > $(dir $@)$(*F).latexmk 2>&1 \ || { echo " FAIL $<"; \ grep -A5 '^!' $(dir $@)$(*F).log; \ sed -n '/^Latexmk: Summary/,/^----/p' $(dir $@)$(*F).latexmk; \ false; } @touch $@ @echo " OK $@" # An arXiv upload is a paper's sources, which arXiv compiles without running # bibtex, from one flat directory. So it is the paper directory's tracked TeX, # the shared files it names with the ../shared/ prefix dropped, and the .bbl # latexmk wrote. The upload is then compiled alone, without bibtex, the way # arXiv will; one that needs anything outside itself fails here, not after # submission. For a paper in its own directory: make arxiv/attestation/attestation.tar.gz arxiv/%.tar.gz: pdfs/%.pdf @rm -rf arxiv/$* arxiv/$*.check && mkdir -p arxiv/$* @cd $(dir $*) && git ls-files -- '*.tex' '*.sty' '*.cls' '*.bst' '*.pdf' '*.png' '*.jpg' \ | tar -cf - -T - | tar -xf - -C $(CURDIR)/arxiv/$* @for n in $$(grep -rhoE '\.\./shared/[A-Za-z0-9_-]+' arxiv/$* | sort -u | sed 's|.*/||'); do \ cp shared/$$n.* arxiv/$*/; done @find arxiv/$* -name '*.tex' -exec sed -i.orig 's|\.\./shared/||g' {} + && find arxiv/$* -name '*.orig' -delete @cp pdfs/$*.bbl arxiv/$*/ @cp -R arxiv/$* arxiv/$*.check && cd arxiv/$*.check \ && latexmk -pdf -bibtex- -halt-on-error -interaction=nonstopmode -quiet $(notdir $*).tex > /dev/null 2>&1 \ || { echo " FAIL arxiv/$*: the upload does not compile alone"; false; } @rm -rf arxiv/$*.check && tar -czf $@ -C arxiv/$* . @echo " OK $@ ($$(tar -tzf $@ | wc -l | tr -d ' ') files)" # Clean auxiliary files (keep PDFs). The second sweep clears what the previous # rule left at the repo root; latexmk writes beside the PDF and never there. .PHONY: clean clean: @mkdir -p pdfs @find pdfs -type f ! -name '*.pdf' -delete @find . -maxdepth 1 \( -name '*.aux' -o -name '*.log' -o -name '*.out' \ -o -name '*.toc' -o -name '*.bbl' -o -name '*.blg' \ -o -name '*.fls' -o -name '*.fdb_latexmk' \) -delete @echo "Cleaned." # Clean everything .PHONY: clean-all clean-all: @echo "Removing all generated files..." @rm -rf pdfs @echo "Done" # Every paper, one per line. scripts/gen-index.sh reads this. .PHONY: list list: @printf '%s\n' $(ALL_TEX) .PHONY: help help: @echo "Hanzo Papers Makefile" @echo "" @echo "Targets:" @echo " make -k all - Compile all $(words $(ALL_TEX)) papers, report every failure" @echo " make pdfs/.pdf - Compile a specific paper" @echo " make arxiv//.tar.gz - arXiv upload of a paper in its own directory" @echo " make list - List all discovered papers" @echo " make clean - Remove auxiliary files" @echo " make clean-all - Remove all generated files" @echo "" @echo "Docker:" @echo " docker run --rm -v \"\$$PWD:/workdir\" -w /workdir texlive/texlive:latest make -k all" .PHONY: index index: @scripts/gen-index.sh