Skip to content

fix: clean build error output (#79) #252

fix: clean build error output (#79)

fix: clean build error output (#79) #252

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 → E2E → smoke → package
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ci-windows-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
name: build + test (windows x64, self-host)
runs-on: windows-latest
timeout-minutes: 45
env:
MCPP_HOME: C:\Users\runneradmin\.mcpp
steps:
- uses: actions/checkout@v4
- name: Cache mcpp sandbox
uses: actions/cache@v4
with:
path: ~\.mcpp
key: mcpp-sandbox-${{ runner.os }}-${{ hashFiles('mcpp.toml', '.xlings.json') }}
restore-keys: |
mcpp-sandbox-${{ runner.os }}-
- 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: Bootstrap mcpp via xlings
shell: bash
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '0.4.30'
run: |
WORK=$(mktemp -d)
zipfile="xlings-${XLINGS_VERSION}-windows-x86_64.zip"
curl -fsSL -o "${WORK}/${zipfile}" \
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${zipfile}"
cd "${WORK}"
unzip -q "${zipfile}"
"$WORK/xlings-${XLINGS_VERSION}-windows-x86_64/subos/default/bin/xlings.exe" self install
export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH"
echo "$USERPROFILE/.xlings/subos/default/bin" >> "$GITHUB_PATH"
xlings.exe --version
xlings.exe install mcpp -y
echo "=== Searching for mcpp binary ==="
find "$USERPROFILE/.xlings" -name "mcpp.exe" -o -name "mcpp" 2>/dev/null | head -10
MCPP=$(find "$USERPROFILE/.xlings" -name "mcpp.exe" -path "*/bin/*" 2>/dev/null | head -1)
if [ -z "$MCPP" ]; then
MCPP=$(find "$USERPROFILE/.xlings" -name "mcpp" -path "*/bin/*" 2>/dev/null | head -1)
fi
test -n "$MCPP" || { echo "FAIL: mcpp not found after xlings install"; exit 1; }
echo "Found mcpp at: $MCPP"
"$MCPP" --version
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
XLINGS_BIN=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe")
echo "XLINGS_BIN=$XLINGS_BIN" >> "$GITHUB_ENV"
- name: Build mcpp from source (self-host)
shell: bash
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$MCPP" build
MCPP_SELF=$(find target -name "mcpp.exe" -path "*/bin/*" | head -1)
test -n "$MCPP_SELF" || { echo "FAIL: no mcpp.exe"; exit 1; }
MCPP_SELF=$(cd "$(dirname "$MCPP_SELF")" && pwd)/$(basename "$MCPP_SELF")
echo "Self-hosted binary: $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
# 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: E2E suite
shell: bash
# See ci-linux.yml — fail-fast on hung tests instead of burning the
# whole job budget. Per-test 600s timeout lives in run_all.sh.
timeout-minutes: 25
run: |
export MCPP="$MCPP_SELF"
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL
"$MCPP_SELF" self config --mirror GLOBAL
"$MCPP_SELF" toolchain default llvm@20.1.7
bash tests/e2e/run_all.sh
- 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
- 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
- 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"
MCPP_BIN=$(find target -name "mcpp.exe" -path "*/bin/*" | head -1)
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