Paper Compile #617
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
| name: Paper Compile | |
| # Compiles project papers to PDF in the llmXive house style. Runs: | |
| # - every 30 minutes (in case a paper was added without triggering paths) | |
| # - on push to any project's paper/source/ or to the llmxive.cls style | |
| # | |
| # Strategy (per scripts/compile_paper.py): | |
| # 1. Restyle the original arXiv source into a wrapper that loads llmxive.cls | |
| # 2. Compile with lualatex (3 passes + bibtex when applicable) | |
| # 3. On failure, fall back to fetching the canonical arXiv PDF so the | |
| # paper modal always has SOMETHING to show. | |
| # | |
| # Per-paper failures never fail the workflow; the next tick retries them. | |
| # Security: workflow_dispatch inputs are untrusted; surfaced to the shell | |
| # via env: vars (never interpolated directly into run: steps) and validated | |
| # by Python's argparse before reaching any subprocess. | |
| on: | |
| schedule: | |
| - cron: "*/30 * * * *" | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'projects/**/paper/source/**' | |
| - 'papers/.style/**' | |
| - 'scripts/compile_paper.py' | |
| - 'scripts/restyle_arxiv_paper.py' | |
| - '.github/workflows/paper-compile.yml' | |
| workflow_dispatch: | |
| inputs: | |
| project: | |
| description: "Restrict to one PROJ-NNN-... dir (empty = --all)" | |
| required: false | |
| default: "" | |
| max: | |
| description: "Cap on --all (default: 20)" | |
| required: false | |
| default: "20" | |
| permissions: | |
| actions: write # workflow_dispatch to pages.yml | |
| contents: write | |
| concurrency: | |
| group: paper-compile | |
| cancel-in-progress: false | |
| jobs: | |
| compile: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - name: Install TeX Live (lualatex + fontspec + standard collections) | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y --no-install-recommends \ | |
| texlive-luatex texlive-fonts-recommended texlive-fonts-extra \ | |
| texlive-latex-recommended texlive-latex-extra texlive-science \ | |
| texlive-publishers texlive-bibtex-extra biber \ | |
| ghostscript | |
| which lualatex && lualatex --version | head -1 | |
| - name: Install llmxive package | |
| run: pip install -e . | |
| - name: Compile papers | |
| env: | |
| INPUT_PROJECT: ${{ github.event.inputs.project }} | |
| INPUT_MAX: ${{ github.event.inputs.max }} | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${INPUT_PROJECT:-}" ]; then | |
| python3 scripts/compile_paper.py "projects/${INPUT_PROJECT}" | |
| else | |
| MAX="${INPUT_MAX:-20}" | |
| python3 scripts/compile_paper.py --all --max "$MAX" | |
| fi | |
| - name: Commit + push | |
| id: commit | |
| # Concurrent cron workflows write web/data/projects.json on every | |
| # tick (each one regens it). A naive pull-after-commit conflicts on | |
| # that file ~always when another tick raced ahead. Strategy: | |
| # 1. PDFs land in projects/<pid>/paper/pdf/ (no-conflict files — | |
| # only this cron writes those particular PDFs this minute). | |
| # 2. Fetch + merge main FIRST so we have the latest world. | |
| # 3. AFTER the pull, regenerate projects.json from the merged | |
| # state — guarantees we never commit a stale projects.json. | |
| # 4. Commit + push. On push race, repeat from step 2. | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Quick check: did the compile step produce any new PDFs? | |
| if [ -z "$(git status --porcelain 'projects/*/paper/pdf/*.pdf' 2>/dev/null)" ]; then | |
| echo "no PDF changes" | |
| echo "pushed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| for i in 1 2 3 4 5; do | |
| # Pull latest main BEFORE regenerating projects.json. Use stash | |
| # so any uncommitted PDFs survive the pull. | |
| git stash push --include-untracked --keep-index -m "compile-pdfs" 2>/dev/null || true | |
| if ! git pull --rebase --autostash origin main; then | |
| echo "pull/rebase failed on attempt $i; aborting + retrying" | |
| git rebase --abort 2>/dev/null || true | |
| git stash pop 2>/dev/null || true | |
| sleep $((5 * i)) | |
| continue | |
| fi | |
| git stash pop 2>/dev/null || true | |
| # Regenerate projects.json AFTER the pull so it reflects merged state. | |
| python -c "from llmxive.agents.status_reporter import regenerate_web_data; from pathlib import Path; regenerate_web_data(repo_root=Path('.'))" | |
| git add 'projects/*/paper/pdf/*.pdf' web/data/projects.json 2>/dev/null || true | |
| if git diff --cached --quiet; then | |
| echo "no changes after rebase (someone else compiled these)" | |
| echo "pushed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git commit -m "paper-compile: produce/refresh paper PDFs" | |
| if git push; then | |
| echo "pushed=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "push attempt $i failed (race?); resetting commit + retrying" | |
| git reset --soft HEAD~1 | |
| sleep $((5 * i)) | |
| done | |
| echo "push failed after 5 attempts" >&2 | |
| echo "pushed=false" >> "$GITHUB_OUTPUT" | |
| exit 1 | |
| - name: Trigger Pages deploy | |
| # GitHub suppresses workflow triggers from GITHUB_TOKEN pushes, so | |
| # pages.yml's `push` trigger never fires for bot commits. Explicit | |
| # workflow_dispatch DOES fire regardless of token provenance. | |
| if: steps.commit.outputs.pushed == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: gh workflow run pages.yml --ref main |