Files
pdfcraft/.github/workflows/windows-arm64.yml
T

179 lines
8.7 KiB
YAML

# Windows on ARM (#61): package the ARM64 release the way release.yml does (cross-compiled on an
# x64 runner, unsigned here), then install that MSI on a Windows 11 ARM64 runner and run it, and
# run the test suite natively on ARM64.
name: Windows ARM64
on:
push:
branches: [main]
pull_request:
paths:
- Cargo.toml
- Cargo.lock
- "**/Cargo.toml"
- "**/build.rs"
- .github/workflows/windows-arm64.yml
- packaging/windows/**
schedule:
- cron: "41 4 * * 1" # weekly, so drift in the runner images shows up
workflow_dispatch:
concurrency:
group: windows-arm64-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1
jobs:
package:
name: package (cross-compiled on x64)
runs-on: windows-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-pc-windows-msvc
- uses: Swatinem/rust-cache@v2
with:
key: windows-arm64-package
- name: Install WiX
shell: pwsh
run: dotnet tool install --global wix --version 5.0.2
- name: Build and package (unsigned)
shell: pwsh
env:
DIST: ${{ github.workspace }}\dist\arm64
run: ./packaging/windows/package.ps1 -Arch arm64
- uses: actions/upload-artifact@v4
with:
name: printcraft-windows-arm64
path: dist/arm64/*
if-no-files-found: error
retention-days: 7
install:
name: install and run on ARM64
needs: package
runs-on: windows-11-arm
timeout-minutes: 30
steps:
- uses: actions/download-artifact@v4
with:
name: printcraft-windows-arm64
path: dist
- name: Install the MSI, check it, run it, uninstall it
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$msi = (Get-ChildItem dist/*.msi | Select-Object -First 1).FullName
$p = Start-Process msiexec.exe -ArgumentList '/i', "`"$msi`"", '/qn', '/l*v', 'install.log' -Wait -PassThru
if ($p.ExitCode -ne 0) { Get-Content install.log -Tail 60; throw "msiexec /i exited $($p.ExitCode)" }
$dir = Join-Path $env:ProgramFiles 'PrintCraft'
foreach ($exe in 'printcraft.exe', 'printcraft-cli.exe') {
$path = Join-Path $dir $exe
if (-not (Test-Path $path)) { Get-ChildItem -Recurse $env:ProgramFiles -Filter $exe | Select-Object -First 3; throw "$exe not installed in $dir" }
$bytes = [System.IO.File]::ReadAllBytes($path)
$machine = [BitConverter]::ToUInt16($bytes, [BitConverter]::ToInt32($bytes, 0x3C) + 4)
if ($machine -ne 0xAA64) { throw "$exe is for machine 0x$('{0:X}' -f $machine), not ARM64" }
Write-Output "ok $path (ARM64)"
}
# The installed CLI runs natively: open, render and edit a small PDF through the engine.
$cli = Join-Path $dir 'printcraft-cli.exe'
& $cli tools | Out-Null
if ($LASTEXITCODE -ne 0) { throw "printcraft-cli tools exited $LASTEXITCODE" }
$body = 'BT /F1 24 Tf 20 150 Td (Hello ARM64) Tj ET'
$objs = @('<< /Type /Catalog /Pages 2 0 R >>', '<< /Type /Pages /Kids [3 0 R] /Count 1 >>',
'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>',
"<< /Length $($body.Length) >>`nstream`n$body`nendstream", '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>')
$sb = [System.Text.StringBuilder]::new("%PDF-1.7`n"); $offsets = @()
for ($i = 0; $i -lt $objs.Count; $i++) { $offsets += $sb.Length; [void]$sb.Append("$($i + 1) 0 obj`n$($objs[$i])`nendobj`n") }
$xref = $sb.Length
[void]$sb.Append("xref`n0 $($objs.Count + 1)`n0000000000 65535 f `n")
foreach ($o in $offsets) { [void]$sb.Append(('{0:D10} 00000 n `n' -f $o)) }
[void]$sb.Append("trailer`n<< /Size $($objs.Count + 1) /Root 1 0 R >>`nstartxref`n$xref`n%%EOF`n")
New-Item -ItemType Directory -Force smoke | Out-Null
[System.IO.File]::WriteAllText("$PWD\smoke\hello.pdf", $sb.ToString(), [System.Text.Encoding]::ASCII)
$steps = '[{"tool":"doc_open","args":{"path":"hello.pdf"}},{"tool":"text_paragraphs","args":{"doc":1,"page":1}},{"tool":"text_edit","args":{"doc":1,"page":1,"paragraph":1,"text":"Edited on ARM64"}},{"tool":"page_render","args":{"doc":1,"page":1,"dpi":72},"out":"smoke/page.png"},{"tool":"doc_save","args":{"doc":1,"path":"out.pdf"}}]'
[System.IO.File]::WriteAllText("$PWD\smoke\steps.json", $steps)
$out = & $cli run --script smoke\steps.json --root smoke 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) { Write-Output $out; throw "printcraft-cli run exited $LASTEXITCODE" }
if ($out -notmatch 'Hello ARM64') { Write-Output $out; throw 'text_paragraphs did not read the page' }
foreach ($f in 'smoke\page.png', 'smoke\out.pdf') { if (-not (Test-Path $f) -or (Get-Item $f).Length -eq 0) { Write-Output $out; throw "$f was not written" } }
Write-Output 'ok printcraft-cli: opened, read, edited, rendered and saved a PDF on ARM64'
$p = Start-Process msiexec.exe -ArgumentList '/x', "`"$msi`"", '/qn' -Wait -PassThru
if ($p.ExitCode -ne 0) { throw "msiexec /x exited $($p.ExitCode)" }
if (Test-Path (Join-Path $dir 'printcraft.exe')) { throw 'uninstall left printcraft.exe behind' }
Write-Output 'ok uninstalled'
test:
name: test (native ARM64)
runs-on: windows-11-arm
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
key: windows-arm64-test
- name: Test
run: cargo test --workspace --locked
# TEMPORARY (remove before merge): find the test in tests/view.rs that intermittently dies with
# STATUS_ACCESS_VIOLATION on ARM64. Single-threaded, so the last "test … " line names it.
diag-view:
name: diag view.rs crash (temporary)
runs-on: windows-11-arm
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
key: windows-arm64-test
- name: Build the view tests
run: cargo test -p printcraft-ui-egui --test view --no-run
- name: Crash dump of the view tests, with the crashing stack
shell: pwsh
run: |
$dumps = "$env:RUNNER_TEMP\dumps"; New-Item -ItemType Directory -Force $dumps | Out-Null
$key = 'HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps'
New-Item -Force $key | Out-Null
Set-ItemProperty $key DumpFolder $dumps; Set-ItemProperty $key DumpType 2; Set-ItemProperty $key DumpCount 3
$exe = (Get-ChildItem target\debug\deps\view-*.exe | Sort-Object LastWriteTime | Select-Object -Last 1).FullName
Write-Output "binary: $exe"
for ($i = 1; $i -le 60; $i++) {
& $exe --quiet *> $null
if ($LASTEXITCODE -ne 0) { Write-Output "crashed on run $i (exit $LASTEXITCODE)"; Start-Sleep 5; if (Get-ChildItem $dumps -Filter *.dmp) { break } }
}
$dump = Get-ChildItem $dumps -Filter *.dmp | Select-Object -First 1
if (-not $dump) { Write-Output 'no dump written'; exit 0 }
$cdb = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\Debuggers' -Recurse -Filter cdb.exe -ErrorAction SilentlyContinue | Sort-Object { $_.FullName -notmatch 'arm64' } | Select-Object -First 1
if (-not $cdb) { Write-Output 'cdb not found'; Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10' -Directory | Out-String; exit 0 }
Write-Output "cdb: $($cdb.FullName)"
& $cdb.FullName -z $dump.FullName -y "srv*;$(Split-Path $exe)" -c ".ecxr; kn 40; ~* kn 12; q" 2>&1 | Select-Object -Last 220
- name: "A: parallel (the default), 30 runs"
shell: bash
run: |
crashes=0
for i in $(seq 1 30); do
cargo test -q -p printcraft-ui-egui --test view > run.log 2>&1 || { crashes=$((crashes+1)); grep -E "STATUS_|panicked" run.log | head -2; }
done
echo "A parallel: crashes $crashes / 30"
- name: "B: parallel with 16 MB test-thread stacks, 30 runs"
shell: bash
env:
RUST_MIN_STACK: "16777216"
run: |
crashes=0
for i in $(seq 1 30); do
cargo test -q -p printcraft-ui-egui --test view > run.log 2>&1 || { crashes=$((crashes+1)); grep -E "STATUS_|panicked" run.log | head -2; }
done
echo "B big stacks: crashes $crashes / 30"