fix: clean build error output #251
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: 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 |