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]>
254 lines
13 KiB
YAML
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
|