Skip to content

test: app exit code in ci #104

test: app exit code in ci

test: app exit code in ci #104

Workflow file for this run

name: Windows CI
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
permissions:
contents: read
id-token: write # Azure OIDC: the launch/exit smoke test signs the exe first,
# because an unsigned uiAccess binary refuses to launch
jobs:
build:
name: Windows build test (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: windows-latest
triplet: x64-windows-static
vcvars_arch: amd64
- arch: arm64
runner: windows-11-arm
triplet: arm64-windows-static
vcvars_arch: arm64
concurrency:
group: windows-ci-${{ matrix.arch }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: pwsh
env:
CLIPP_RELEASE_TRIPLET: ${{ matrix.triplet }}
VCPKG_BINARY_SOURCES: clear;files,${{ github.workspace }}/vcpkg-cache,readwrite
steps:
- name: Checkout clipp
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Read vcpkg baseline
id: vcpkg-baseline
run: |
$manifest = Get-Content -Raw src\vcpkg.json | ConvertFrom-Json
$baseline = $manifest.'builtin-baseline'
if (-not $baseline) {
throw "src\vcpkg.json must define builtin-baseline for CI."
}
"ref=$baseline" >> $env:GITHUB_OUTPUT
- name: Set vcpkg short root
run: |
$workspaceRoot = [System.IO.Path]::GetPathRoot($env:GITHUB_WORKSPACE)
if (-not $workspaceRoot) {
throw "Could not determine workspace drive from GITHUB_WORKSPACE='$env:GITHUB_WORKSPACE'."
}
$vcpkgRoot = Join-Path $workspaceRoot "v"
if (Test-Path $vcpkgRoot) {
throw "vcpkg short root already exists: $vcpkgRoot"
}
New-Item -ItemType Directory -Path $vcpkgRoot | Out-Null
"VCPKG_ROOT=$vcpkgRoot" >> $env:GITHUB_ENV
Write-Host "Using VCPKG_ROOT=$vcpkgRoot"
- name: Checkout vcpkg
run: |
git clone --no-tags https://github.com/microsoft/vcpkg.git $env:VCPKG_ROOT
git -C $env:VCPKG_ROOT checkout --detach ${{ steps.vcpkg-baseline.outputs.ref }}
- name: Bootstrap vcpkg
run: |
$bootstrap = Join-Path $env:VCPKG_ROOT "bootstrap-vcpkg.bat"
& $bootstrap -disableMetrics
- name: Prepare vcpkg binary cache
run: New-Item -ItemType Directory -Force vcpkg-cache | Out-Null
- name: Cache vcpkg binaries
uses: actions/cache@v5
with:
path: vcpkg-cache
key: ${{ runner.os }}-${{ runner.arch }}-vcpkg-${{ matrix.triplet }}-${{ hashFiles('src/vcpkg.json', 'src/vcpkg-triplets/**') }}
restore-keys: |
${{ runner.os }}-${{ runner.arch }}-vcpkg-${{ matrix.triplet }}-
- name: Build Release
run: .\scripts\build_windows.ps1 -BuildType Release -Triplet $env:CLIPP_RELEASE_TRIPLET -VcVarsArch ${{ matrix.vcvars_arch }} -DisableCodeSigning
- name: Verify Windows artifacts
run: |
$exe = ".\build\windows-release\clipp.exe"
$shim = ".\build\windows-release\clipp.com"
if (-not (Test-Path $exe)) {
throw "Missing expected executable: $exe"
}
if (-not (Test-Path $shim)) {
throw "Missing expected console shim: $shim"
}
- name: Verify static dependency closure
run: |
$exe = ".\build\windows-release\clipp.exe"
$vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe"
$installPath = & $vswhere -latest -products * -property installationPath
$vcvars = Join-Path $installPath "VC\Auxiliary\Build\vcvarsall.bat"
$dump = & cmd.exe /d /s /c "call `"$vcvars`" ${{ matrix.vcvars_arch }} >nul && dumpbin /dependents `"$exe`""
$dump
$blocked = $dump | Select-String -Pattern "VCRUNTIME|MSVCP|ucrtbase|libsodium|xxhash|zstd|lodepng" -CaseSensitive:$false
if ($blocked) {
throw "Release exe imports non-system runtime/dependency DLLs: $($blocked -join '; ')"
}
# ---- launch/exit smoke test -------------------------------------------------
# Replays the winget validation harness's launch-and-quit sequence and fails on any
# nonzero exit code — it treats one as a failed run (winget-pkgs PR #408205: Clipp
# 1.4.0.150 came back "returned exit code: -1073741189" = 0xC000027B,
# STATUS_STOWED_EXCEPTION; ../WM_NIGHT died the same way in #393605).
#
# This MUST test the shipped bits: build_windows.ps1 stamps uiAccess=true into every
# Release exe, and an unsigned uiAccess binary REFUSES TO LAUNCH ("A referral was
# returned from the server"). Stripping the stamp to make it launchable would test a
# binary we never ship, so CI signs first — same Trusted Signing path as the release
# workflow — and launches the real thing.
#
# Signing needs the AZURE_* secrets, which are absent on fork PRs. Those legs skip
# the smoke test rather than fail: nothing to verify without a signature.
- name: Can we sign? (smoke test needs a signed binary)
id: cansign
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
run: |
$ok = [bool]$env:AZURE_CLIENT_ID
"ok=$($ok.ToString().ToLower())" >> $env:GITHUB_OUTPUT
if (-not $ok) {
Write-Host "::notice::No signing credentials (fork PR?) — skipping the launch/exit smoke test."
}
- name: Azure login (Trusted Signing)
if: steps.cansign.outputs.ok == 'true'
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign Windows artifacts
if: steps.cansign.outputs.ok == 'true'
uses: azure/trusted-signing-action@v0
with:
endpoint: ${{ vars.ARTIFACT_SIGNING_ENDPOINT }}
trusted-signing-account-name: ${{ vars.ARTIFACT_SIGNING_ACCOUNT }}
certificate-profile-name: ${{ vars.ARTIFACT_SIGNING_CERTIFICATE_PROFILE }}
files-folder: ${{ github.workspace }}\build\windows-release
files-folder-filter: exe,com
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
# The action's dependency cache keys are arch-agnostic, so the amd64 and
# arm64 matrix legs poison each other's TrustedSigning module cache
# (Azure/trusted-signing-action#146). Fresh install costs ~1 min per leg.
cache-dependencies: false
- name: Verify signature and uiAccess stamp
if: steps.cansign.outputs.ok == 'true'
run: |
$exe = ".\build\windows-release\clipp.exe"
$sig = Get-AuthenticodeSignature $exe
if ($sig.Status -ne 'Valid') {
throw "Signature on $exe is not Valid: $($sig.Status) — $($sig.StatusMessage)"
}
Write-Host "[+] signed by: $($sig.SignerCertificate.Subject)"
# Assert we are about to launch a uiAccess binary — the whole point is that the
# shipped configuration is what gets exercised.
$mt = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.*\*\mt.exe" -ErrorAction SilentlyContinue |
Sort-Object FullName -Descending | Select-Object -First 1
if ($mt) {
$manifest = Join-Path $env:RUNNER_TEMP 'shipped.manifest'
& $mt.FullName -nologo -inputresource:"$exe;#1" -out:"$manifest" | Out-Null
$xml = Get-Content -Raw $manifest
if ($xml -match 'uiAccess="true"') { Write-Host '[+] uiAccess=true present (shipped configuration).' }
else { Write-Host '::warning::uiAccess=true NOT present in the embedded manifest.' }
}
- name: Enable WER local crash dumps
if: steps.cansign.outputs.ok == 'true'
run: |
# A stowed exception is a WinRT/COM fail-fast: it bypasses unhandled-exception
# filters, so Clipp's own crash handler never sees it and writes nothing. WER
# LocalDumps catches it at a lower level — the only way to get a dump at all.
$dumps = Join-Path $env:RUNNER_TEMP 'werdumps'
New-Item -ItemType Directory -Force $dumps | Out-Null
$key = 'HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\clipp.exe'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name DumpFolder -Value $dumps -Type ExpandString
Set-ItemProperty -Path $key -Name DumpType -Value 2 -Type DWord # full dump
Set-ItemProperty -Path $key -Name DumpCount -Value 5 -Type DWord
"WER_DUMP_DIR=$dumps" >> $env:GITHUB_ENV
# -RequireWindow: this runner has a virgin HKCU, so no group key exists and Clipp
# must open its first-run XAML-Islands window — the exact path a validation VM takes,
# and the one a developer box with a configured key never exercises. If the window
# never appears, the island failed to come up: that is a failure, not a shrug.
# Graceful path: tray-menu Exit runs main()'s full teardown and must return 0.
- name: Smoke test — graceful exit (signed, uiAccess-stamped exe)
if: steps.cansign.outputs.ok == 'true'
run: .\scripts\exitcode_smoke.ps1 -ExePath ".\build\windows-release\clipp.exe" -RequireWindow -CloseMode TrayExit
# Harness path: close the main window like Process.CloseMainWindow, then verify the
# app stays resident instead of self-terminating. A 0xC000027B here IS the winget
# validation failure reproduced — this is the leg that can actually catch it.
- name: Smoke test — winget harness sequence
if: steps.cansign.outputs.ok == 'true'
run: .\scripts\exitcode_smoke.ps1 -ExePath ".\build\windows-release\clipp.exe" -RequireWindow -CloseMode Harness
- name: Collect crash diagnostics
if: failure() && steps.cansign.outputs.ok == 'true'
run: |
Write-Host '--- Application Error / WER events (last 30 min) ---'
Get-WinEvent -FilterHashtable @{
LogName = 'Application'; StartTime = (Get-Date).AddMinutes(-30)
} -ErrorAction SilentlyContinue |
Where-Object { $_.ProviderName -in 'Application Error', 'Windows Error Reporting' -and
$_.Message -match 'clipp' } |
ForEach-Object { $_.TimeCreated; $_.Message; '---' }
Write-Host "--- Dumps in $env:WER_DUMP_DIR ---"
Get-ChildItem $env:WER_DUMP_DIR -ErrorAction SilentlyContinue
- name: Upload crash dumps
if: failure() && steps.cansign.outputs.ok == 'true'
uses: actions/upload-artifact@v4
with:
name: werdumps-${{ matrix.arch }}
path: ${{ env.WER_DUMP_DIR }}
if-no-files-found: ignore