cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/hero-release-note
goq / scripts / verify-demo-build.sh
verify-demo-build.sh 9.6K executable
1#!/usr/bin/env bash2set -euo pipefail34script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"5repo_dir="$(cd -- "$script_dir/.." && pwd -P)"6cd "$repo_dir"78if [[ -f "$HOME/.cargo/env" ]]; then9 # shellcheck source=/dev/null10 source "$HOME/.cargo/env"11fi1213# The complete gate is the ordered union of these stages. Running with no14# arguments runs every one of them, so the release and hardware-UAT workflows15# and a local `./scripts/verify-demo-build.sh` keep their exact prior coverage.16# CI runs the same stages as separate parallel jobs purely to cut wall clock;17# scripts/tests/demo-gate-stages.sh proves the union still spends every check.18#19# `quick` deliberately runs first: format, syntax, ShellCheck, and whitespace20# failures used to surface only after the multi-minute cross build had already21# run, which cost a full gate to learn about a missing newline.22readonly ALL_STAGES=(quick cross native gstreamer repo-tests loopback containment)23readonly LOOPBACK_CASES=(1 2 3 4)2425usage() {26 cat >&2 <<'USAGE'27usage: verify-demo-build.sh [--stage STAGE] [--loopback-case NUMBER]2829 --stage STAGE Run a single stage instead of the complete gate.30 One of: quick cross native gstreamer repo-tests31 loopback containment all (default: all).32 --loopback-case NUMBER Run a single loopback proof (1-4) instead of all33 four. Requires --stage loopback.3435With no arguments this runs the complete repository gate.36USAGE37}3839stage=all40loopback_case=all4142while [[ $# -gt 0 ]]; do43 case "$1" in44 --stage)45 [[ $# -ge 2 ]] || {46 printf -- '--stage requires a value\n' >&247 exit 248 }49 stage="$2"50 shift 251 ;;52 --loopback-case)53 [[ $# -ge 2 ]] || {54 printf -- '--loopback-case requires a value\n' >&255 exit 256 }57 loopback_case="$2"58 shift 259 ;;60 -h | --help)61 usage62 exit 063 ;;64 *)65 printf 'unknown argument: %s\n' "$1" >&266 usage67 exit 268 ;;69 esac70done7172stage_is_known=false73for known_stage in "${ALL_STAGES[@]}" all; do74 if [[ "$stage" == "$known_stage" ]]; then75 stage_is_known=true76 break77 fi78done79if [[ "$stage_is_known" != true ]]; then80 printf 'unknown stage: %s\n' "$stage" >&281 usage82 exit 283fi8485if [[ "$loopback_case" != all ]]; then86 if [[ "$stage" != loopback ]]; then87 printf -- '--loopback-case requires --stage loopback\n' >&288 exit 289 fi90 case_is_known=false91 for known_case in "${LOOPBACK_CASES[@]}"; do92 if [[ "$loopback_case" == "$known_case" ]]; then93 case_is_known=true94 break95 fi96 done97 if [[ "$case_is_known" != true ]]; then98 printf -- '--loopback-case must be one of %s\n' "${LOOPBACK_CASES[*]}" >&299 exit 2100 fi101fi102103case "${GOQ_VERIFY_IN_PROCESS_GSTREAMER:-0}" in104 0 | 1) ;;105 *)106 printf 'GOQ_VERIFY_IN_PROCESS_GSTREAMER must be 0 or 1\n' >&2107 exit 1108 ;;109esac110111case "${GOQ_REQUIRE_LINUX_CROSS_BUILD:-0}" in112 0 | 1) ;;113 *)114 printf 'GOQ_REQUIRE_LINUX_CROSS_BUILD must be 0 or 1\n' >&2115 exit 1116 ;;117esac118119require_commands() {120 local command_name121 for command_name in "$@"; do122 if ! command -v "$command_name" >/dev/null 2>&1; then123 printf 'required command is missing: %s\n' "$command_name" >&2124 exit 1125 fi126 done127}128129require_rust_test() {130 local test_name="$1"131 shift132 local test_list133134 test_list="$(cargo test --locked "$@" -- --list)"135 if ! grep -Eq "(^|::)${test_name}: test$" <<<"$test_list"; then136 printf 'required Rust test is not discoverable: %s\n' "$test_name" >&2137 exit 1138 fi139}140141run_stage_quick() {142 require_commands cargo node git shellcheck143 cargo fmt --all -- --check144 while IFS= read -r frontend_source; do145 node --check "$frontend_source"146 done < <(find portal -maxdepth 1 -type f \( -name '*.js' -o -name '*.mjs' \) -print | sort)147 node --test portal/*.test.mjs148 find scripts -type f -name '*.sh' -exec shellcheck {} +149 git diff --check150 echo 'demo_gate_stage_quick=ok'151}152153run_stage_cross() {154 local cross_command155 if [[ "${GOQ_REQUIRE_LINUX_CROSS_BUILD:-0}" == 1 ]]; then156 for cross_command in cargo-zigbuild zig; do157 if ! command -v "$cross_command" >/dev/null 2>&1; then158 printf 'required Linux cross-build command is missing: %s\n' \159 "$cross_command" >&2160 exit 1161 fi162 done163 fi164 ./scripts/run-linux-cross-build-gate.sh165 echo 'demo_gate_stage_cross=ok'166}167168run_stage_native() {169 require_commands cargo rustc git170 require_rust_test 'resolves_pinned_upstream_gamescope_pipewire_contract' \171 -p sigil-host --bin sigil172 cargo test --locked --workspace --all-targets173 cargo clippy --locked --workspace --all-targets -- -D warnings174 cargo check --locked -p portal --all-targets \175 --features experimental-non-macos-pointer-capture176 echo 'experimental_non_macos_pointer_capture_compile=ok'177 cargo build --locked -p sigil-host --bin sigil178179 local host_dependencies180 host_dependencies="$(cargo tree --locked -p sigil-host --edges normal)"181 if grep -Eiq '(^|[[:space:]├└│─])(tauri|wry|webkit)([[:space:]-]|$)' <<<"$host_dependencies"; then182 echo 'pure host dependency gate failed: desktop/webview dependency detected' >&2183 grep -Ei 'tauri|wry|webkit' <<<"$host_dependencies" >&2184 exit 1185 fi186187 local catalog_dependencies188 catalog_dependencies="$({189 cargo tree --locked -p sigil-host --edges normal190 cargo tree --locked -p portal --edges normal191 })"192 if grep -Eiq '(^|[[:space:]├└│─])(moq-media|moq-mux)([[:space:]-]|$)' <<<"$catalog_dependencies"; then193 echo 'MoQ catalog boundary failed: standard-media dependency detected' >&2194 grep -Ei 'moq-media|moq-mux' <<<"$catalog_dependencies" >&2195 exit 1196 fi197 echo 'demo_gate_stage_native=ok'198}199200run_stage_gstreamer() {201 local gstreamer_element202 if [[ "${GOQ_VERIFY_IN_PROCESS_GSTREAMER:-0}" != 1 ]]; then203 echo 'in_process_gstreamer_gate=skipped (GOQ_VERIFY_IN_PROCESS_GSTREAMER is not 1)'204 return 0205 fi206 require_commands cargo gst-inspect-1.0207 for gstreamer_element in videotestsrc queue videoconvert videoscale capsfilter x264enc h264parse appsink; do208 gst-inspect-1.0 "$gstreamer_element" >/dev/null || {209 printf 'in-process GStreamer gate requires the %s plugin\n' \210 "$gstreamer_element" >&2211 exit 1212 }213 done214 local control_test='encoder_control_coalesces_latest_state_and_acknowledges_only_configured_idr'215 local gstreamer_test='in_process_gstreamer_x264_smoke'216 require_rust_test "$control_test" -p sigil-host217 cargo test --locked -p sigil-host "$control_test"218 cargo check --locked -p sigil-host --all-targets --features in-process-gstreamer219 require_rust_test "$gstreamer_test" -p sigil-host --features in-process-gstreamer220 cargo test --locked -p sigil-host --features in-process-gstreamer \221 "$gstreamer_test" -- --ignored --nocapture222 echo 'in_process_gstreamer_gate=ok'223}224225run_stage_repo_tests() {226 require_commands cargo rustc node git ffmpeg shellcheck227 ./scripts/run-shell-tests.sh scripts/tests228 echo 'demo_gate_stage_repo_tests=ok'229}230231run_loopback_case() {232 case "$1" in233 1)234 ./scripts/loopback-proof.sh235 ;;236 2)237 ./scripts/loopback-proof.sh --profile debug --control-v2 --viewers 3 \238 --primary-frames 600 --reconnect-cycles 3239 ;;240 3)241 ./scripts/loopback-proof.sh --profile debug --control-v2 --viewers 3 \242 --focus-handoffs 3 --assert-neutral-before-successor \243 --primary-frames 600 --reconnect-cycles 3244 ;;245 4)246 ./scripts/loopback-proof.sh --profile release --legacy-exclusive --viewers 2 \247 --expect-second-rejected248 ;;249 *)250 printf 'unknown loopback case: %s\n' "$1" >&2251 exit 2252 ;;253 esac254}255256run_stage_loopback() {257 local case_number258 require_commands cargo rustc git ffmpeg259 if [[ "$loopback_case" == all ]]; then260 for case_number in "${LOOPBACK_CASES[@]}"; do261 run_loopback_case "$case_number"262 done263 else264 run_loopback_case "$loopback_case"265 fi266 echo 'demo_gate_stage_loopback=ok'267}268269run_stage_containment() {270 require_commands cargo271 cargo test --locked -p portal --release \272 commands::state::tests::rejects_direct_node_when_debug_mode_is_disabled \273 -- --exact274275 cargo test --locked -p portal --release \276 commands::state::tests::ordinary_release_excludes_direct_node_bypass \277 -- --exact278279 cargo test --locked -p portal --release --features demo-direct-node \280 commands::state::tests::app_state_accepts_direct_node_only_in_debug_builds \281 -- --exact282283 cargo test --locked -p sigil-host --release \284 tests::ordinary_release_excludes_configured_host_auth_bypass \285 -- --exact286287 cargo test --locked -p sigil-host --release --features demo-auth-bypass \288 tests::configured_host_auth_bypass_is_explicitly_build_contained \289 -- --exact290 echo 'demo_gate_stage_containment=ok'291}292293run_stage() {294 case "$1" in295 quick) run_stage_quick ;;296 cross) run_stage_cross ;;297 native) run_stage_native ;;298 gstreamer) run_stage_gstreamer ;;299 repo-tests) run_stage_repo_tests ;;300 loopback) run_stage_loopback ;;301 containment) run_stage_containment ;;302 *)303 printf 'unknown stage: %s\n' "$1" >&2304 exit 2305 ;;306 esac307}308309if [[ "$stage" == all ]]; then310 require_commands cargo rustc node git ffmpeg shellcheck311fi312313printf 'repo=%s\n' "$repo_dir"314printf 'revision=%s\n' "$(git rev-parse HEAD)"315printf 'stage=%s\n' "$stage"316rustc --version317cargo --version318319if [[ "$stage" == all ]]; then320 for current_stage in "${ALL_STAGES[@]}"; do321 run_stage "$current_stage"322 done323 # `quick` already checked this, but re-check after every build so a stage324 # that dirties the worktree still fails the complete gate.325 git diff --check326 echo 'demo_build_preflight=ok'327else328 run_stage "$stage"329fi