wai doctor (toolchain.doctor, also the tool toolchain_doctor of wai mcp) prints one line per check, each pass, FAIL with its fix, not checked with the reason, or n/a. With --json, the envelope carries each check's state and facts, and a result: passed, failed, or passed-with-not-checked. A check that could not be run is never a pass. It exits 1 when a check failed, as the exit-code table says, and 0 otherwise. It looks at this wai's version and path, and at whether wai at a prompt reaches it: each wai earlier on PATH is asked --version --json, and one that does not answer as WAI's does, or that hangs, is a finding. One reached through a relative entry of PATH is named and not run. It finds the relay and the room service as wai dev finds them, and checks that each is of this wai's toolchain. It also finds the room client's files and Studio's. Each row of the quickstart README's port table, compiled in, is checked: free, or held by what the system will name to this account. On Linux that comes from /proc, on macOS from lsof, and on Windows from the system's own socket tables. It finds browsers for Studio and for the harnesses, and starts none. It reports where wai key keeps keys, and whether the directory and each file are this account's alone: by mode and owner on Unix, by SID through win_acl.rs on Windows, with the command that fixes each. A seed is never opened. On macOS it asks the keychain tool for its search list alone, and whether the login keychain is unlocked always reads not checked, since asking can raise a prompt. Nothing is sent and nothing is written. Each program asked has 3 s and is then stopped with its process group. Each group of checks runs on its own thread, and the run ends at 6 s: what has not finished by then reads as not checked. wai-relay and wai-room gain --version [--json], naming their own version and the toolchain they belong to (wai-rs's version in the tree they were built from), which is what wai doctor reads. build.sh runs both before it packs them. doctor joins the closed verb list (catalogue, door check). The catalogue export and Studio's generated actions are regenerated. tests/cli_doctor.rs drives the real binary on a made-up machine: its own PATH, home and temp directory, with wai hard-linked into a bin of its own. It is added to the conformance workflow's quickstart step, and wai/quickstart/README.md to that workflow's paths. Co-Authored-By: Claude Opus 5.5 <[email protected]>
120 lines
7.1 KiB
Shell
Executable file
120 lines
7.1 KiB
Shell
Executable file
#!/usr/bin/env bash
|
|
# The quickstart archive: the `wai` command line and what `wai dev` runs, for one target.
|
|
#
|
|
# bash wai/quickstart/build.sh <out-dir> --web <dir> --studio <dir> [--target <triple>] [--version <v>]
|
|
#
|
|
# Builds, each release and --locked, each from its own workspace:
|
|
# wai wai/wai-rs, --no-default-features --features quickstart: the mandatory
|
|
# floor, hello, create, dev and verify; no system codec library
|
|
# wai-relay the relay (crate joule-moq-relay, joule-code/joule-code-rs)
|
|
# wai-room the room service (crate joule-moq-room)
|
|
# wai-ingest the ingest gateway: WHIP, WHEP, SRT and RTMP into a room (crate joule-moq-ingest)
|
|
# wai-remote-host a remote desktop room's host agent
|
|
# wai-pack the packager
|
|
# wai-quantum the quantum command line (wai/wai-quantum-cli), with the features its own
|
|
# release builds it with (wai-quantum-cli-release.yml): on macOS, metal, the
|
|
# state vector on the GPU, so the program install.sh puts on PATH and the one
|
|
# Homebrew installs (the wai formula depends on wai-quantum's) are one
|
|
# and writes <out-dir>/wai-<version>-<triple>.tar.gz holding bin/ (those seven), share/wai/web/
|
|
# (the room client, from --web: bash wai/quickstart/build-web.sh makes it), share/wai/studio/
|
|
# (WAI Studio, which `wai dev` serves at /_wai/, from --studio: bash
|
|
# wai/quickstart/build-studio.sh makes it), README.md and LICENSE, with <archive>.sha256
|
|
# beside it in the form `shasum -a 256 -c` reads. The version
|
|
# is wai/wai-rs's unless given; the triple is the host's unless given. Extra cargo flags for
|
|
# the target (a linker, static linking) come from the environment, as cargo reads them.
|
|
set -euo pipefail
|
|
usage() { echo "usage: bash wai/quickstart/build.sh <out-dir> --web <dir> --studio <dir> [--target <triple>] [--version <v>]" >&2; exit 2; }
|
|
[ $# -ge 5 ] || usage
|
|
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
|
OUT=$(mkdir -p "$1" && cd "$1" && pwd); shift
|
|
WEBDIR="" STUDIODIR="" TRIPLE="" VERSION=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--web) WEBDIR="$2"; shift 2 ;;
|
|
--studio) STUDIODIR="$2"; shift 2 ;;
|
|
--target) TRIPLE="$2"; shift 2 ;;
|
|
--version) VERSION="$2"; shift 2 ;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
[ -n "$WEBDIR" ] && [ -f "$WEBDIR/room/index.html" ] && [ -f "$WEBDIR/pkg/wai_live_web_bg.wasm" ] && [ -f "$WEBDIR/pkg/joule_moq_web_bg.wasm" ] \
|
|
|| { echo "--web: a directory build-web.sh made (room/, moq/, pkg/)" >&2; exit 2; }
|
|
[ -n "$STUDIODIR" ] && [ -f "$STUDIODIR/index.html" ] && [ -f "$STUDIODIR/pkg/wai_studio_core_bg.wasm" ] && [ -f "$STUDIODIR/BUILD.json" ] \
|
|
|| { echo "--studio: a directory build-studio.sh made (index.html, pkg/, quantum/, BUILD.json)" >&2; exit 2; }
|
|
HOST=$(rustc -vV | sed -n 's/^host: //p')
|
|
TRIPLE=${TRIPLE:-$HOST}
|
|
VERSION=${VERSION:-$(sed -n 's/^version = "\(.*\)"$/\1/p' "$ROOT/wai/wai-rs/Cargo.toml" | head -1)}
|
|
EXE=""; case "$TRIPLE" in *windows*) EXE=".exe" ;; esac
|
|
QUANTUM=(); case "$TRIPLE" in *apple-darwin) QUANTUM=(--features metal) ;; esac
|
|
TGT=(--target "$TRIPLE")
|
|
|
|
build() { # <dir> <binary> <cargo args...>: the binary's path, or a failure
|
|
local dir="$1" bin="$2"; shift 2
|
|
(
|
|
# Under a target directory shared by every workspace (CARGO_TARGET_DIR), each build gets its
|
|
# own: wai also builds as a cdylib, so its rlib's file name carries no hash, and a second
|
|
# feature set of it (the room service's) would overwrite the first in one directory.
|
|
if [ -n "${CARGO_TARGET_DIR:-}" ]; then export CARGO_TARGET_DIR="$CARGO_TARGET_DIR/quickstart-$bin"; fi
|
|
cd "$dir"
|
|
cargo build --locked --release "${TGT[@]}" "$@" >&2 || exit 1
|
|
t=$(cargo metadata --format-version 1 --no-deps --locked | python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])') || exit 1
|
|
echo "$t/$TRIPLE/release/$bin$EXE"
|
|
)
|
|
}
|
|
need() { # <binary> <cargo args...>: copied into the stage, or the script stops
|
|
local b
|
|
b=$(build "$@") || { echo "build.sh: the build of $2 failed" >&2; exit 1; }
|
|
cp "$b" "$STAGE/$NAME/bin/"
|
|
}
|
|
|
|
NAME="wai-$VERSION-$TRIPLE"
|
|
STAGE=$(mktemp -d)
|
|
trap 'rm -rf "$STAGE"' EXIT
|
|
mkdir -p "$STAGE/$NAME/bin" "$STAGE/$NAME/share/wai"
|
|
need "$ROOT/wai/wai-rs" wai --no-default-features --features quickstart --bin wai
|
|
need "$ROOT/joule-code/joule-code-rs" wai-relay -p joule-moq-relay --bin wai-relay
|
|
need "$ROOT/joule-code/joule-code-rs/crates/joule-moq-room" wai-room --bin wai-room
|
|
need "$ROOT/joule-code/joule-code-rs" wai-ingest -p joule-moq-ingest --bin wai-ingest
|
|
need "$ROOT/joule-code/joule-code-rs/crates/joule-moq-remote" wai-remote-host --bin wai-remote-host
|
|
need "$ROOT/wai/wai-pack" wai-pack --bin wai-pack
|
|
need "$ROOT/wai/wai-quantum-cli" wai-quantum --bin wai-quantum ${QUANTUM[@]+"${QUANTUM[@]}"}
|
|
cp -R "$WEBDIR" "$STAGE/$NAME/share/wai/web"
|
|
cp -R "$STUDIODIR" "$STAGE/$NAME/share/wai/studio"
|
|
cp "$ROOT/wai/quickstart/README.md" "$STAGE/$NAME/README.md"
|
|
cp "$ROOT/wai/LICENSE" "$STAGE/$NAME/LICENSE"
|
|
chmod 755 "$STAGE/$NAME"/bin/*
|
|
# Each binary runs before it is packaged, where it can run here.
|
|
# The wai packaged must hold this checkout's examples, file for file and byte for byte: a stale
|
|
# binary would ship templates from another commit.
|
|
if [ "$TRIPLE" = "$HOST" ]; then
|
|
"$STAGE/$NAME/bin/wai-pack$EXE" fixture list > /dev/null
|
|
"$STAGE/$NAME/bin/wai-quantum$EXE" --version > /dev/null
|
|
# wai doctor asks these two which toolchain they belong to.
|
|
"$STAGE/$NAME/bin/wai-relay$EXE" --version > /dev/null
|
|
"$STAGE/$NAME/bin/wai-room$EXE" --version > /dev/null
|
|
# wai-ingest without a configuration says how it is run, and exits with a usage error.
|
|
set +e; "$STAGE/$NAME/bin/wai-ingest$EXE" > /dev/null 2> "$STAGE/ingest.err"; s=$?; set -e
|
|
[ "$s" = 2 ] && grep -q "usage: wai-ingest" "$STAGE/ingest.err" || { echo "build.sh: the packaged wai-ingest does not run" >&2; exit 1; }
|
|
rm -f "$STAGE/ingest.err"
|
|
python3 - "$STAGE/$NAME/bin/wai$EXE" "$ROOT/wai/examples" <<'PY' || { echo "build.sh: the packaged wai does not hold this checkout's examples" >&2; exit 1; }
|
|
import json, os, subprocess, sys, tempfile
|
|
wai, ex = sys.argv[1], sys.argv[2]
|
|
with tempfile.TemporaryDirectory() as d:
|
|
for t in json.loads(subprocess.check_output([wai, "create", "--list", "--json"]))["data"]:
|
|
out = os.path.join(d, t["template"])
|
|
subprocess.check_output([wai, "create", out, "--template", t["template"]])
|
|
for f in t["files"]:
|
|
src = os.path.join(ex, t["template"], f)
|
|
if os.path.exists(src) and open(src, "rb").read() != open(os.path.join(out, f), "rb").read():
|
|
sys.exit(f"{t['template']}/{f} differs")
|
|
PY
|
|
fi
|
|
mkdir -p "$OUT"
|
|
# The same bytes for the same inputs: sorted names, fixed owner and times.
|
|
if tar --version 2>/dev/null | grep -q GNU; then
|
|
tar --sort=name --owner=0 --group=0 --numeric-owner --mtime='2026-01-01 00:00Z' -C "$STAGE" -cf - "$NAME" | gzip -n -9 > "$OUT/$NAME.tar.gz"
|
|
else
|
|
tar -C "$STAGE" -cf - "$NAME" | gzip -n -9 > "$OUT/$NAME.tar.gz"
|
|
fi
|
|
( cd "$OUT" && { command -v sha256sum >/dev/null && sha256sum "$NAME.tar.gz" || shasum -a 256 "$NAME.tar.gz"; } > "$NAME.tar.gz.sha256" )
|
|
echo "packaged $OUT/$NAME.tar.gz ($(cut -d' ' -f1 "$OUT/$NAME.tar.gz.sha256"), $(du -h "$OUT/$NAME.tar.gz" | cut -f1))"
|