feat(toolchain): toolchain × target 命名统一 — 二轴身份模型 (0.0.93) #229
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: cross-build-test | |
| # mcpp cross-build test — the single source of truth for "which CROSS-build | |
| # target combinations mcpp supports", verified end-to-end. | |
| # | |
| # Cross = host arch ≠ target arch. Verification targets are mcpp ITSELF and | |
| # xlings (real, self-hosting C++23 module projects), cross-built from source for | |
| # each target triple, arch-checked, and smoke-run under qemu-user. | |
| # | |
| # ── Supported cross matrix (built + verified below) ──────────────────────── | |
| # target | toolchain | host→target | run | |
| # ----------------------|---------------------------------|---------------|----- | |
| # aarch64-linux-musl | aarch64-linux-musl-gcc@16.1.0 | x86_64→arm64 | qemu | |
| # x86_64-w64-mingw32 | mingw-cross-gcc@16.1.0 (MSVCRT) | linux→windows | wine | |
| # | |
| # The mingw row is OS-cross (same arch, different OS/ABI: ELF→PE), so it lives | |
| # in its own job below with wine verification instead of the qemu arch matrix. | |
| # See .agents/docs/2026-07-15-mingw-linux-cross-windows-design.md. | |
| # | |
| # mcpp resolves a cross `--target <triple>-musl` build to the triple-named cross | |
| # gcc musl toolchain from the xlings ecosystem (xim:<triple>-gcc, see | |
| # src/build/prepare.cppm). Output is a fully static musl ELF (no PT_INTERP), | |
| # which also makes the aarch64 artefact runnable natively in Termux/Android — | |
| # qemu-aarch64 is the CI proxy for "does this cross artefact actually execute". | |
| # | |
| # ── NOT here ─────────────────────────────────────────────────────────────── | |
| # * Same-arch builds (host arch == target arch) are NOT cross. The native musl | |
| # static build `--target x86_64-linux-musl` (x86_64 host) is exercised by | |
| # ci-linux.yml's "Toolchain: musl-gcc" step, and release.yml for the static | |
| # release artefact. Keep them there; this file is cross-arch only. | |
| # | |
| # ── Planned cross rows (documented; NOT yet wired in mcpp — keep as comments) ─ | |
| # * llvm/clang cross : clang is inherently a cross-compiler, but mcpp does not | |
| # yet inject `-target <triple>` + a cross sysroot for a | |
| # clang toolchain; cross `--target` resolves to gcc musl | |
| # only. Wire the clang cross path first, then add a row. | |
| # * riscv64-linux-musl: add once xim:riscv64-linux-musl-gcc ships to | |
| # xlings-res + xim-pkgindex. | |
| on: | |
| push: | |
| branches: [ main ] | |
| pull_request: | |
| branches: [ main ] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| cross-build: | |
| name: cross-build ${{ matrix.target }} (mcpp + xlings) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: aarch64-linux-musl | |
| file_arch: "ARM aarch64" | |
| qemu_bin: qemu-aarch64-static | |
| env: | |
| MCPP_HOME: /home/runner/.mcpp | |
| # Verbose every mcpp invocation for richer CI diagnostics (src/cli.cppm). | |
| MCPP_VERBOSE: "1" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Cache mcpp sandbox | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.mcpp | |
| key: mcpp-sandbox-${{ runner.os }}-cross-${{ matrix.target }}-${{ hashFiles('mcpp.toml', '.xlings.json') }} | |
| restore-keys: | | |
| mcpp-sandbox-${{ runner.os }}-cross-${{ matrix.target }}- | |
| - name: Cache xlings | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.xlings | |
| key: xlings-${{ runner.os }}-v2-${{ hashFiles('.xlings.json') }} | |
| restore-keys: | | |
| xlings-${{ runner.os }}-v2- | |
| - name: Install qemu-user-static | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y qemu-user-static | |
| ${{ matrix.qemu_bin }} --version | head -1 | |
| - name: Bootstrap mcpp via xlings | |
| env: | |
| XLINGS_NON_INTERACTIVE: '1' | |
| # 0.4.62 (current) — kept in lock-step with the xlings the release | |
| # is built/bundled against (release.yml). A past 0.4.61 "download 404 | |
| # for mcpp@<pin>" was NOT a version bug — the xlings-res/mcpp GitHub | |
| # release assets were uploaded in a broken state (records present, | |
| # blobs missing → 404 on GET); re-uploaded clean. The stale-INDEX | |
| # half is handled by the marker-clear below. | |
| XLINGS_VERSION: '0.4.62' | |
| run: | | |
| tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz" | |
| curl -fsSL -o "/tmp/${tarball}" \ | |
| "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" | |
| tar -xzf "/tmp/${tarball}" -C /tmp | |
| "/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install | |
| export PATH="$HOME/.xlings/subos/default/bin:$PATH" | |
| xlings --version | |
| # Force a real index re-sync even on a warm cache: drop the TTL refresh | |
| # markers so `xlings update` actually pulls the latest index (sees the | |
| # current bootstrap pin) while the toolchain payloads stay cached. | |
| find "$HOME/.xlings" -name '.xlings-index-cache.json' -delete 2>/dev/null || true | |
| xlings config --mirror GLOBAL 2>/dev/null || true | |
| xlings update -y 2>/dev/null || xlings update 2>/dev/null || true | |
| xlings install mcpp -y | |
| echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" | |
| echo "MCPP_BOOT=$HOME/.xlings/subos/default/bin/mcpp" >> "$GITHUB_ENV" | |
| - name: Self-host build (bootstrap mcpp -> fresh host mcpp) | |
| run: | | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| "$XLINGS_BIN" config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP_BOOT" self config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP_BOOT" build | |
| MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") | |
| test -x "$MCPP" | |
| "$MCPP" self config --mirror GLOBAL | |
| echo "MCPP=$MCPP" >> "$GITHUB_ENV" | |
| - name: "Cross-build mcpp -> ${{ matrix.target }}" | |
| run: | | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| "$MCPP" build --target ${{ matrix.target }} | |
| bin=$(find target/${{ matrix.target }} -type f -name mcpp | head -1) | |
| [ -n "$bin" ] || { echo "no mcpp artefact for ${{ matrix.target }}"; exit 1; } | |
| echo "== file =="; file "$bin" | |
| file "$bin" | grep -q "${{ matrix.file_arch }}" || { echo "expected ${{ matrix.file_arch }}"; exit 1; } | |
| file "$bin" | grep -q "statically linked" || { echo "expected static"; exit 1; } | |
| echo "MCPP_XBIN=$bin" >> "$GITHUB_ENV" | |
| - name: "Cross-build xlings -> ${{ matrix.target }}" | |
| run: | | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| git clone --depth 1 https://github.com/openxlings/xlings /tmp/xlings-src | |
| cd /tmp/xlings-src | |
| "$MCPP" self config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP" build --target ${{ matrix.target }} | |
| xbin=$(find target/${{ matrix.target }} -type f -name xlings | head -1) | |
| [ -n "$xbin" ] || { echo "no xlings artefact for ${{ matrix.target }}"; exit 1; } | |
| echo "== file =="; file "$xbin" | |
| file "$xbin" | grep -q "${{ matrix.file_arch }}" || { echo "expected ${{ matrix.file_arch }}"; exit 1; } | |
| file "$xbin" | grep -q "statically linked" || { echo "expected static"; exit 1; } | |
| echo "XLINGS_XBIN=$xbin" >> "$GITHUB_ENV" | |
| - name: "Smoke-run cross artefacts under qemu" | |
| run: | | |
| RUN="${{ matrix.qemu_bin }}" | |
| # mcpp is self-contained, so --version runs cleanly under bare qemu — | |
| # this is the hard execution proof for the cross artefact. | |
| echo "== mcpp --version ==" | |
| mver=$($RUN "$MCPP_XBIN" --version) | |
| echo "$mver"; echo "$mver" | grep -q "mcpp" || { echo "mcpp --version failed"; exit 1; } | |
| # xlings expects a real runtime environment (sandbox/config) and may | |
| # exit non-zero on a bare `--version` under qemu; its ELF arch + static | |
| # linkage were already asserted in the build step, so treat execution | |
| # here as best-effort rather than gating. | |
| echo "== xlings --version (best-effort under qemu) ==" | |
| xver=$($RUN "$XLINGS_XBIN" --version 2>&1 || true) | |
| echo "$xver" | |
| # ── Linux → Windows MinGW cross (OS-cross, same arch: ELF→PE) ───────────── | |
| # Builds a demo project for x86_64-w64-mingw32 with the from-source GCC-16 | |
| # MSVCRT cross toolchain, asserts the artefact is a fully-static PE, and runs | |
| # it under wine. Delegated to the e2e harness (tests/e2e/102_mingw_cross_wine.sh, | |
| # `# requires: mingw-cross wine`) so the run_all cap-gating stays the single | |
| # source of truth. See 2026-07-15-mingw-linux-cross-windows-design.md Part C. | |
| mingw-cross-wine: | |
| name: mingw-cross linux→windows (build + wine run) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| env: | |
| MCPP_HOME: /home/runner/.mcpp | |
| MCPP_VERBOSE: "1" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Cache mcpp sandbox | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.mcpp | |
| key: mcpp-sandbox-${{ runner.os }}-mingw-cross-${{ hashFiles('mcpp.toml', '.xlings.json') }} | |
| restore-keys: | | |
| mcpp-sandbox-${{ runner.os }}-mingw-cross- | |
| - name: Cache xlings | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.xlings | |
| key: xlings-${{ runner.os }}-v2-${{ hashFiles('.xlings.json') }} | |
| restore-keys: | | |
| xlings-${{ runner.os }}-v2- | |
| - name: Install wine | |
| run: | | |
| sudo dpkg --add-architecture i386 || true | |
| sudo apt-get update -qq | |
| sudo apt-get install -y wine64 wine || sudo apt-get install -y wine | |
| wine --version | |
| - name: Bootstrap mcpp via xlings | |
| env: | |
| XLINGS_NON_INTERACTIVE: '1' | |
| XLINGS_VERSION: '0.4.62' | |
| run: | | |
| tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz" | |
| curl -fsSL -o "/tmp/${tarball}" \ | |
| "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" | |
| tar -xzf "/tmp/${tarball}" -C /tmp | |
| "/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install | |
| export PATH="$HOME/.xlings/subos/default/bin:$PATH" | |
| find "$HOME/.xlings" -name '.xlings-index-cache.json' -delete 2>/dev/null || true | |
| xlings config --mirror GLOBAL 2>/dev/null || true | |
| xlings update -y 2>/dev/null || xlings update 2>/dev/null || true | |
| xlings install mcpp -y | |
| echo "XLINGS_BIN=$HOME/.xlings/subos/default/bin/xlings" >> "$GITHUB_ENV" | |
| echo "MCPP_BOOT=$HOME/.xlings/subos/default/bin/mcpp" >> "$GITHUB_ENV" | |
| - name: Self-host build (fresh host mcpp) | |
| run: | | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| "$MCPP_BOOT" self config --mirror GLOBAL 2>/dev/null || true | |
| "$MCPP_BOOT" build | |
| MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") | |
| test -x "$MCPP" | |
| "$MCPP" self config --mirror GLOBAL | |
| echo "MCPP=$MCPP" >> "$GITHUB_ENV" | |
| - name: Install mingw-cross toolchain | |
| run: | | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| "$MCPP" toolchain install mingw-cross 16.1.0 | |
| - name: "e2e: cross-build + wine run" | |
| run: | | |
| export MCPP_VENDORED_XLINGS="$XLINGS_BIN" | |
| bash tests/e2e/102_mingw_cross_wine.sh |