docs: architecture — mcpp-index CI as a native mcpp workspace (vs smo… #159
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: validate | |
| on: | |
| pull_request: | |
| paths: ["pkgs/**/*.lua", "tests/**", "README.md", ".github/workflows/validate.yml"] | |
| push: | |
| branches: [main] | |
| schedule: | |
| # nightly full regression — exercises every smoke suite regardless of diff | |
| - cron: "0 6 * * *" | |
| workflow_dispatch: | |
| env: | |
| # Bumped to 0.0.73: carries Windows runtime-DLL deployment, required by the | |
| # compat.openblas Windows recipe (bin/libopenblas.dll staged beside the .exe). | |
| MCPP_VERSION: "0.0.73" | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install lua | |
| run: sudo apt-get install -y --no-install-recommends lua5.4 | |
| - name: Lint package descriptors | |
| run: | | |
| fail=0 | |
| for f in pkgs/*/*.lua; do | |
| # 1. Lua syntax check — load (= compile) without executing. | |
| # `loadfile(name, 't')` rejects bytecode and parses text only. | |
| if ! lua5.4 -e "assert(loadfile('$f', 't'))" >/dev/null 2>&1; then | |
| echo "::error file=$f::lua syntax error" | |
| fail=1 | |
| fi | |
| # 2. xpkg V1 baseline: the file has to populate `package = { ... }` | |
| # with at least `spec`, `name`, and an `xpm` table. Form A vs | |
| # Form B (mcpp = "<path>" / mcpp = { ... }) is descriptor-author | |
| # choice and not enforced here. | |
| for needle in 'spec *=' 'name *=' 'xpm *='; do | |
| if ! grep -q "$needle" "$f"; then | |
| echo "::error file=$f::missing required field ($needle)" | |
| fail=1 | |
| fi | |
| done | |
| # 3. Package version identifiers and dependency versions should be | |
| # bare versions ("1.2.3"), not upstream tag names ("v1.2.3"). | |
| # Download URLs may still contain refs/tags/v* when upstream | |
| # uses that tag spelling. | |
| if grep -nE '\["v[0-9]+|\["[^"]+"\][[:space:]]*=[[:space:]]*"v[0-9]+' "$f"; then | |
| echo "::error file=$f::version identifiers must not use a leading v" | |
| fail=1 | |
| fi | |
| # 4. Mirror table sanity: when a download `url` is written as a | |
| # { GLOBAL=..., CN=... } table, both regions must be present and | |
| # the CN entry must point at the gitcode mcpp-res mirror. | |
| if ! lua5.4 tests/check_mirror_urls.lua "$f"; then | |
| fail=1 | |
| fi | |
| done | |
| [ $fail -eq 0 ] && echo "All package files valid." | |
| exit $fail | |
| mirror-cn-reachable: | |
| # Closed-loop guard for the CN mirror: every CN url referenced by a | |
| # descriptor must be a live, downloadable gitcode release asset. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install lua | |
| run: sudo apt-get install -y --no-install-recommends lua5.4 | |
| - name: Check CN mirror assets are reachable | |
| run: | | |
| fail=0 | |
| # collect unique CN urls across all descriptors | |
| : > /tmp/cn.tsv | |
| for f in pkgs/*/*.lua; do | |
| lua5.4 tests/list_cn_urls.lua "$f" >> /tmp/cn.tsv || true | |
| done | |
| sort -u /tmp/cn.tsv -o /tmp/cn.tsv | |
| total=$(grep -c . /tmp/cn.tsv || true) | |
| echo "checking $total CN mirror url(s)" | |
| while IFS=$'\t' read -r url sha; do | |
| [ -z "$url" ] && continue | |
| # follow redirects; gitcode release assets resolve to object storage | |
| code=$(curl -fsSL -o /dev/null -w '%{http_code}' --retry 2 --max-time 60 "$url" || echo "000") | |
| if [ "$code" != "200" ]; then | |
| echo "::error::CN mirror unreachable ($code): $url" | |
| fail=1 | |
| else | |
| echo "ok: $url" | |
| fi | |
| done < /tmp/cn.tsv | |
| [ $fail -eq 0 ] && echo "All CN mirror urls reachable." | |
| exit $fail | |
| # ── Decide what to smoke-test ───────────────────────────────────────── | |
| # PR builds run ONLY the example projects whose package descriptor changed | |
| # (tests/examples/<pkg>/). Anything that can't be narrowed that way — a | |
| # changed pkg without an example yet, a scaffolding/CI change, or a | |
| # push/nightly/manual run — falls back to the full legacy smoke regression. | |
| detect: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| examples: ${{ steps.f.outputs.examples }} | |
| full: ${{ steps.f.outputs.full }} | |
| full_linux: ${{ steps.f.outputs.full_linux }} | |
| full_portable: ${{ steps.f.outputs.full_portable }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install jq | |
| run: sudo apt-get install -y --no-install-recommends jq | |
| - id: f | |
| run: | | |
| # The two heavy suites run on disjoint platforms and from disjoint | |
| # scripts: smoke-full-linux runs the smoke_compat_{core,imgui,archive, | |
| # imgui_window}.sh + smoke_imgui_module.sh scripts (Linux); smoke-portable | |
| # runs ONLY smoke_compat_portable.sh (windows+macos). So a change to one | |
| # platform's harness needn't trigger the other's. We classify each changed | |
| # file into full_linux / full_portable; unclassified test changes, the | |
| # common harness, and pkgs without an example fall back to BOTH (no | |
| # coverage is ever silently dropped). | |
| full_linux=false | |
| full_portable=false | |
| examples='[]' | |
| event="${{ github.event_name }}" | |
| if [ "$event" != "pull_request" ]; then | |
| # push to main / nightly schedule / manual dispatch → full regression | |
| full_linux=true; full_portable=true | |
| else | |
| git fetch -q origin "${{ github.base_ref }}" | |
| changed=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD") | |
| echo "changed files:"; echo "$changed" | sed 's/^/ /' | |
| sel="" | |
| while IFS= read -r file; do | |
| [ -z "$file" ] && continue | |
| case "$file" in | |
| # Common harness / the workflow itself → both platforms. | |
| tests/run_example.sh|tests/check_mirror_urls.lua|tests/list_cn_urls.lua|.github/workflows/validate.yml) | |
| full_linux=true; full_portable=true ;; | |
| # Portable suite script runs only in smoke-portable (win+mac). | |
| tests/smoke_compat_portable.sh) | |
| full_portable=true ;; | |
| # Linux-only smoke scripts run only in smoke-full-linux. | |
| tests/smoke_compat_core.sh|tests/smoke_compat_imgui.sh|tests/smoke_compat_archive.sh|tests/smoke_compat_imgui_window.sh|tests/smoke_imgui_module.sh) | |
| full_linux=true ;; | |
| # Example projects are handled by the smoke-examples fast path. | |
| tests/examples/*) | |
| : ;; | |
| # Any other tests/ change (new helper / new smoke script) → both (safe). | |
| tests/*) | |
| full_linux=true; full_portable=true ;; | |
| pkgs/*/*.lua) | |
| b=$(basename "$file" .lua); b=${b#compat.} | |
| if [ -d "tests/examples/$b" ]; then | |
| sel="$sel$b"$'\n' | |
| else | |
| # changed pkg with no example yet → cannot narrow, run both | |
| full_linux=true; full_portable=true | |
| fi | |
| ;; | |
| esac | |
| done <<< "$changed" | |
| if [ -n "$sel" ]; then | |
| examples=$(printf '%s' "$sel" | grep -v '^$' | sort -u | jq -R . | jq -sc .) | |
| fi | |
| fi | |
| full=false | |
| { [ "$full_linux" = true ] || [ "$full_portable" = true ]; } && full=true | |
| echo "full=$full" >> "$GITHUB_OUTPUT" | |
| echo "full_linux=$full_linux" >> "$GITHUB_OUTPUT" | |
| echo "full_portable=$full_portable" >> "$GITHUB_OUTPUT" | |
| echo "examples=$examples" >> "$GITHUB_OUTPUT" | |
| echo "=> full_linux=$full_linux full_portable=$full_portable examples=$examples" | |
| # ── Fast path: per-changed-package example projects (Linux) ─────────── | |
| smoke-examples: | |
| needs: detect | |
| if: needs.detect.outputs.examples != '[]' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| pkg: ${{ fromJson(needs.detect.outputs.examples) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Restore mcpp registry cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.mcpp/registry | |
| key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} | |
| restore-keys: | | |
| mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}- | |
| - name: Download mcpp | |
| run: | | |
| curl -L -fsS -o mcpp.tar.gz \ | |
| "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz" | |
| tar -xzf mcpp.tar.gz | |
| root="$PWD/mcpp-${MCPP_VERSION}-linux-x86_64" | |
| mkdir -p "$HOME/.mcpp/registry" | |
| cp -a "$root/registry/." "$HOME/.mcpp/registry/" | |
| echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" | |
| echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" | |
| echo "$root/bin" >> "$GITHUB_PATH" | |
| - name: Run example "${{ matrix.pkg }}" | |
| env: | |
| MCPP_INDEX_MIRROR: GLOBAL | |
| run: | | |
| "$MCPP" --version | |
| timeout 1800 bash tests/run_example.sh "${{ matrix.pkg }}" | |
| # ── Full regression (legacy whole-suite smoke) ──────────────────────── | |
| smoke-full-linux: | |
| needs: detect | |
| if: needs.detect.outputs.full_linux == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Restore mcpp registry cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.mcpp/registry | |
| key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} | |
| restore-keys: | | |
| mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}- | |
| # Persist the smoke download cache (toolchains + compat source archives) | |
| # ACROSS runs. Without this, every run re-downloads xim:python/make, the | |
| # gcc/llvm toolchains, and every source tarball. restore-keys gives a | |
| # partial hit even when pkgs change, so only new/changed archives download; | |
| # save_smoke_cache (in the smoke scripts) repopulates the dir each run. | |
| - name: Cache smoke downloads | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ runner.temp }}/mcpp-smoke-cache | |
| key: smoke-dl-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua') }} | |
| restore-keys: | | |
| smoke-dl-${{ runner.os }}-${{ env.MCPP_VERSION }}- | |
| - name: Download mcpp | |
| run: | | |
| curl -L -fsS -o mcpp.tar.gz \ | |
| "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/mcpp-${MCPP_VERSION}-linux-x86_64.tar.gz" | |
| tar -xzf mcpp.tar.gz | |
| root="$PWD/mcpp-${MCPP_VERSION}-linux-x86_64" | |
| mkdir -p "$HOME/.mcpp/registry" | |
| cp -a "$root/registry/." "$HOME/.mcpp/registry/" | |
| echo "MCPP=$root/bin/mcpp" >> "$GITHUB_ENV" | |
| echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV" | |
| echo "$root/bin" >> "$GITHUB_PATH" | |
| - name: Run compat smoke tests | |
| env: | |
| MCPP_INDEX_SMOKE_MCPP_HOME: ${{ runner.temp }}/mcpp-smoke-home | |
| MCPP_INDEX_SMOKE_CACHE_DIR: ${{ runner.temp }}/mcpp-smoke-cache | |
| run: | | |
| mkdir -p "$MCPP_INDEX_SMOKE_MCPP_HOME" | |
| "$MCPP" --version | |
| timeout 1800 bash tests/smoke_compat_core.sh | |
| timeout 1800 bash tests/smoke_compat_imgui.sh | |
| timeout 1800 bash tests/smoke_compat_archive.sh | |
| # GLFW/OpenGL window + imgui-module demos. These pull compat.glfw | |
| # (abi:glibc). The imgui-module smoke pins llvm@20.1.7 (clang/libc++); | |
| # on mcpp ≤0.0.67 that tripped the conflated `abi:` gate ("requires | |
| # abi=glibc … is abi=libc++") because a clang+libc++ toolchain on | |
| # *-linux-gnu was mislabelled. Fixed in mcpp 0.0.68 (dimensional ABI | |
| # model: libc and cxxstdlib are independent axes), so these are back in | |
| # the blocking set. The window run itself stays opt-in (needs a display). | |
| timeout 1800 bash tests/smoke_compat_imgui_window.sh | |
| timeout 1800 bash tests/smoke_imgui_module.sh | |
| smoke-portable: | |
| needs: detect | |
| if: needs.detect.outputs.full_portable == 'true' | |
| name: smoke-${{ matrix.platform }} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: macos | |
| os: macos-15 | |
| archive: mcpp-0.0.73-macosx-arm64.tar.gz | |
| root: mcpp-0.0.73-macosx-arm64 | |
| mcpp: bin/mcpp | |
| xlings: registry/bin/xlings | |
| - platform: windows | |
| os: windows-latest | |
| archive: mcpp-0.0.73-windows-x86_64.zip | |
| root: mcpp-0.0.73-windows-x86_64 | |
| mcpp: bin/mcpp.exe | |
| xlings: registry/bin/xlings.exe | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Restore mcpp registry cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.mcpp/registry | |
| key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} | |
| restore-keys: | | |
| mcpp-registry-${{ runner.os }}-${{ env.MCPP_VERSION }}- | |
| # Persist the smoke download cache across runs (same rationale as | |
| # smoke-full-linux). Previously the portable job never set | |
| # MCPP_INDEX_SMOKE_CACHE_DIR at all, so its 5 projects each re-downloaded | |
| # the full llvm toolchain + compat archives WITHIN one run; now they share | |
| # one dir, persisted between runs too. | |
| - name: Cache smoke downloads | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ runner.temp }}/mcpp-smoke-cache | |
| key: smoke-dl-${{ runner.os }}-${{ env.MCPP_VERSION }}-${{ hashFiles('pkgs/**/*.lua') }} | |
| restore-keys: | | |
| smoke-dl-${{ runner.os }}-${{ env.MCPP_VERSION }}- | |
| - name: Download mcpp | |
| shell: bash | |
| env: | |
| MCPP_ARCHIVE: ${{ matrix.archive }} | |
| MCPP_ROOT: ${{ matrix.root }} | |
| run: | | |
| curl -L -fsS -o "$MCPP_ARCHIVE" \ | |
| "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}/${MCPP_ARCHIVE}" | |
| case "$MCPP_ARCHIVE" in | |
| *.zip) | |
| powershell -NoProfile -Command "Expand-Archive -Force -Path '${MCPP_ARCHIVE}' -DestinationPath '.'" | |
| ;; | |
| *) | |
| tar -xzf "$MCPP_ARCHIVE" | |
| ;; | |
| esac | |
| root="$PWD/$MCPP_ROOT" | |
| mkdir -p "$HOME/.mcpp/registry" | |
| cp -a "$root/registry/." "$HOME/.mcpp/registry/" | |
| if [[ "$RUNNER_OS" == "Windows" ]]; then | |
| echo "MCPP=$(cygpath -m "$root/${{ matrix.mcpp }}")" >> "$GITHUB_ENV" | |
| echo "MCPP_VENDORED_XLINGS=$(cygpath -m "$root/${{ matrix.xlings }}")" >> "$GITHUB_ENV" | |
| echo "$(cygpath -m "$root/bin")" >> "$GITHUB_PATH" | |
| else | |
| echo "MCPP=$root/${{ matrix.mcpp }}" >> "$GITHUB_ENV" | |
| echo "MCPP_VENDORED_XLINGS=$root/${{ matrix.xlings }}" >> "$GITHUB_ENV" | |
| echo "$root/bin" >> "$GITHUB_PATH" | |
| fi | |
| - name: Run portable compat smoke tests | |
| shell: bash | |
| env: | |
| MCPP_INDEX_MIRROR: GLOBAL | |
| # Enables the within-run + cross-run download cache (copy/save_smoke_cache). | |
| MCPP_INDEX_SMOKE_CACHE_DIR: ${{ runner.temp }}/mcpp-smoke-cache | |
| run: | | |
| mkdir -p "$MCPP_INDEX_SMOKE_CACHE_DIR" | |
| "$MCPP" --version | |
| bash tests/smoke_compat_portable.sh |