open-standards/.github/workflows/wai-examples.yml
Transaction Science 213c6aee6c wai doctor: ends what an answered program leaves running; CI checks each archive
Linux and macOS: a program asked is now waited for with waitid and
WNOWAIT, which leaves it unreaped. Once it has exited, its process
group gets SIGKILL, and only then is it reaped. While the leader is an
unreaped zombie, its group's number cannot be given to another group.
So this ends what the program left running behind it, whether or not
it answered, and nothing else. A program that does not answer in time
is stopped with its group before it is reaped, as before. Other Unix
systems keep the polling wait. On Windows the job's kill-on-close does
the same, and the new test runs there too.

A program file that is open for writing somewhere cannot be run for a
moment (ETXTBSY), for example one just written while another thread of
this process starts a program. A spawn refused so is now tried again
for up to 200 ms. This was a real flake in the tests that write
stand-in programs, and wai doctor itself asks several programs at once.

CI: wai/quickstart/test/archive_doctor.py runs bin/wai doctor --json
from an unpacked archive, with the archive's bin first on PATH. It
fails unless every install and stack check passes and each piece those
checks found is inside the archive. Without that, a relay elsewhere on
PATH or Studio's files from WAI_STUDIO would hide a piece the archive
lacks. Ports, browsers and the keystore are printed, not required.
stack.web and stack.studio now name their path in facts.

The release runs it on every leg that builds an archive: macOS where
the triple is the host's, Linux x86_64 and arm64, and Windows on the
staged directory. At PR time it runs on the macOS archive wai-examples
builds, the only archive built then. archive_doctor_test.py runs it
against stand-ins in wai-examples' portable job.

Real browsers on Linux: wai-studio's webkit job runs in the browser
image and builds wai. It now requires browser.conformance to pass with
that wai.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 17:24:17 -05:00

254 lines
13 KiB
YAML

name: WAI examples
# Every example in wai/examples/ run as a person runs it: `wai create` makes the project from
# the template the binary holds, and `wai dev` runs it (wai/quickstart/test/run-examples.sh).
# Each fails the job if its example stops working.
#
# - portable (linux): remote-desktop (a drawn screen shared into a room, the session accepted
# by the independent verifier), package-and-deliver (packed and verified), verify-receipts
# (the Rust verifier on this checkout's wai crate and the Python one, each refusing a changed
# byte), encoder-ingest (SRT into a room, every group verified against its receipt); and the
# package managers' entries the release writes (wai/quickstart/test/formulae_test.py): every
# binary of the archive but wai-quantum, which they depend on as its own package, and that
# one built as its own release builds it; and the check of `wai doctor` from an archive
# (wai/quickstart/test/archive_doctor_test.py), against stand-ins.
# - browser (macos): the room client and WAI Studio built and packaged as the release packages
# them (wai/quickstart/build-web.sh, build-studio.sh, build.sh, its bin/ with wai-ingest and
# wai-quantum), the checkout left as it was; the quickstart's readiness check, `wai dev --check`,
# from the unpacked archive in an empty directory, Studio at /_wai/ among the pieces it checks;
# `wai doctor --json` from the unpacked archive, every install and stack check passed
# (wai/quickstart/test/archive_doctor.py; the release runs it on every platform's archive);
# the quickstart on a clean machine (wai/quickstart/test/clean-machine.sh: a fresh HOME and
# directory, the system's PATH, every binary and Studio's files in the archive, each binary
# run, then hello, verify, create and dev --check); then video-room, watch-together,
# messaging, remote-desktop and package-and-deliver in the host's browser; and apple-app
# (the native sink's macOS slice, the SwiftUI app built, a recorded session replayed).
# - android (linux-arm64): android-app, the native sink's Kotlin API on the host JVM.
#
# Push filters match the push's delta, so every path an example builds on is listed.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
paths:
- 'wai/examples/**'
- 'wai/quickstart/**'
- 'wai/wai-rs/**'
- 'wai/wai-pack/**'
- 'wai/wai-live-web/**'
- 'wai/wai-quantum/**'
- 'wai/wai-quantum-cli/**'
- 'wai/wai-quantum-qpu/**'
- 'wai/wai-studio/**'
- 'wai/quantum-studio/**'
- 'wai/wai-web/**'
- 'wai/wai-ops/**'
- 'wai/wai-sink-swift/**'
- 'wai/wai-sink-android/**'
- 'wai/claim-conformance/_ed25519.py'
- 'wai/claim-conformance/_blake3.py'
- 'wai/live-conformance/remote_desktop_verify.py'
- 'wai/live-conformance/live_rooms_verify.py'
- 'wai/live-conformance/telemetry_aggregate_verify.py'
- 'wai/pack-conformance/packs/video-base-01/**'
- 'joule-code/joule-code-rs/**'
# Read by wai/quickstart/test/formulae_test.py.
- '.github/workflows/wai-release.yml'
- '.github/workflows/wai-quantum-cli-release.yml'
- '.github/workflows/wai-examples.yml'
schedule:
- cron: '17 5 * * *'
jobs:
# The path filters gate the trigger; this job gates the work from the pull request's own
# diff (scripts/pr-touches-paths.py, reading the list above), as wai-pack.yml explains.
relevant:
name: WAI examples · does this change touch them
runs-on: linux
outputs:
run: ${{ steps.gate.outputs.run }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: gate
run: |
set -euo pipefail
r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-examples.yml \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}")
echo "this change touches the examples: $r"
echo "run=$r" >> "$GITHUB_OUTPUT"
portable:
name: WAI examples · portable · linux
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux
timeout-minutes: 90
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.99.1
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1
rustup toolchain install 1.99.1 --profile minimal
rustup default 1.99.1
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
command -v python3 >/dev/null && command -v curl >/dev/null || { $SUDO apt-get update -qq; $SUDO apt-get install -y -qq python3 curl; }
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
# The binaries the archive holds, from this checkout. The portable examples load no page,
# so the room client's files are the source tree's (WAI_WEB), without its WebAssembly.
- name: Build wai, the relay, the room service, the remote host and the packager
shell: bash
run: |
set -euo pipefail
b="$RUNNER_TEMP/bin"; mkdir -p "$b"
out() { (cd "$1" && cargo metadata --format-version 1 --no-deps --locked | python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])'); }
(cd wai/wai-rs && cargo build --locked --release --no-default-features --features quickstart --bin wai) && cp "$(out wai/wai-rs)/release/wai" "$b/"
(cd joule-code/joule-code-rs && cargo build --locked --release -p joule-moq-relay --bin wai-relay) && cp "$(out joule-code/joule-code-rs)/release/wai-relay" "$b/"
(cd joule-code/joule-code-rs/crates/joule-moq-room && cargo build --locked --release --bin wai-room) && cp "$(out joule-code/joule-code-rs/crates/joule-moq-room)/release/wai-room" "$b/"
(cd joule-code/joule-code-rs/crates/joule-moq-remote && cargo build --locked --release --bin wai-remote-host) && cp "$(out joule-code/joule-code-rs/crates/joule-moq-remote)/release/wai-remote-host" "$b/"
(cd wai/wai-pack && cargo build --locked --release --bin wai-pack) && cp "$(out wai/wai-pack)/release/wai-pack" "$b/"
echo "$b" >> "$GITHUB_PATH"
- name: The portable examples
shell: bash
env:
WAI_WEB: ${{ github.workspace }}/joule-code/joule-code-rs/crates/joule-moq-web/web
run: bash wai/quickstart/test/run-examples.sh portable
- name: The Homebrew formula and the Scoop manifest the release writes
shell: bash
run: python3 -I wai/quickstart/test/formulae_test.py
- name: The check of wai doctor from an archive, against stand-ins
shell: bash
run: python3 -I wai/quickstart/test/archive_doctor_test.py
browser:
name: WAI examples · the archive, the readiness check, the browser and Apple · macos
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: macos
timeout-minutes: 120
steps:
- uses: actions/checkout@v5
# launchd's PATH omits cargo and node on the macOS host runner.
- name: Put cargo and node on PATH
shell: bash
run: |
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
echo "/opt/homebrew/bin" >> "$GITHUB_PATH"
- name: wasm32 target and wasm-bindgen CLI 0.2.127
shell: bash
run: |
rustup target add wasm32-unknown-unknown
command -v wasm-bindgen >/dev/null && wasm-bindgen --version | grep -q ' 0\.2\.127$' || cargo install --locked wasm-bindgen-cli --version 0.2.127
- name: The room client, WAI Studio and the archive, as the release builds them
shell: bash
run: |
set -euo pipefail
nice -n 10 bash wai/quickstart/build-web.sh "$RUNNER_TEMP/web"
nice -n 10 bash wai/quickstart/build-studio.sh "$RUNNER_TEMP/studio"
nice -n 10 bash wai/quickstart/build.sh "$RUNNER_TEMP/dist" --web "$RUNNER_TEMP/web" --studio "$RUNNER_TEMP/studio"
# The three builds leave the checkout as they found it: everything they write is under
# an ignored directory or outside the tree.
left=$(git status --porcelain --untracked-files=all)
[ -z "$left" ] || { echo "$left"; echo "::error::the builds left files in the checkout"; exit 1; }
mkdir -p "$RUNNER_TEMP/unpacked"
tar -C "$RUNNER_TEMP/unpacked" -xzf "$RUNNER_TEMP"/dist/wai-*.tar.gz
echo "WAI_DIST=$(ls -d "$RUNNER_TEMP"/unpacked/wai-*)" >> "$GITHUB_ENV"
# The quickstart's readiness check: from the archive alone, in an empty directory, the
# relay, the room service and the page on one origin, each piece checked through it.
- name: wai hello, wai verify and wai dev --check from the archive
shell: bash
run: |
set -euo pipefail
d="$RUNNER_TEMP/clean"; rm -rf "$d"; mkdir -p "$d"; cd "$d"
"$WAI_DIST/bin/wai" hello h
"$WAI_DIST/bin/wai" verify h
mkdir p && cd p && "$WAI_DIST/bin/wai" dev --check --port 0 | tee "$RUNNER_TEMP/dev-check.out"
grep -q "ok WAI Studio at /_wai/" "$RUNNER_TEMP/dev-check.out" || { echo "::error::the archive's wai dev does not serve WAI Studio"; exit 1; }
# wai doctor from the unpacked archive, its bin first on PATH: every install and stack check
# passes; the ports and the browsers depend on the runner, and are printed.
- name: wai doctor from the archive
shell: bash
run: python3 -I wai/quickstart/test/archive_doctor.py "$WAI_DIST"
# The quickstart as a new user runs it, from the archive and its .sha256 alone, with no
# browser step: it fails when the archive lacks any binary, the room client, or Studio's
# page and core, or when a step fails.
- name: The quickstart on a clean machine, from the archive
shell: bash
run: bash wai/quickstart/test/clean-machine.sh "$RUNNER_TEMP"/dist/wai-*.tar.gz
- name: The examples in the browser
shell: bash
env:
BROWSER_BIN: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome # allow-name: the macOS runner's browser executable, a file path
run: |
set -euo pipefail
test -x "$BROWSER_BIN" || { echo "::error::this runner has no browser at $BROWSER_BIN"; exit 1; }
nice -n 10 bash wai/quickstart/test/run-examples.sh browser
# The apple example builds WaiFFI.xcframework, which cross-compiles each
# slice (wai-sink-swift/scripts/build-xcframework.sh names the set in its
# header). A target's std belongs to one toolchain, so the pin moving to
# 1.99.1 left this job with none of them, and the macos slice failed at
# x86_64-apple-darwin with E0463 "can't find crate for `core`". The step
# had been passing on targets a previous toolchain happened to have
# installed on the runner; naming them here is what makes it reproducible
# on a host that has never built this. The tvos slices' nightly is a
# separate toolchain and keeps its own targets, so it is not re-added.
- name: The targets the apple slices need
shell: bash
run: |
set -euo pipefail
rustup target add aarch64-apple-darwin x86_64-apple-darwin aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
- name: apple-app
shell: bash
run: nice -n 10 bash wai/quickstart/test/run-examples.sh apple
android:
name: WAI examples · android-app · linux-arm64
needs: relevant
if: needs.relevant.outputs.run == 'true'
runs-on: linux-arm64
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.99.1
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1
rustup toolchain install 1.99.1 --profile minimal
rustup default 1.99.1
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: JDK 17
shell: bash
run: |
set -euo pipefail
j="/usr/lib/jvm/java-17-openjdk-$(dpkg --print-architecture)"
if [ ! -x "$j/bin/javac" ]; then
sudo=$(command -v sudo || true)
$sudo apt-get update -qq
$sudo apt-get install -y -qq --no-install-recommends openjdk-17-jdk-headless >/dev/null
fi
echo "JAVA_HOME=$j" >> "$GITHUB_ENV"
- name: wai
shell: bash
run: |
set -euo pipefail
(cd wai/wai-rs && cargo build --locked --release --no-default-features --features quickstart --bin wai)
t=$(cd wai/wai-rs && cargo metadata --format-version 1 --no-deps --locked | python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])')
echo "$t/release" >> "$GITHUB_PATH"
- name: android-app
shell: bash
run: |
set -euo pipefail
cache=/ci-cache/wai-sink-android
mkdir -p "$cache" 2>/dev/null || cache="$RUNNER_TEMP/wai-sink-android"
export WAI_SINK_GRADLE_DISTS="$cache/dists" GRADLE_USER_HOME="$cache/gradle-home"
bash wai/quickstart/test/run-examples.sh android