#!/usr/bin/env bash # The quickstart archive: the `wai` command line and what `wai dev` runs, for one target. # # bash wai/quickstart/build.sh --web --studio [--target ] [--version ] # # 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 /wai--.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 .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 --web --studio [--target ] [--version ]" >&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() { # : 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() { # : 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-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))"