ci: use native 'mcpp index update' for the sandbox index refresh #277
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: | ||
| # 0.0.96: carries the windows scanner symlink-escape crash fix (mcpp#230), | ||
| # so all platforms ride the same version again. Older floors of note: | ||
| # 0.0.94 fixed feature-gated `sources` under `mcpp test` (mcpp#218); | ||
| # 0.0.91 added standard = "c++fly" to the resolver grammar, so c++fly | ||
| # descriptors get the lint WARN below, not a hard grammar-parse rejection. | ||
| MCPP_VERSION: "0.0.96" | ||
| 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 | ||
| # 5. c++fly admission policy (mcpp design 2026-07-14 §11-Q2, v1): | ||
| # c++fly means "toolchain's latest level + every experimental | ||
| # gate" — deliberately toolchain-dependent, so a published | ||
| # package built with it is not reproducible for consumers. | ||
| # Policy: WARN (never fail) and observe ecosystem usage before | ||
| # deciding whether to tighten. Two spellings: `language = ` is | ||
| # the descriptor's inline mcpp-segment key; `standard = ` covers | ||
| # mcpp.toml content embedded in heredoc/generated_files blocks. | ||
| if grep -nE '\b(language|standard)[[:space:]]*=[[:space:]]*"c\+\+fly"' "$f" >/dev/null; then | ||
| echo "::warning file=$f::declares C++ standard \"c++fly\" (experimental playground mode) — toolchain-dependent and non-reproducible for consumers; published packages should pin a concrete standard (c++23/c++26)" | ||
| fi | ||
| done | ||
| [ $fail -eq 0 ] && echo "All package files valid." | ||
| exit $fail | ||
| # ── Single-source-of-truth grammar check ───────────────────────── | ||
| # `mcpp xpkg parse` uses EXACTLY the resolver's parser, so what | ||
| # passes here is what builds for users of the pinned MCPP_VERSION. | ||
| # Strict by default: unknown mcpp-segment keys fail (they would be | ||
| # silently ignored at build time). This also mechanically enforces | ||
| # the rollout rule "floor first, new grammar after": descriptors | ||
| # needing a newer grammar cannot pass a lint pinned to an older mcpp. | ||
| - name: Download pinned 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 | ||
| echo "MCPP=$PWD/mcpp-${MCPP_VERSION}-linux-x86_64/bin/mcpp" >> "$GITHUB_ENV" | ||
| - name: Parse descriptors with the resolver grammar (mcpp xpkg parse) | ||
| run: | | ||
| fail=0 | ||
| for f in pkgs/*/*.lua; do | ||
| if ! "$MCPP" xpkg parse "$f" > /dev/null; then | ||
| echo "::error file=$f::mcpp xpkg parse failed (resolver grammar)" | ||
| fail=1 | ||
| fi | ||
| done | ||
| [ $fail -eq 0 ] && echo "All descriptors parse with mcpp ${MCPP_VERSION}." | ||
| 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 | ||
| # ── The whole test surface, as a mcpp workspace ─────────────────────── | ||
| # mcpp-index is a mcpp [workspace]; every per-library test project under | ||
| # tests/examples/ is a member. `mcpp test --workspace` builds + runs each | ||
| # member's tests/ (behavioral assertions) on each OS — members self-gate by | ||
| # `[target.'cfg(...)']` (e.g. the X11/glfw stack is linux-only, openblas is | ||
| # windows-only), so one command covers the matrix with no shell driver. | ||
| # The ~/.mcpp/registry cache carries the built compat packages (xpkgs) across | ||
| # runs, so repeat builds are fast. | ||
| workspace: | ||
| name: workspace (${{ matrix.platform }}) | ||
| runs-on: ${{ matrix.os }} | ||
| timeout-minutes: 60 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| # Archive names are derived from env.MCPP_VERSION in the Download | ||
| # step — bumping the pin is a ONE-line change (hardcoded versions | ||
| # here once 404'd a pin bump). | ||
| - platform: linux | ||
| os: ubuntu-latest | ||
| suffix: linux-x86_64 | ||
| ext: tar.gz | ||
| mcpp: bin/mcpp | ||
| xlings: registry/bin/xlings | ||
| mcpp_version: "0.0.96" # keep in sync with env.MCPP_VERSION | ||
| - platform: macos | ||
| os: macos-15 | ||
| suffix: macosx-arm64 | ||
| ext: tar.gz | ||
| mcpp: bin/mcpp | ||
| xlings: registry/bin/xlings | ||
| mcpp_version: "0.0.96" # keep in sync with env.MCPP_VERSION | ||
| - platform: windows | ||
| os: windows-latest | ||
| suffix: windows-x86_64 | ||
| ext: zip | ||
| mcpp: bin/mcpp.exe | ||
| xlings: registry/bin/xlings.exe | ||
| mcpp_version: "0.0.96" # keep in sync with env.MCPP_VERSION | ||
| env: | ||
| MCPP_EFFECTIVE: ${{ matrix.mcpp_version }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Restore mcpp registry cache | ||
| uses: actions/cache@v4 | ||
| with: | ||
| # Holds toolchains AND the built compat packages (data/xpkgs), so a | ||
| # repeat `mcpp test` rebuilds little. | ||
| path: ~/.mcpp/registry | ||
| key: mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} | ||
| restore-keys: | | ||
| mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}- | ||
| - name: Download mcpp | ||
| shell: bash | ||
| env: | ||
| MCPP_ARCHIVE: mcpp-${{ env.MCPP_EFFECTIVE }}-${{ matrix.suffix }}.${{ matrix.ext }} | ||
| MCPP_ROOT: mcpp-${{ env.MCPP_EFFECTIVE }}-${{ matrix.suffix }} | ||
| run: | | ||
| curl -L -fsS -o "$MCPP_ARCHIVE" \ | ||
| "https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_EFFECTIVE}/${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 | ||
| # compat.ffmpeg carries NASM .asm sources. No host install needed: | ||
| # mcpp >= 0.0.95 resolves nasm itself (PATH → sandbox → auto | ||
| # `xlings install nasm`), lazily, only when the plan has .asm units — | ||
| # same self-bootstrap contract as the llvm toolchain. The sandbox copy | ||
| # lands in ~/.mcpp/registry, so the cache carries it across runs. | ||
| # The refresh below is needed because release archives vendor an index | ||
| # snapshot and the bootstrap is offline-first: 0.0.96 shipped with a | ||
| # nasm.lua deployed engines can't resolve — it "installs" empty | ||
| # (mcpp#232; fixed in xim-pkgindex#398 after the release was cut). | ||
| # One `mcpp index update` (native wrapper over `xlings update` with | ||
| # the sandbox env + transient-network retry) lets the bootstrap see | ||
| # the fixed descriptor. Drop once a mcpp release vendors a post-#398 | ||
| # index snapshot. | ||
| - name: Refresh sandbox package index (nasm bootstrap, mcpp#232) | ||
| if: runner.os == 'Linux' | ||
| shell: bash | ||
| run: "$MCPP" index update | ||
| - name: mcpp test --workspace | ||
| shell: bash | ||
| env: | ||
| MCPP_INDEX_MIRROR: GLOBAL | ||
| run: | | ||
| "$MCPP" --version | ||
| # No `timeout` wrapper: absent on macOS runners; job-level timeout-minutes bounds it. | ||
| "$MCPP" test --workspace | ||
| - name: tinyhttps module package smoke | ||
| shell: bash | ||
| env: | ||
| MCPP_INDEX_MIRROR: GLOBAL | ||
| run: bash tests/smoke_tinyhttps_module.sh | ||
| # ── Exception: the public `imgui` C++23-module package (namespace "") ── | ||
| # Not yet a workspace member: its package has an empty namespace (the builtin | ||
| # default index), which a workspace member can't point at a local path the way | ||
| # namespaced compat.*/nlohmann packages can. Until mcpp can map the default | ||
| # namespace to a local index, this one keeps a thin driver that reseeds the | ||
| # default index from the repo. Tracked in the rearchitecture design doc. | ||
| imgui-module: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
| env: | ||
| MCPP_EFFECTIVE: "0.0.96" # keep in sync with env.MCPP_VERSION | ||
| 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_EFFECTIVE }}-${{ hashFiles('pkgs/**/*.lua', 'tests/**', '.github/workflows/validate.yml') }} | ||
| restore-keys: | | ||
| mcpp-registry-${{ runner.os }}-${{ env.MCPP_EFFECTIVE }}- | ||
| - 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: imgui module package smoke | ||
| env: | ||
| MCPP_INDEX_MIRROR: GLOBAL | ||
| run: | | ||
| "$MCPP" --version | ||
| timeout 1800 bash tests/smoke_imgui_module.sh | ||