name: wai release binaries build on Windows # The Windows archive of a wai release holds seven binaries: wai, wai-relay, wai-room, wai-ingest, # wai-remote-host, wai-pack and wai-quantum (wai-release.yml's Windows job). Until this workflow, # only the release itself built them on Windows, so a Windows-only break reached the release: # wai-v0.5.0's Windows leg failed to link wai-ingest, because joule-contrib declared POSIX # getrusage unconditionally (unresolved external symbol getrusage). # # This builds the same seven binaries for x86_64-pc-windows-msvc, as the release does (same # directories, packages, features and --locked), so a link or compile error shows up on main. # It is a debug build: it links every binary, which is what catches a missing symbol, in a # fraction of a release build's time on the one Windows runner. Then it does what the release's # Windows job does after its build, with those binaries (wai-quantum's built again in release, as a # debug one overflows the Windows main thread's stack): it stages the archive, runs `wai.exe # create --list`, `wai-quantum.exe --version` and wai/quickstart/test/archive_doctor.py from it, # zips it, and runs the quickstart from the zip as a new user runs it # (wai/quickstart/test/clean-machine.ps1), so those steps have run on a Windows archive before a # tag needs them. It runs nightly, and on a push to main that changes a crate the Windows archive # builds from joule-code, or one of the files those steps run (wai-rs, wai-pack and # wai-quantum-cli have Windows jobs of their own). # # Windows runner notes: shell pwsh (PowerShell 7); the runner injects CI=true, cleared as the # release job clears it. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }} cancel-in-progress: true on: schedule: - cron: '20 5 * * *' push: branches: [main] paths: - 'joule-code/joule-code-rs/Cargo.lock' - 'joule-code/joule-code-rs/Cargo.toml' - 'joule-code/joule-code-rs/crates/joule-contrib/**' - 'joule-code/joule-code-rs/crates/joule-moq-ingest/**' - 'joule-code/joule-code-rs/crates/joule-moq-relay/**' - 'joule-code/joule-code-rs/crates/joule-moq-room/**' - 'joule-code/joule-code-rs/crates/joule-moq-remote/**' - 'wai/quickstart/test/archive_doctor.py' - 'wai/quickstart/test/clean-machine.ps1' - '.github/workflows/wai-release-windows-build.yml' env: CARGO_TERM_COLOR: always jobs: build: name: the seven release binaries ยท windows runs-on: windows timeout-minutes: 120 env: CI: "" steps: - uses: actions/checkout@v5 - name: Build each binary the Windows archive holds shell: pwsh run: | $ErrorActionPreference = 'Stop' Remove-Item Env:CI -ErrorAction SilentlyContinue $t = "x86_64-pc-windows-msvc" # Each binary built is copied here, for the archive the next step stages. $bins = Join-Path $env:RUNNER_TEMP 'bins' Remove-Item $bins -Recurse -Force -ErrorAction Ignore New-Item -ItemType Directory -Force -Path $bins | Out-Null $builds = @( @{ dir = 'wai\wai-rs'; bin = 'wai'; args = @('--no-default-features', '--features', 'quickstart', '--bin', 'wai') }, @{ dir = 'joule-code\joule-code-rs'; bin = 'wai-relay'; args = @('-p', 'joule-moq-relay', '--bin', 'wai-relay') }, @{ dir = 'joule-code\joule-code-rs\crates\joule-moq-room'; bin = 'wai-room'; args = @('--bin', 'wai-room') }, @{ dir = 'joule-code\joule-code-rs'; bin = 'wai-ingest'; args = @('-p', 'joule-moq-ingest', '--bin', 'wai-ingest') }, @{ dir = 'joule-code\joule-code-rs\crates\joule-moq-remote'; bin = 'wai-remote-host'; args = @('--bin', 'wai-remote-host') }, @{ dir = 'wai\wai-pack'; bin = 'wai-pack'; args = @('--bin', 'wai-pack') }, @{ dir = 'wai\wai-quantum-cli'; bin = 'wai-quantum'; args = @('--bin', 'wai-quantum') } ) $failed = @() foreach ($b in $builds) { Push-Location $b.dir cargo build --locked --target $t @($b.args) if ($LASTEXITCODE -ne 0) { $failed += $b.bin; Write-Host "::error::the Windows build of $($b.bin) failed" } else { $target = (cargo metadata --format-version 1 --no-deps --locked | ConvertFrom-Json).target_directory $exe = Join-Path $target "$t\debug\$($b.bin).exe" if (-not (Test-Path $exe)) { $failed += $b.bin; Write-Host "::error::$($b.bin).exe was not produced" } else { Copy-Item $exe $bins; Write-Host "built $($b.bin).exe" } } Pop-Location } if ($failed.Count -gt 0) { throw "the Windows build failed for: $($failed -join ', ')" } Write-Host "all seven release binaries build and link for $t" # The rest of the release's Windows leg, which no tag has reached (wai-v0.5.0's failed at the # build): the archive staged as wai-release.yml's Windows job stages it, from the debug # binaries above, then `wai.exe create --list`, `wai-quantum.exe --version`, # archive_doctor.py, the zip and its .sha256, and the quickstart from the zip # (clean-machine.ps1), so a Windows-only break in those steps shows up on main and not at a # tag. The room client and WAI Studio are the newest wai release's (wai-web-.tar.gz, # checked against its .sha256), as the release's own Windows job takes them from its web # job: building them needs the WebAssembly toolchain and Node, which only the macOS runner # has. Keep these two steps in step with wai-release.yml's Windows job. - name: Package the archive as the release does shell: pwsh env: FORGE: http://100.64.224.0:3400 FORGE_REPO: Transaction-Science/open-standards run: | $ErrorActionPreference = 'Stop' Remove-Item Env:CI -ErrorAction SilentlyContinue $t = "x86_64-pc-windows-msvc" $v = (Select-String -Path wai\wai-rs\Cargo.toml -Pattern '^version = "(.*)"$' | Select-Object -First 1).Matches[0].Groups[1].Value # The newest wai release that holds the room client and Studio. $rels = Invoke-RestMethod -TimeoutSec 60 -Uri "$env:FORGE/api/v1/repos/$env:FORGE_REPO/releases?limit=30" $rel = $rels | Where-Object { $_.tag_name -like 'wai-v*' -and ($_.assets | Where-Object { $_.name -like 'wai-web-*.tar.gz' }) } | Select-Object -First 1 if (-not $rel) { throw "no wai release on the forge holds a wai-web archive" } $webName = ($rel.assets | Where-Object { $_.name -like 'wai-web-*.tar.gz' } | Select-Object -First 1).name $web = Join-Path $env:RUNNER_TEMP $webName $from = "$env:FORGE/$env:FORGE_REPO/releases/download/$($rel.tag_name)" curl.exe -fsS -m 300 -o $web "$from/$webName" if ($LASTEXITCODE -ne 0) { throw "the download of $webName from $($rel.tag_name) failed" } curl.exe -fsS -m 60 -o "$web.sha256" "$from/$webName.sha256" if ($LASTEXITCODE -ne 0) { throw "the download of $webName.sha256 from $($rel.tag_name) failed" } $want = ((Get-Content "$web.sha256" -Raw).Trim() -split '\s+')[0].ToLower() $got = (Get-FileHash $web -Algorithm SHA256).Hash.ToLower() if ($got -ne $want) { throw "$webName is SHA-256 $got, not $want" } Write-Host "the room client and Studio: $webName from $($rel.tag_name)" Remove-Item (Join-Path $env:RUNNER_TEMP 'web'), (Join-Path $env:RUNNER_TEMP 'studio') -Recurse -Force -ErrorAction Ignore tar -xzf $web -C $env:RUNNER_TEMP if ($LASTEXITCODE -ne 0) { throw "$webName did not unpack" } # wai-quantum as the release builds it: a debug wai-quantum.exe overflows the 1 MB stack # Windows gives a program's main thread before it prints its version. Push-Location wai\wai-quantum-cli cargo build --locked --release --target $t --bin wai-quantum if ($LASTEXITCODE -ne 0) { throw "the release build of wai-quantum failed" } $target = (cargo metadata --format-version 1 --no-deps --locked | ConvertFrom-Json).target_directory Copy-Item -Force (Join-Path $target "$t\release\wai-quantum.exe") (Join-Path $env:RUNNER_TEMP 'bins') Pop-Location # From here as wai-release.yml's Windows job, with this run's debug binaries (wai-quantum's # a release build). $name = "wai-$v-$t" $stage = Join-Path $env:RUNNER_TEMP $name $dist = Join-Path $env:RUNNER_TEMP 'dist' Remove-Item $stage, $dist -Recurse -Force -ErrorAction Ignore New-Item -ItemType Directory -Force -Path (Join-Path $stage 'bin'), (Join-Path $stage 'share\wai'), $dist | Out-Null Copy-Item (Join-Path $env:RUNNER_TEMP 'bins\*.exe') (Join-Path $stage 'bin') Copy-Item -Recurse (Join-Path $env:RUNNER_TEMP 'web') (Join-Path $stage 'share\wai\web') Copy-Item -Recurse (Join-Path $env:RUNNER_TEMP 'studio') (Join-Path $stage 'share\wai\studio') Copy-Item wai\quickstart\README.md, wai\LICENSE $stage & (Join-Path $stage 'bin\wai.exe') create --list if ($LASTEXITCODE -ne 0) { throw "the packaged wai does not run" } & (Join-Path $stage 'bin\wai-quantum.exe') --version if ($LASTEXITCODE -ne 0) { throw "the packaged wai-quantum does not run" } python -I wai\quickstart\test\archive_doctor.py $stage if ($LASTEXITCODE -ne 0) { throw "wai doctor from the archive: an install or stack check did not pass" } Compress-Archive -Path $stage -DestinationPath (Join-Path $dist "$name.zip") -Force $h = (Get-FileHash (Join-Path $dist "$name.zip") -Algorithm SHA256).Hash.ToLower() Set-Content -NoNewline -Path (Join-Path $dist "$name.zip.sha256") -Value "$h $name.zip`n" Write-Host "packaged $name.zip ($((Get-Item (Join-Path $dist "$name.zip")).Length) bytes)" - name: The quickstart from the archive, as a new user runs it shell: pwsh run: | $ErrorActionPreference = 'Stop' Remove-Item Env:CI -ErrorAction SilentlyContinue $v = (Select-String -Path wai\wai-rs\Cargo.toml -Pattern '^version = "(.*)"$' | Select-Object -First 1).Matches[0].Groups[1].Value $name = "wai-$v-x86_64-pc-windows-msvc" pwsh -NoProfile -File wai\quickstart\test\clean-machine.ps1 (Join-Path $env:RUNNER_TEMP "dist\$name.zip") if ($LASTEXITCODE -ne 0) { throw "the quickstart failed from $name.zip" }