From 0977ae0be8f8c19213d33c40b045c5634590d895 Mon Sep 17 00:00:00 2001 From: Thomas Schmelzer Date: Fri, 9 Oct 2026 10:33:20 +0400 Subject: [PATCH] Add workflow compiling changed preprints Compile each preprints//source whose git tree hash differs from the one recorded in its preprint- release, or that has no release yet, and publish the PDF as that release's asset. Pull requests build but do not publish. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/preprints.yml | 156 ++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .github/workflows/preprints.yml diff --git a/.github/workflows/preprints.yml b/.github/workflows/preprints.yml new file mode 100644 index 0000000..32d7fad --- /dev/null +++ b/.github/workflows/preprints.yml @@ -0,0 +1,156 @@ +# Compile every preprint whose LaTeX source has changed since it was last built. +# +# Each preprint lives in preprints//source/. Its compiled PDF is +# published as the single asset of a GitHub release tagged preprint-, +# and the release notes record the git tree hash of the source folder it was +# built from. A preprint is compiled when +# +# - it has never been compiled (there is no release yet), or +# - the tree hash of its source folder differs from the recorded one. +# +# The tree hash covers every tracked file under source/, so any change to the +# LaTeX, the code that generates figures and tables, or the Makefile triggers +# a rebuild, and nothing else does. Source folders without a top-level .tex +# file containing \documentclass are skipped. +# +# On pull requests the changed preprints are compiled to check that they +# still build, and the PDFs are attached to the run, but nothing is released. + +name: Preprints + +on: + push: + branches: [main] + paths: + - "preprints/**" + - ".github/workflows/preprints.yml" + pull_request: + paths: + - "preprints/**" + - ".github/workflows/preprints.yml" + workflow_dispatch: + inputs: + force: + description: "Compile every preprint, changed or not" + type: boolean + default: false + +permissions: + contents: write + +concurrency: + group: preprints-${{ github.ref }} + cancel-in-progress: false + +jobs: + detect: + name: Find preprints to compile + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.scan.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + + - name: Compare source trees with the last release + id: scan + env: + GH_TOKEN: ${{ github.token }} + FORCE: ${{ inputs.force || false }} + run: | + set -euo pipefail + entries=() + for dir in preprints/*/source; do + [ -d "$dir" ] || continue + name=$(basename "$(dirname "$dir")") + + main=$(grep -lE '^[[:space:]]*\\documentclass' "$dir"/*.tex 2>/dev/null | head -n 1 || true) + if [ -z "$main" ]; then + echo "::notice::$name: no LaTeX document in $dir, skipping" + continue + fi + main=$(basename "$main" .tex) + + tree=$(git rev-parse "HEAD:$dir") + built=$(gh release view "preprint-$name" --json body --jq .body 2>/dev/null \ + | sed -nE 's/^source-tree: ([0-9a-f]+).*/\1/p' || true) + + if [ "$FORCE" = "true" ]; then + reason="forced" + elif [ -z "$built" ]; then + reason="never compiled" + elif [ "$built" != "$tree" ]; then + reason="source changed ($built -> $tree)" + else + echo "$name: up to date ($tree)" + continue + fi + echo "$name: $reason" + entries+=("$(jq -cn --arg name "$name" --arg main "$main" --arg tree "$tree" \ + '{name: $name, main: $main, tree: $tree}')") + done + matrix=$(printf '%s\n' ${entries[@]+"${entries[@]}"} | jq -cs .) + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + echo "Preprints to compile: $matrix" + + compile: + name: Compile ${{ matrix.preprint.name }} + needs: detect + if: needs.detect.outputs.matrix != '[]' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + preprint: ${{ fromJSON(needs.detect.outputs.matrix) }} + env: + NAME: ${{ matrix.preprint.name }} + MAIN: ${{ matrix.preprint.main }} + TREE: ${{ matrix.preprint.tree }} + SRC: preprints/${{ matrix.preprint.name }}/source + steps: + - uses: actions/checkout@v4 + + - name: Install TeX Live + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + texlive-latex-base texlive-latex-recommended texlive-latex-extra \ + texlive-fonts-recommended lmodern latexmk + + # Preprints with a Makefile may generate figures and tables in Python. + - name: Install uv + if: hashFiles(format('{0}/Makefile', env.SRC)) != '' + uses: astral-sh/setup-uv@v6 + + - name: Compile + working-directory: ${{ env.SRC }} + run: | + if [ -f Makefile ]; then + make compile + else + latexmk -pdf -interaction=nonstopmode -halt-on-error -file-line-error "$MAIN.tex" + fi + test -f "$MAIN.pdf" + cp "$MAIN.pdf" "$GITHUB_WORKSPACE/$NAME.pdf" + + - name: Attach PDF to the run + uses: actions/upload-artifact@v4 + with: + name: ${{ env.NAME }} + path: ${{ env.NAME }}.pdf + if-no-files-found: error + + # Releases cannot move their tag, so the old release is replaced. + - name: Publish release + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + env: + GH_TOKEN: ${{ github.token }} + run: | + tag="preprint-$NAME" + gh release delete "$tag" --cleanup-tag --yes 2>/dev/null || true + gh release create "$tag" "$NAME.pdf" \ + --target "$GITHUB_SHA" \ + --title "$NAME" \ + --latest=false \ + --notes "Compiled from \`$SRC\` at $GITHUB_SHA. + + source-tree: $TREE"