Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
289 lines
13 KiB
YAML
289 lines
13 KiB
YAML
name: Build latest book artifacts
|
|
|
|
# Rebuilds the PDF and EPUB editions whenever the book sources change and
|
|
# uploads them to the rolling "latest" pre-release (assets are overwritten in
|
|
# place, so the download URLs stay stable). Versioned releases (v1.2, ...) are
|
|
# still cut manually at milestones.
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'book/**'
|
|
- 'book-zhtw/**'
|
|
- 'book-en/**'
|
|
- 'book-es/**'
|
|
- 'book-id/**'
|
|
- 'book-ru/**'
|
|
- 'book-ta/**'
|
|
- 'book-vi/**'
|
|
- 'book-ja/**'
|
|
- 'book-ko/**'
|
|
- 'book-ar/**'
|
|
- 'book-tr/**'
|
|
- 'book-hu/**'
|
|
- 'book-he/**'
|
|
- 'build_epub.sh'
|
|
- 'epub.css'
|
|
- 'epub_external_links.lua'
|
|
- 'flatten_epub_toc.py'
|
|
- '.github/workflows/build-latest.yml'
|
|
- '.github/scripts/**'
|
|
workflow_dispatch:
|
|
inputs:
|
|
edition:
|
|
description: Edition to verify on a non-main ref
|
|
required: true
|
|
default: all
|
|
type: choice
|
|
options:
|
|
- all
|
|
- zh-CN
|
|
- zh-TW
|
|
- en
|
|
- es
|
|
- id
|
|
- ru
|
|
- ta
|
|
- vi
|
|
- tr
|
|
- ko
|
|
- hu
|
|
- ja
|
|
- ar
|
|
- he
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
concurrency:
|
|
# Serialize runs for the same ref without allowing a newer push to cancel
|
|
# a long-running build that is already in progress.
|
|
group: build-latest-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: macos-latest
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
|
|
# ── Build-environment caching ──────────────────────────────────────────
|
|
# GitHub-hosted runners are ephemeral, so the ~10 min of tool/font
|
|
# installs re-runs on every push. The two heavy pieces — the MacTeX tree
|
|
# (~5 min) and the document fonts (~2 min) — are cached across runs and
|
|
# only reinstalled on a cache miss.
|
|
#
|
|
# Bump the trailing "-vN" in a cache key whenever you change what that
|
|
# cache holds (the mactex package, or the font cask list / Apple-font
|
|
# script) so the next run rebuilds it instead of restoring a stale tree.
|
|
|
|
# Homebrew cask fonts AND the Apple PingFang download all land in
|
|
# ~/Library/Fonts, so one user-writable directory captures every font.
|
|
- name: Restore fonts cache
|
|
id: fonts-cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/Library/Fonts
|
|
key: fonts-${{ runner.os }}-${{ runner.arch }}-v4-${{ hashFiles('.github/scripts/install_apple_fonts.sh') }}
|
|
|
|
# The MacTeX tree is installed by a root pkg into /usr/local/texlive, so it
|
|
# can't be extracted straight back as the runner user. Cache a tarball
|
|
# instead (a user-owned file actions/cache can read) and restore it with
|
|
# sudo. /Library/TeX holds the texbin symlinks and is tarred alongside.
|
|
- name: Restore TeX Live cache
|
|
id: texlive-cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/texlive-cache/texlive.tar
|
|
key: texlive-${{ runner.os }}-${{ runner.arch }}-mactex-nogui-v1
|
|
|
|
- name: Install CLI tools
|
|
env:
|
|
HOMEBREW_NO_AUTO_UPDATE: '1'
|
|
HOMEBREW_NO_INSTALL_CLEANUP: '1'
|
|
run: brew install pandoc poppler librsvg epubcheck
|
|
|
|
- name: Install MacTeX (cache miss)
|
|
if: steps.texlive-cache.outputs.cache-hit != 'true'
|
|
env:
|
|
HOMEBREW_NO_AUTO_UPDATE: '1'
|
|
HOMEBREW_NO_INSTALL_CLEANUP: '1'
|
|
run: |
|
|
brew install --cask mactex-no-gui
|
|
mkdir -p ~/texlive-cache
|
|
sudo tar -cf ~/texlive-cache/texlive.tar -C / usr/local/texlive Library/TeX
|
|
sudo chown "$USER" ~/texlive-cache/texlive.tar
|
|
|
|
- name: Restore MacTeX from cache (cache hit)
|
|
if: steps.texlive-cache.outputs.cache-hit == 'true'
|
|
run: sudo tar -xf ~/texlive-cache/texlive.tar -C /
|
|
|
|
- name: Put TeX on PATH
|
|
run: echo "/Library/TeX/texbin" >> "$GITHUB_PATH"
|
|
|
|
# PingFang is an on-demand font since macOS Sequoia and is absent on fresh
|
|
# runners (only an unusable reserved UI copy exists); without it
|
|
# rsvg-convert renders Chinese figure text in a fallback font (Japanese
|
|
# glyph variants). The Apple-font script fetches the real font from Apple's
|
|
# asset CDN. Arabic and Korean fonts are required by release builds; the
|
|
# Japanese Noto fonts (WIP ja build) remain non-fatal so a bad cask name
|
|
# can never break the release build for the other languages.
|
|
- name: Install fonts (cache miss)
|
|
if: steps.fonts-cache.outputs.cache-hit != 'true'
|
|
env:
|
|
HOMEBREW_NO_AUTO_UPDATE: '1'
|
|
HOMEBREW_NO_INSTALL_CLEANUP: '1'
|
|
run: |
|
|
brew install --cask font-dejavu font-noto-sans-cjk-sc font-noto-sans-cjk-tc font-noto-serif-tamil font-amiri font-noto-naskh-arabic font-noto-sans-arabic
|
|
brew install --cask font-noto-sans-cjk-jp || true
|
|
brew install --cask font-noto-serif-cjk-jp || brew install --cask font-noto-serif-cjk || true
|
|
brew install --cask font-noto-sans-cjk-kr
|
|
brew install --cask font-noto-serif-cjk-kr || brew install --cask font-noto-serif-cjk
|
|
bash .github/scripts/install_apple_fonts.sh
|
|
|
|
# Refresh fontconfig on every run (the cache restore doesn't touch it).
|
|
# PANGOCAIRO_BACKEND=fontconfig at build time: pango's CoreText backend on
|
|
# the runners can't use the user-installed fonts and silently substitutes
|
|
# another CJK font in SVG figures; the fontconfig backend picks them up.
|
|
- name: Refresh font cache
|
|
run: fc-cache -f
|
|
|
|
# Each language is built in its own background job so the XeLaTeX
|
|
# passes run concurrently instead of back-to-back. Output is captured per
|
|
# language and printed after all jobs finish (interleaved live logs would
|
|
# be unreadable). The ja build (WIP) runs in the same batch but is
|
|
# non-fatal — only a main-language failure fails the step.
|
|
# macOS runners ship bash 3.2, so no associative arrays: pid↔dir pairing
|
|
# is carried in a "pid:dir" string list.
|
|
- name: Build PDFs
|
|
env:
|
|
PANGOCAIRO_BACKEND: fontconfig
|
|
EDITION: ${{ inputs.edition || 'all' }}
|
|
run: |
|
|
set -u
|
|
if [ "$GITHUB_REF" = "refs/heads/main" ] || [ "$EDITION" = "all" ]; then
|
|
main="book book-zhtw book-en book-es book-id book-ru book-vi book-ta book-ar book-tr book-ko book-hu book-he"
|
|
optional="book-ja"
|
|
else
|
|
# workflow_dispatch on a feature branch is a focused edition
|
|
# verification run. Release publication remains gated to main.
|
|
case "$EDITION" in
|
|
zh-CN) main="book" ;;
|
|
zh-TW) main="book-zhtw" ;;
|
|
en) main="book-en" ;;
|
|
es) main="book-es" ;;
|
|
id) main="book-id" ;;
|
|
ru) main="book-ru" ;;
|
|
ta) main="book-ta" ;;
|
|
vi) main="book-vi" ;;
|
|
tr) main="book-tr" ;;
|
|
ko) main="book-ko" ;;
|
|
hu) main="book-hu" ;;
|
|
ja) main="book-ja" ;;
|
|
ar) main="book-ar" ;;
|
|
he) main="book-he" ;;
|
|
*) echo "::error::Unsupported edition: $EDITION"; exit 2 ;;
|
|
esac
|
|
optional=""
|
|
fi
|
|
pids=""
|
|
for dir in $main $optional; do
|
|
( cd "$dir" && bash build_pdf.sh ) > "build_${dir}.log" 2>&1 &
|
|
pids="$pids $!:$dir"
|
|
done
|
|
fail=0
|
|
for pd in $pids; do
|
|
pid="${pd%%:*}"; dir="${pd##*:}"
|
|
if wait "$pid"; then
|
|
echo "OK: $dir"
|
|
elif [ "$dir" = "book-ja" ]; then
|
|
echo "::warning::$dir PDF build failed (WIP, non-fatal)"
|
|
else
|
|
echo "::error::PDF build failed for $dir"
|
|
fail=1
|
|
fi
|
|
done
|
|
for dir in $main $optional; do
|
|
echo "===================== $dir ====================="
|
|
cat "build_${dir}.log" 2>/dev/null || true
|
|
done
|
|
exit $fail
|
|
|
|
- name: Verify Chinese figure fonts
|
|
if: github.ref == 'refs/heads/main'
|
|
run: |
|
|
python3 .github/scripts/verify_pdf_fonts.py \
|
|
"book/深入理解-AI-Agent-李博杰-v2.0.pdf" \
|
|
"book-zhtw/深入理解-AI-Agent-李博杰-v2.0-zhtw.pdf"
|
|
|
|
- name: Build EPUBs
|
|
env:
|
|
EDITION: ${{ inputs.edition || 'all' }}
|
|
run: |
|
|
if [ "$GITHUB_REF" = "refs/heads/main" ] || [ "$EDITION" = "all" ]; then
|
|
./build_epub.sh all
|
|
else
|
|
./build_epub.sh "$EDITION"
|
|
fi
|
|
|
|
# WIP: ja EPUB depends on the ja PDF above; non-fatal, not part of `all`.
|
|
- name: Build Japanese EPUB (WIP, non-fatal)
|
|
if: github.ref == 'refs/heads/main'
|
|
continue-on-error: true
|
|
run: ./build_epub.sh ja
|
|
|
|
- name: Build Arabic EPUB (WIP, non-fatal)
|
|
if: github.ref == 'refs/heads/main'
|
|
continue-on-error: true
|
|
run: ./build_epub.sh ar
|
|
|
|
- name: Upload verification artifacts
|
|
if: github.repository != 'bojieli/ai-agent-book' || github.ref != 'refs/heads/main'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: book-verification-${{ inputs.edition || 'all' }}
|
|
path: |
|
|
book*/*.pdf
|
|
book*/*.epub
|
|
if-no-files-found: error
|
|
|
|
- name: Upload to the rolling latest release
|
|
if: github.repository == 'bojieli/ai-agent-book' && github.ref == 'refs/heads/main'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -e
|
|
out="$(mktemp -d)"
|
|
cp "book/深入理解-AI-Agent-李博杰-v2.0.pdf" "$out/AI-Agents-in-Depth-zh-CN.pdf"
|
|
cp "book-zhtw/深入理解-AI-Agent-李博杰-v2.0-zhtw.pdf" "$out/AI-Agents-in-Depth-zh-TW.pdf"
|
|
cp book-en/AI-Agents-in-Depth-Bojie-Li-v2.0.pdf "$out/AI-Agents-in-Depth-en.pdf"
|
|
cp book-es/AI-Agents-en-Profundidad-Bojie-Li-v2.0-es.pdf "$out/AI-Agents-in-Depth-es.pdf"
|
|
cp book-id/AI-Agents-in-Depth-Bojie-Li-v2.0-id.pdf "$out/AI-Agents-in-Depth-id.pdf"
|
|
cp book-ru/AI-Agents-in-Depth-v2.0-ru.pdf "$out/AI-Agents-in-Depth-ru.pdf"
|
|
cp book-ta/AI-Agents-in-Depth-Bojie-Li-v2.0-ta.pdf "$out/AI-Agents-in-Depth-ta.pdf"
|
|
cp book-vi/AI-Agents-in-Depth-Bojie-Li-v2.0-vi.pdf "$out/AI-Agents-in-Depth-vi.pdf"
|
|
cp book-ar/AI-Agents-in-Depth-v2.0-ar.pdf "$out/AI-Agents-in-Depth-ar.pdf"
|
|
cp book-tr/AI-Agents-in-Depth-Bojie-Li-v2.0-tr.pdf "$out/AI-Agents-in-Depth-tr.pdf"
|
|
cp book-ko/AI-Agents-in-Depth-v2.0-ko.pdf "$out/AI-Agents-in-Depth-ko.pdf"
|
|
cp book-hu/AI-Agents-in-Depth-v2.0-hu.pdf "$out/AI-Agents-in-Depth-hu.pdf"
|
|
cp book-he/AI-Agents-in-Depth-v2.0-he.pdf "$out/AI-Agents-in-Depth-he.pdf"
|
|
cp "book/深入理解-AI-Agent-李博杰-v2.0.epub" "$out/AI-Agents-in-Depth-zh-CN.epub"
|
|
cp "book-zhtw/深入理解-AI-Agent-李博杰-v2.0-zhtw.epub" "$out/AI-Agents-in-Depth-zh-TW.epub"
|
|
cp book-en/AI-Agents-in-Depth-Bojie-Li-v2.0.epub "$out/AI-Agents-in-Depth-en.epub"
|
|
cp book-es/AI-Agents-en-Profundidad-Bojie-Li-v2.0-es.epub "$out/AI-Agents-in-Depth-es.epub"
|
|
cp book-id/AI-Agents-in-Depth-Bojie-Li-v2.0-id.epub "$out/AI-Agents-in-Depth-id.epub"
|
|
cp book-ru/AI-Agents-in-Depth-v2.0-ru.epub "$out/AI-Agents-in-Depth-ru.epub"
|
|
cp book-ta/AI-Agents-in-Depth-Bojie-Li-v2.0-ta.epub "$out/AI-Agents-in-Depth-ta.epub"
|
|
cp book-vi/AI-Agents-in-Depth-Bojie-Li-v2.0-vi.epub "$out/AI-Agents-in-Depth-vi.epub"
|
|
cp book-tr/AI-Agents-in-Depth-Bojie-Li-v2.0-tr.epub "$out/AI-Agents-in-Depth-tr.epub"
|
|
cp book-ko/AI-Agents-in-Depth-v2.0-ko.epub "$out/AI-Agents-in-Depth-ko.epub"
|
|
cp book-hu/AI-Agents-in-Depth-v2.0-hu.epub "$out/AI-Agents-in-Depth-hu.epub"
|
|
cp book-he/AI-Agents-in-Depth-v2.0-he.epub "$out/AI-Agents-in-Depth-he.epub"
|
|
# WIP: ja artifacts and the Arabic EPUB are copied only if their
|
|
# non-fatal builds produced them.
|
|
cp book-ja/AI-Agents-in-Depth-Bojie-Li-v2.0-ja.pdf "$out/AI-Agents-in-Depth-ja.pdf" || echo "ja PDF not built yet (WIP) — skipping"
|
|
cp book-ja/AI-Agents-in-Depth-Bojie-Li-v2.0-ja.epub "$out/AI-Agents-in-Depth-ja.epub" || echo "ja EPUB not built yet (WIP) — skipping"
|
|
cp book-ar/AI-Agents-in-Depth-v2.0-ar.epub "$out/AI-Agents-in-Depth-ar.epub" || echo "ar EPUB not built yet (WIP) — skipping"
|
|
gh release upload latest "$out"/* --clobber --repo "${{ github.repository }}"
|