Skip to content

fix(scanner, manifest, build): one answer per question, and no key that reaches no decision (2026.9.9.1) #2071

fix(scanner, manifest, build): one answer per question, and no key that reaches no decision (2026.9.9.1)

fix(scanner, manifest, build): one answer per question, and no key that reaches no decision (2026.9.9.1) #2071

Workflow file for this run

name: ci-windows
# Windows CI for mcpp — same flow as Linux (ci-linux.yml) and macOS (ci-macos.yml):
# xlings install mcpp → self-host build → smoke → package
#
# SHAPE: three INDEPENDENT jobs, no `needs:` between them; each restores the
# shared cache lineage (.github/actions/bootstrap-mcpp) and pays one warm
# `mcpp build` to get the PR's own binary. The e2e suite moved to
# ci-windows-e2e.yml (sharded ×2) — it was 9.7 of this job's 20.4 min.
#
# before: build → unit → xlings → stdin → e2e → toolchains → package ≈ 20 min
# after: max(build+unit+package, toolchains+integration) ≈ 8 min
# in parallel with ci-windows-e2e (≈ 8 min)
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ci-windows-${{ github.ref }}
cancel-in-progress: true
env:
MCPP_HOME: C:\Users\runneradmin\.mcpp
jobs:
build-test:
name: build + test + package (windows x64, self-host)
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/bootstrap-mcpp
- name: Build mcpp from source (self-host)
shell: bash
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$MCPP" build
# Pick the NEWEST mcpp.exe, not an arbitrary one: `target/` is
# restored from cache and keeps a directory per build fingerprint,
# so after a version bump the freshly built binary sits alongside
# the previous release's. `find | head -1` returned whichever the
# directory walk hit first — which is how a 0.0.106 build ran the
# 0.0.105 binary and failed 01_help_and_version.
MCPP_SELF=$(find target -name "mcpp.exe" -path "*/bin/*" -printf "%T@ %p\n" \
| sort -rn | head -1 | cut -d" " -f2-)
test -n "$MCPP_SELF" || { echo "FAIL: no mcpp.exe"; exit 1; }
MCPP_SELF=$(cd "$(dirname "$MCPP_SELF")" && pwd)/$(basename "$MCPP_SELF")
"$MCPP_SELF" --version
echo "MCPP_SELF=$MCPP_SELF" >> "$GITHUB_ENV"
- name: Unit + integration tests via mcpp test
shell: bash
run: |
export MCPP_VENDORED_XLINGS=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe")
"$MCPP_SELF" test
- name: Package Windows release zip
id: package
shell: bash
run: |
VERSION=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml)
WRAPPER="mcpp-${VERSION}-windows-x86_64"
ZIPNAME="${WRAPPER}.zip"
# Pick the NEWEST mcpp.exe, not an arbitrary one: `target/` is
# restored from cache and keeps a directory per build fingerprint,
# so after a version bump the freshly built binary sits alongside
# the previous release's. `find | head -1` returned whichever the
# directory walk hit first — which is how a 0.0.106 build ran the
# 0.0.105 binary and failed 01_help_and_version.
MCPP_BIN=$(find target -name "mcpp.exe" -path "*/bin/*" -printf "%T@ %p\n" \
| sort -rn | head -1 | cut -d" " -f2-)
test -n "$MCPP_BIN" || { echo "FAIL: no mcpp.exe in target/"; exit 1; }
STAGING=$(mktemp -d)
mkdir -p "$STAGING/$WRAPPER/bin" "$STAGING/$WRAPPER/registry/bin"
cp "$MCPP_BIN" "$STAGING/$WRAPPER/bin/mcpp.exe"
printf '@echo off\r\n"%%~dp0bin\\mcpp.exe" %%*\r\n' > "$STAGING/$WRAPPER/mcpp.bat"
cp README.md "$STAGING/$WRAPPER/" 2>/dev/null || true
cp LICENSE "$STAGING/$WRAPPER/" 2>/dev/null || true
XLINGS_EXE="$USERPROFILE/.xlings/subos/default/bin/xlings.exe"
[ -f "$XLINGS_EXE" ] && cp "$XLINGS_EXE" "$STAGING/$WRAPPER/registry/bin/xlings.exe"
mkdir -p dist
(cd "$STAGING" && 7z a -tzip "$ZIPNAME" "$WRAPPER")
cp "$STAGING/$ZIPNAME" "dist/$ZIPNAME"
(cd dist && sha256sum "$ZIPNAME" > "$ZIPNAME.sha256")
echo "zipname=$ZIPNAME" >> "$GITHUB_OUTPUT"
ls -la dist/
- name: Smoke-test the packaged zip
shell: bash
run: |
ZIPNAME="${{ steps.package.outputs.zipname }}"
WRAPPER="${ZIPNAME%.zip}"
SMOKE=$(mktemp -d)
(cd "$SMOKE" && unzip -q "$GITHUB_WORKSPACE/dist/$ZIPNAME")
"$SMOKE/$WRAPPER/bin/mcpp.exe" --version
test -f "$SMOKE/$WRAPPER/registry/bin/xlings.exe"
test -f "$SMOKE/$WRAPPER/mcpp.bat"
echo "Smoke-test passed"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: mcpp-windows-x86_64
path: |
dist/*.zip
dist/*.sha256
# Everything that needs a toolchain other than the default, plus the two
# Windows-specific behavioural regressions. Kept on one runner: each leg is
# seconds-to-2-minutes, so per-leg runners would cost more setup than they
# save.
# A Windows machine WITHOUT Visual Studio — the shape of an ordinary user's
# box, and the one shape no GitHub image provides. Every runner ships VS, so
# a bare-Windows regression was structurally invisible here; the fresh-install
# workflow now covers it too, but that one only runs post-release, which is
# far too late to learn that `mcpp new && mcpp build` no longer works on a
# stock machine.
#
# Order matters: mcpp is built while Visual Studio is still present (the
# self-host build uses llvm, which targets the MSVC ABI and needs it), and
# only then is VS masked. e2e 182 opens by asserting that MSVC detection
# FAILS, so an incomplete mask fails the job instead of quietly testing the
# ordinary path.
no-msvc-fallback:
name: "bare Windows: no Visual Studio (windows x64)"
# Takes the binary build-test already produced instead of building here.
# Building in this job means compiling with clang, which reads the MSVC
# STL — the open handles that leaves make the VS directories unrenamable,
# so the masking below silently did nothing.
needs: build-test
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Fetch the PR's mcpp.exe
uses: actions/download-artifact@v4
with:
name: mcpp-windows-x86_64
path: dist
- name: Unpack it
shell: bash
run: |
ZIP=$(ls dist/*.zip | head -1)
test -n "$ZIP" || { echo "FAIL: no zip artifact"; exit 1; }
unzip -q "$ZIP" -d unpacked
MCPP_SELF=$(find unpacked -name "mcpp.exe" | head -1)
test -n "$MCPP_SELF" || { echo "FAIL: no mcpp.exe in $ZIP"; exit 1; }
MCPP_SELF=$(cd "$(dirname "$MCPP_SELF")" && pwd)/$(basename "$MCPP_SELF")
"$MCPP_SELF" --version
echo "MCPP_SELF=$MCPP_SELF" >> "$GITHUB_ENV"
# Masked before anything else touches Visual Studio, so no process of
# ours is holding a handle into it.
- name: Mask Visual Studio
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
# All three of msvc.cppm's discovery strategies converge on
# <vsRoot>\VC\Tools\MSVC — vswhere returns an installationPath that
# find_latest_msvc_tools then resolves through it, the env strategy
# checks it explicitly, and the well-known-path scan tests for it.
# So mask the VC directory rather than the Visual Studio root: the
# root is held open on the runner and renaming it is denied, while
# VC one level down renames fine. The runner is disposable, so this
# is both safe and closer to "absent" than any env-only trick.
$vswhere = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
if (Test-Path $vswhere) { Rename-Item $vswhere "vswhere.exe.masked" }
# -Path with a trailing wildcard segment lists the CONTENTS of the
# matches, not the matches themselves, so `…\*\*\VC` would hand back
# VC's children. Resolve-Path returns the directories themselves.
# Errors are reported, not swallowed: a silent failure here is how
# the first attempt "masked" nothing and still looked fine.
Resolve-Path "C:\Program Files*\Microsoft Visual Studio\*\*\VC" `
-ErrorAction SilentlyContinue | ForEach-Object {
$p = $_.Path
Write-Host "masking $p"
try { Rename-Item -LiteralPath $p -NewName "VC.masked" -ErrorAction Stop }
catch { Write-Host " rename failed: $($_.Exception.Message)" }
}
foreach ($v in @('VSINSTALLDIR','VCINSTALLDIR','VCToolsInstallDir',
'VS170COMNTOOLS','VS160COMNTOOLS','VS150COMNTOOLS')) {
"$v=" | Out-File -Append -FilePath $env:GITHUB_ENV -Encoding utf8
}
# Check the mask's own postcondition here, where the cause is
# obvious, instead of letting it surface three steps later as a
# confusing pass.
$left = Get-ChildItem "C:\Program Files*\Microsoft Visual Studio\*\*\VC\Tools\MSVC" `
-Directory -ErrorAction SilentlyContinue
if ($left) {
Write-Host "FAIL: VC tools still present after masking:"
$left | ForEach-Object { Write-Host " $($_.FullName)" }
exit 1
}
Write-Host "Visual Studio masked: no VC\Tools\MSVC remains."
# After masking, so nothing this action does can be holding Visual
# Studio open. It installs xlings (which mcpp resolves the winlibs
# toolchain through) and a released mcpp; neither needs a C++ compiler,
# so a masked VS is irrelevant to it.
- uses: ./.github/actions/bootstrap-mcpp
- name: "No Visual Studio: fallback to winlibs GCC (e2e 182)"
shell: bash
env:
MCPP_VENDORED_XLINGS: ${{ env.XLINGS_BIN }}
run: |
MCPP="$MCPP_SELF" bash tests/e2e/182_windows_no_msvc_fallback.sh
toolchains:
name: "toolchains + regressions (windows x64)"
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/bootstrap-mcpp
- name: Build mcpp from source (self-host)
shell: bash
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$MCPP" build
# Pick the NEWEST mcpp.exe, not an arbitrary one: `target/` is
# restored from cache and keeps a directory per build fingerprint,
# so after a version bump the freshly built binary sits alongside
# the previous release's. `find | head -1` returned whichever the
# directory walk hit first — which is how a 0.0.106 build ran the
# 0.0.105 binary and failed 01_help_and_version.
MCPP_SELF=$(find target -name "mcpp.exe" -path "*/bin/*" -printf "%T@ %p\n" \
| sort -rn | head -1 | cut -d" " -f2-)
test -n "$MCPP_SELF" || { echo "FAIL: no mcpp.exe"; exit 1; }
MCPP_SELF=$(cd "$(dirname "$MCPP_SELF")" && pwd)/$(basename "$MCPP_SELF")
echo "MCPP_SELF=$MCPP_SELF" >> "$GITHUB_ENV"
# Integration: the mcpp built from THIS PR's source ($MCPP_SELF, the
# self-hosted binary) builds & runs a real external C++ project — xlings
# (openxlings/xlings ships its own mcpp.toml). MCPP_VENDORED_XLINGS only
# supplies the xlings package backend mcpp resolves deps through.
- name: "Integration: mcpp builds & runs xlings (openxlings/xlings)"
shell: bash
env:
XLINGS_NON_INTERACTIVE: '1'
run: |
export MCPP_VENDORED_XLINGS=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe")
"$GITHUB_WORKSPACE/.github/tools/git_clone_retry.sh" \
--depth 1 --recurse-submodules \
https://github.com/openxlings/xlings /tmp/xlings-src
cd /tmp/xlings-src
"$MCPP_SELF" self config --mirror GLOBAL
"$MCPP_SELF" build
"$MCPP_SELF" run
# Regression test for the Windows first-run "press Enter to advance" hang.
# Launches mcpp with an OPEN, EMPTY, never-closing stdin pipe. Without
# seal_stdin's Windows fix, any grandchild that reads stdin would inherit
# our pipe and block forever — caught by the timeout below. With the fix,
# every subprocess stdin is redirected from NUL → no possibility of hang.
- name: "Regression: mcpp survives open-empty-stdin (Windows hang fix)"
shell: pwsh
timeout-minutes: 15
env:
MCPP_VENDORED_XLINGS: ${{ env.XLINGS_BIN }}
run: |
$ErrorActionPreference = 'Stop'
# MCPP_SELF was set in a bash step as an MSYS-style path
# (e.g. /d/a/mcpp/...). PowerShell can't exec that — convert it
# to a native Windows path via the git-bash cygpath that ships
# on the runner.
$mcppExe = (& 'C:\Program Files\Git\usr\bin\cygpath.exe' -w $env:MCPP_SELF).Trim()
Write-Host "Resolved MCPP_SELF (Windows form): $mcppExe"
if (-not (Test-Path $mcppExe)) {
throw "MCPP_SELF after cygpath not found: $mcppExe"
}
$tmp = Join-Path $env:RUNNER_TEMP ("stdin-hang-test-" + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $tmp | Out-Null
Set-Location $tmp
& $mcppExe new hello_stdin
Set-Location hello_stdin
function Invoke-McppWithOpenStdin {
param([string]$McppPath, [string]$McppArgs, [int]$TimeoutSeconds = 300)
$psi = [System.Diagnostics.ProcessStartInfo]::new()
$psi.FileName = $McppPath
$psi.Arguments = $McppArgs
$psi.WorkingDirectory = (Get-Location).Path
$psi.UseShellExecute = $false
$psi.RedirectStandardInput = $true # parent holds child's stdin
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
# By default the child inherits the parent's env (we did not
# touch $psi.Environment) so MCPP_VENDORED_XLINGS / PATH / etc.
# propagate.
$p = [System.Diagnostics.Process]::Start($psi)
# Async-drain stdout/stderr so a full output buffer doesn't
# itself deadlock the child (separate failure mode from the
# stdin hang we're testing).
$stdoutTask = $p.StandardOutput.ReadToEndAsync()
$stderrTask = $p.StandardError.ReadToEndAsync()
# NEVER write or close $p.StandardInput — the pipe stays open
# and empty for the lifetime of the child. Any grandchild that
# reads stdin will block on this pipe → caught by WaitForExit.
if (-not $p.WaitForExit($TimeoutSeconds * 1000)) {
try { $p.Kill($true) } catch {}
Write-Host "----- captured stdout -----"
Write-Host $stdoutTask.Result
Write-Host "----- captured stderr -----"
Write-Host $stderrTask.Result
throw "REGRESSION: 'mcpp $McppArgs' HUNG with open-empty stdin after ${TimeoutSeconds}s. The Windows seal_stdin fix is not effective."
}
Write-Host "----- stdout -----"
Write-Host $stdoutTask.Result
Write-Host "----- stderr -----"
Write-Host $stderrTask.Result
if ($p.ExitCode -ne 0) {
throw "'mcpp $McppArgs' exited with code $($p.ExitCode) (no hang, but failed)."
}
}
Write-Host '=== T1: mcpp --version (sanity, fast path) ==='
Invoke-McppWithOpenStdin -McppPath $mcppExe -McppArgs '--version' -TimeoutSeconds 30
Write-Host '=== T2: mcpp build (full bootstrap + toolchain + dep resolve + compile) ==='
Invoke-McppWithOpenStdin -McppPath $mcppExe -McppArgs 'build' -TimeoutSeconds 600
Write-Host '=== T3: mcpp run (post-build run path) ==='
Invoke-McppWithOpenStdin -McppPath $mcppExe -McppArgs 'run' -TimeoutSeconds 120
Write-Host 'SUCCESS: mcpp completes with open-empty stdin → Windows seal_stdin fix verified.'
- name: "Toolchain: LLVM — mcpp new → run"
shell: bash
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
TMP=$(mktemp -d)
cd "$TMP"
"$MCPP_SELF" new hello_win
cd hello_win
"$MCPP_SELF" run
# MinGW-w64 GCC via the xlings ecosystem (xim:mingw-gcc → xlings-res
# winlibs mirror): install → default → modules build/run → standalone
# exe. Same flow as e2e 97 but as a visible CI step. Payload is cached
# via the mcpp sandbox cache after the first run.
- name: "Toolchain: MinGW — install → build → run (xim:mingw-gcc)"
shell: bash
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
MCPP="$MCPP_SELF" bash tests/e2e/97_mingw_toolchain.sh
# windows-latest ships VS 2022 Enterprise with the VC workload, so
# msvc@system detection MUST succeed here — a failure is a regression
# in the discovery/identification chain (vswhere → env → paths).
# Runs BEFORE the LLVM self-host rebuild: that step cleans + rebuilds
# target/, invalidating this job's $MCPP_SELF fingerprint path.
- name: "Toolchain: MSVC — detection & selection (msvc@system)"
shell: bash
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
# Neutral cwd: the repo root's mcpp.toml [toolchain] would shadow
# the global default in `toolchain list` / doctor output.
TMP=$(mktemp -d); cd "$TMP"
out=$("$MCPP_SELF" toolchain default msvc); echo "$out"
grep -q "Detected" <<<"$out"
grep -q "msvc@system" <<<"$out"
"$MCPP_SELF" toolchain list | tee tc-list.txt
grep -E '\*\s*msvc' tc-list.txt
"$MCPP_SELF" self doctor 2>&1 | tee doctor.txt || true
grep -qi "msvc" doctor.txt
# native cl.exe build: full e2e (modules, import std, incremental)
cd "$GITHUB_WORKSPACE"
MCPP="$MCPP_SELF" bash tests/e2e/99_msvc_native_build.sh
# build.mcpp under cl.exe. `MSVC x build.mcpp` was an empty cell in
# this matrix and the feature was correspondingly at zero — the ten
# build.mcpp e2e all run under clang, whose payload path has no
# spaces in it either. Both gaps closed here.
MCPP="$MCPP_SELF" bash tests/e2e/180_msvc_build_mcpp.sh
# restore the LLVM default for the remaining steps
"$MCPP_SELF" toolchain default llvm@20.1.7
# GRAPHICS ON THIS HOST, BUILD ONLY, AND THAT IS THE WHOLE CLAIM.
#
# This runner has no Vulkan device, so what is asserted is what this
# platform decides: that the shader compiler THIS platform uses -- the
# rule declares `xim:shaderc` here and `xim:glslang` on Linux -- produces
# both SPIR-V headers, and that the Vulkan half compiles and links
# against the loader package. Running it is the Linux job's criterion,
# where a software device (`xim:mesa-lavapipe`) is published and the two
# legs' pixels are compared.
#
# The example is otherwise built only on Linux (`build_examples.sh` runs
# there), which is exactly the shape this change exists to remove: the
# half of a lane written for a host is the half that host never
# exercises.
- name: "Graphics: the offscreen example builds on this host"
shell: bash
run: |
set -e
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
cd "$GITHUB_WORKSPACE/examples/10-graphics/offscreen"
# The toolchain is NAMED rather than inherited: leaving it to whatever
# a neighbouring step happened to select makes this step's subject
# depend on step order, which is not a property anybody reads.
"$MCPP_SELF" build --toolchain "llvm@20.1.7"
for f in triangle_vert triangle_frag; do
d="target/.build-mcpp/out/spirv"
test -f "$d/$f.h" || { echo "missing $d/$f.h"; exit 1; }
# THE MAGIC IS NOT ALWAYS IN THE HEADER, AND THAT IS THE POINT OF
# THIS JOB. The rule chooses the shader compiler this platform
# publishes -- glslang on Linux, glslc here -- and the two split
# the declaration differently: glslang writes a complete `const
# uint32_t ...[] = {...}`, glslc an initialiser list the rule
# declares around, so the words land in `<stem>.inc`. An assertion
# naming only the header is an assertion about ONE compiler, which
# is exactly the shape this step exists to catch.
# ONE FILE AT A TIME, because `grep -qs a b` exits 2 when `b`
# does not exist -- even on a match in `a`, and even with `-s`,
# which suppresses the message and not the status. Written as one
# grep over both names, this criterion fails whenever the route
# that produces only a header is taken, which is a failure about
# the criterion and not about the shader.
found=""
for g in "$d/$f.h" "$d/$f.inc"; do
[ -f "$g" ] && grep -q '0x07230203' "$g" && found=1
done
[ -n "$found" ] \
|| { echo "$f carries no SPIR-V magic in either $f.h or $f.inc"; exit 1; }
done
echo "ok: both shader stages compiled and the Vulkan half linked"
# WINDOWS STAYS AT "BUILDS", AND FOUR MEASUREMENTS SAY WHY IT IS NOT
# WAITING ON ANY OF THE THINGS IT WAS THOUGHT TO BE.
#
# 1. Not a missing `vulkan-1.dll`. The program printed `render
# unavailable`, which `src/main.cpp` writes after the render function
# returns nothing. A process that could not resolve `vkCreateInstance`
# from that DLL fails during image load and prints nothing at all.
# mcpp-index's own `vulkan-tests` member calls
# `vkEnumerateInstanceVersion` on the windows shards and passes.
#
# 2. Not an unparseable ICD manifest, though that WAS a real defect.
# `xim:mesa-lavapipe` wrote its rewritten `library_path` into the
# JSON string unescaped, so `C:\Users\...` carried `\U` and the
# loader's cJSON parser rejected the file -- an ICD it skips with no
# error. Fixed in openxlings/xim-pkgindex#781. With the fix the runner
# reads
# "library_path": "C:/Users/.../lib/vulkan_lvp.dll"
# and the manifest parses. The program still prints `render
# unavailable`.
#
# 3. Not a failed `LoadLibrary`. `vulkan_lvp.dll` imports only
# ADVAPI32, GDI32, KERNEL32, ntdll, ole32, SHELL32 and USER32, all of
# which any Windows has; the payload ships nothing else that could be
# missing.
#
# 4. Not the environment variable's vintage. Both `VK_DRIVER_FILES` and
# `VK_ICD_FILENAMES` were set, which covers loaders on either side of
# 1.3.234.
#
# What the loader itself said under `VK_LOADER_DEBUG=all`:
#
# INFO: Loader is running with elevated permissions.
# Environment variable VK_DRIVER_FILES will be ignored
# INFO: Loader is running with elevated permissions.
# Environment variable VK_ICD_FILENAMES will be ignored
# DRIVER: Found no registry files in
# HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\Drivers
# ERROR | DRIVER: windows_read_data_files_in_registry: Registry lookup
# failed to get ICD manifest files. Possibly missing Vulkan driver?
#
# THAT IS THE WHOLE ANSWER, AND NO PACKAGE CHANGE REACHES IT. A GitHub
# Windows runner runs elevated, and the loader discards every driver-path
# environment variable when it is -- it will not let a path a
# non-administrator could write inject a driver into an elevated process.
# It then falls back to the registry, which has no ICD. The mechanism this
# example uses on Linux and macOS is simply unavailable here.
#
# Reaching lavapipe on Windows therefore means registering the ICD under
# `HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\Vulkan\Drivers`, or running the
# program unelevated. `xim:mesa-lavapipe`'s `config()` says outright that
# it places the payload and leaves naming the ICD to the consumer, so the
# registry entry is a decision for that package or for this repository,
# not a defect in either
#
# So the platform builds the Vulkan half and runs the CPU fallback, and
# the next attempt starts from here rather than from the top.
- name: "Toolchain: LLVM — build mcpp (self-host)"
shell: bash
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
cp "$MCPP_SELF" /tmp/mcpp-fresh.exe
MCPP=/tmp/mcpp-fresh.exe
"$MCPP" toolchain default llvm@20.1.7
"$MCPP" clean --bmi-cache
"$MCPP" build 2>&1 | tee build.log; grep -q "Resolved llvm@20.1.7" build.log