-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdev
More file actions
executable file
·307 lines (282 loc) · 12.8 KB
/
Copy pathdev
File metadata and controls
executable file
·307 lines (282 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# ./dev — the DeMoD developer CLI. One command for the whole loop:
# ./dev build [dcf] build demod-ui (dcf = with dm.dcf)
# ./dev run <target> [args] run auto|dash|gcs|rov|mcp or an examples/ name
# ./dev shot <target> [frame] [out.png] headless screenshot -> a PNG
# ./dev test [name|all] font|loopback|ws_loopback|engine_e2e|obd2|smoke
# ./dev check everything CI runs + obd2 (the pre-push gate)
# ./dev fmt | lint stylua + clang-format (advisory)
# Zero deps; auto-enters `nix develop` when the toolchain isn't on PATH.
set -uo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$REPO"
usage() {
cat <<'H'
./dev — the DeMoD developer CLI
build [dcf] build ./demod-ui (dcf adds the dm.dcf transport)
run <target> [args] run auto|dash|gcs|rov|mcp or an examples/ name
shot <target> [frame] [out] headless render -> a PNG (DEMOD_SURFACE=n for shells)
test <name|all> font|loopback|ws_loopback|engine_e2e|obd2|smoke
check [--fast] build + all tests (pre-push gate; --fast skips engine_e2e)
fmt | lint [--all] stylua + clang-format (advisory; default = your changed files)
compiledb write compile_commands.json (for clangd)
doctor report toolchain / tools / versions
watch <target> [shot] rebuild + re-run (or re-shot) on file change
Examples:
./dev run gcs ./dev shot auto 60 DEMOD_SURFACE=3 ./dev shot auto
H
}
# help needs no toolchain
case "${1:-help}" in help | -h | --help) usage; exit 0 ;; esac
# ── auto-enter nix develop if the C toolchain isn't available ─────────────────
have_toolchain() {
command -v gcc >/dev/null 2>&1 && pkg-config --exists sdl2 2>/dev/null \
&& { pkg-config --exists lua5.4 2>/dev/null || pkg-config --exists lua 2>/dev/null; }
}
if [ "${DEV_IN_NIX:-}" != 1 ] && ! have_toolchain; then
if command -v nix >/dev/null 2>&1 && [ -f flake.nix ]; then
exec env DEV_IN_NIX=1 nix develop "$REPO" --command "$REPO/dev" "$@"
fi
echo "dev: no toolchain — install SDL2 + Lua 5.4 + a C compiler, or nix. See DEVELOPING.md." >&2
exit 1
fi
say() { printf '\033[36m[dev]\033[0m %s\n' "$*"; }
die() { printf '\033[31m[dev] %s\033[0m\n' "$*" >&2; exit 1; }
LUA_DIRS=(shell auto dash gcs rov examples)
build() { # build [dcf]
if [ "${1:-}" = dcf ]; then say "make DCF=1"; make DCF=1; else say "make"; make; fi
}
resolve_script() { # -> the .lua path for a target
local t="$1"
case "$t" in
auto | dash | gcs | rov) echo "$REPO/$t/main.lua" ;;
*)
if [ -f "$t" ]; then echo "$t"
elif [ -f "$REPO/examples/$t.lua" ]; then echo "$REPO/examples/$t.lua"
elif [ -f "$REPO/examples/$t" ]; then echo "$REPO/examples/$t"
else die "unknown target '$t' (auto|dash|gcs|rov|mcp or an examples/ name)"; fi ;;
esac
}
app_env() { # export the env a shell target needs
export DEMOD_SHELL_DIR="$REPO/shell/"
case "$1" in
auto) export DEMOD_AUTO_DIR="$REPO/auto/" ;;
dash) export DEMOD_DASH_DIR="$REPO/dash/" ;;
gcs) export DEMOD_GCS_DIR="$REPO/gcs/" ;;
rov) export DEMOD_ROV_DIR="$REPO/rov/" ;;
esac
}
cmd_run() {
local t="${1:-}"; shift || true
[ -n "$t" ] || die "usage: dev run <auto|dash|gcs|rov|mcp|example> [args]"
if [ "$t" = mcp ]; then exec python3 "$REPO/mcp/demod_mcp_server.py" "$@"; fi
build dcf >/dev/null || die "build failed"
local s; s="$(resolve_script "$t")"
case "$t" in auto | dash | gcs | rov) app_env "$t" ;; esac
say "run $s"
exec "$REPO/demod-ui" "$s" "$@"
}
ppm2png() { # <ppm> <png>
python3 - "$1" "$2" <<'PY'
import sys, struct, zlib
def read_ppm(d):
assert d[:2] == b"P6"; i, t = 2, []
while len(t) < 3:
while d[i:i+1].isspace(): i += 1
if d[i:i+1] == b"#":
while d[i:i+1] != b"\n": i += 1
continue
j = i
while not d[j:j+1].isspace(): j += 1
t.append(d[i:j]); i = j
w, h = int(t[0]), int(t[1]); i += 1
return w, h, d[i:i+w*h*3]
def chunk(tag, data):
return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag+data) & 0xffffffff)
w, h, rgb = read_ppm(open(sys.argv[1], "rb").read())
raw = bytearray()
for y in range(h): raw.append(0); raw += rgb[y*w*3:(y+1)*w*3]
png = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b""))
open(sys.argv[2], "wb").write(png)
PY
}
cmd_shot() {
local t="${1:-}" frame="${2:-60}" out="${3:-}"
[ -n "$t" ] || die "usage: dev shot <target> [frame] [out.png] (DEMOD_SURFACE=n for shells)"
[ -n "$out" ] || out="$(basename "${t%.lua}").png"
build dcf >/dev/null || die "build failed"
local s; s="$(resolve_script "$t")"
case "$t" in auto | dash | gcs | rov) app_env "$t" ;; esac
local ppm; ppm="$(mktemp)"
say "render $s (frame $frame)"
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy DEMOD_SHOT="$ppm" DEMOD_SHOT_FRAME="$frame" \
timeout 25 "$REPO/demod-ui" "$s" >/dev/null 2>&1 || true
[ -s "$ppm" ] || { rm -f "$ppm"; die "no frame produced (does $t render?)"; }
ppm2png "$ppm" "$out"; rm -f "$ppm"
say "wrote $out"
}
smoke_examples() {
build dcf >/dev/null || return 1
local ok=0 e
for e in hello dsp_panel dsp_studio systems_viz card_launcher; do
if SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy DEMOD_SHOT="$(mktemp)" DEMOD_SHOT_FRAME=40 \
timeout 20 "$REPO/demod-ui" "examples/$e.lua" >/dev/null 2>&1; then
echo " smoke $e: ok"
else echo " smoke $e: FAIL"; ok=1; fi
done
return $ok
}
# AR passthrough HUD: build with ARHUD=1, feed a synthetic RGBA frame, and
# confirm the compositor produces a non-empty frame headlessly (no camera).
# Exercises both the mono path and the stereo (SBS + lens warp + pose) path.
ar_test() {
make ARHUD=1 >/dev/null 2>&1 || return 1
local frame pose ppm rc; frame="$(mktemp)"; pose="$(mktemp)"; ppm="$(mktemp)"; rc=0
tools/ar_testframe.sh "$frame" >/dev/null 2>&1 || { rm -f "$frame" "$pose" "$ppm"; return 1; }
printf '\x00\x00\xa0\x3f\x00\x00\x00\xbf\x00\x00\x00\x40\x00\x00\x00\x00\xcd\xcc\xcc\x3d\x00\x00\x00\x00\xa4\x70\x7d\x3f' > "$pose"
# mono
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy DEMOD_AR_FRAME="$frame" \
DEMOD_SHOT="$ppm" DEMOD_SHOT_FRAME=40 DEMOD_SHOT_QUIT=1 \
timeout 20 "$REPO/demod-ui" examples/ar_hud.lua >/dev/null 2>&1 || rc=1
[ -s "$ppm" ] || rc=1
# stereo SBS + barrel warp + head-tracking pose
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy DEMOD_AR_FRAME="$frame" \
DEMOD_AR_EYES=2 DEMOD_AR_K1=0.22 DEMOD_AR_K2=0.24 DEMOD_AR_POSE="$pose" \
DEMOD_SHOT="$ppm" DEMOD_SHOT_FRAME=40 DEMOD_SHOT_QUIT=1 \
timeout 20 "$REPO/demod-ui" examples/ar_hud.lua >/dev/null 2>&1 || rc=1
[ -s "$ppm" ] || rc=1
rm -f "$frame" "$pose" "$ppm"
return $rc
}
# OpenXR present sink: the live path can't run without a headset, but the seam +
# stub (src/ar/xr_sink.c compiled without the SDK) must keep building and must
# fall back to the SDL present. Guards the DEMOD_XR-gated app.c seam from bit-rot.
xr_test() {
make XR=1 >/dev/null 2>&1 || return 1
local ppm rc; ppm="$(mktemp)"; rc=0
DEMOD_XR=1 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
DEMOD_SHOT="$ppm" DEMOD_SHOT_FRAME=2 DEMOD_SHOT_QUIT=1 \
timeout 20 "$REPO/demod-ui" examples/hello.lua >/dev/null 2>&1 || rc=1
[ -s "$ppm" ] || rc=1 # stub returns NULL → SDL fallback still produces a frame
rm -f "$ppm"
return $rc
}
run_one_test() {
case "$1" in
font) make test ;;
loopback) bash audio-stack/bridge/test/loopback.sh ;;
ws_loopback) bash audio-stack/bridge/test/ws_loopback.sh ;;
engine_e2e) bash audio-stack/bridge/test/engine_e2e.sh ;;
obd2) bash auto/test/obd2_selftest.sh ;;
smoke) smoke_examples ;;
ar) ar_test ;;
xr) xr_test ;;
*) die "unknown test '$1' (font|loopback|ws_loopback|engine_e2e|obd2|smoke|ar|xr|all)" ;;
esac
}
cmd_test() {
local n="${1:-all}"
if [ "$n" = all ]; then
for t in font smoke ar xr obd2 loopback ws_loopback engine_e2e; do
say "test $t"; run_one_test "$t" || die "test '$t' failed"
done
say "all tests passed"
else run_one_test "$n"; fi
}
# files changed vs HEAD (working tree + staged + untracked) — the actionable set
changed() {
{ git diff --name-only HEAD; git diff --name-only --cached; git ls-files -o --exclude-standard; } \
2>/dev/null | sort -u
}
# populate arrays LUA[] and C[] for fmt/lint, honoring --all
_targets() {
LUA=(); C=()
if [ "${1:-}" = --all ]; then
LUA=("${LUA_DIRS[@]}")
mapfile -t C < <(find src include \( -name '*.c' -o -name '*.h' \) 2>/dev/null)
else
mapfile -t LUA < <(changed | grep -E '\.lua$' || true)
mapfile -t C < <(changed | grep -E '\.(c|h)$' || true)
fi
}
cmd_fmt() {
local LUA C; _targets "${1:-}"
if command -v stylua >/dev/null 2>&1 && [ ${#LUA[@]} -gt 0 ]; then say "stylua (${#LUA[@]})"; stylua "${LUA[@]}"; fi
if command -v clang-format >/dev/null 2>&1 && [ ${#C[@]} -gt 0 ]; then say "clang-format (${#C[@]})"; clang-format -i "${C[@]}"; fi
[ ${#LUA[@]} -eq 0 ] && [ ${#C[@]} -eq 0 ] && say "nothing changed to format (--all for the whole tree)"
return 0
}
cmd_lint() { # advisory: reports, nonzero on style diffs
local LUA C r=0; _targets "${1:-}"
if command -v stylua >/dev/null 2>&1 && [ ${#LUA[@]} -gt 0 ]; then stylua --check "${LUA[@]}" || r=1; fi
if command -v clang-format >/dev/null 2>&1 && [ ${#C[@]} -gt 0 ]; then
clang-format --dry-run --Werror "${C[@]}" 2>&1 | head -20; [ "${PIPESTATUS[0]}" = 0 ] || r=1
fi
return $r
}
cmd_doctor() {
say "toolchain & tools"
local t
for t in gcc make pkg-config python3 nix stylua clang-format bear entr lua-language-server; do
if command -v "$t" >/dev/null 2>&1; then printf ' %-22s %s\n' "$t" "$(command -v "$t")"
else printf ' %-22s \033[33mmissing\033[0m\n' "$t"; fi
done
printf ' %-22s %s\n' "SDL2" "$(pkg-config --modversion sdl2 2>/dev/null || echo '?')"
printf ' %-22s %s\n' "Lua" "$(pkg-config --modversion lua5.4 2>/dev/null || pkg-config --modversion lua 2>/dev/null || echo '?')"
printf ' %-22s %s\n' "font blob" "$([ -f "$HOME/.local/share/demod/unifont.dmf" ] && echo installed || echo 'not installed (make font — optional)')"
printf ' %-22s %s\n' "demod-ui" "$([ -x ./demod-ui ] && echo built || echo 'not built (./dev build)')"
printf ' %-22s %s\n' "compile_commands" "$([ -f compile_commands.json ] && echo present || echo 'run ./dev compiledb (clangd)')"
}
cmd_compiledb() {
command -v bear >/dev/null 2>&1 || die "bear not found (it's in the dev shell)"
say "bear -- make clean + DCF=1"
make clean >/dev/null 2>&1
bear -- make DCF=1 >/dev/null || die "compiledb build failed"
say "wrote compile_commands.json — clangd is ready"
}
cmd_watch() {
command -v entr >/dev/null 2>&1 || die "entr not found (it's in the dev shell)"
local t="${1:-}" mode="${2:-run}"
[ -n "$t" ] || die "usage: dev watch <target> [run|shot]"
say "watch: re-$mode $t on change (Ctrl-C to stop)"
find shell auto dash gcs rov examples src include -type f 2>/dev/null | entr -rc "$REPO/dev" "$mode" "$t"
}
cmd_check() {
local fast=0; [ "${1:-}" = --fast ] && fast=1
local fail=0 log t0 ts; log="$(mktemp)"; t0=$SECONDS
chk() { local name="$1"; shift; printf ' %-34s' "$name"; ts=$SECONDS
if "$@" >"$log" 2>&1; then printf '\033[32mPASS\033[0m %3ds\n' $((SECONDS - ts))
else printf '\033[31mFAIL\033[0m %3ds\n' $((SECONDS - ts)); tail -6 "$log" | sed 's/^/ /'; fail=1; fi; }
say "check — CI parity + obd2$([ $fast = 1 ] && echo ' (fast)')"
chk "build (default/MPL)" bash -c 'make clean >/dev/null 2>&1; make >/dev/null'
chk "build (DCF=1)" bash -c 'make DCF=1 >/dev/null'
chk "font/decode test" make test
chk "example smoke (headless)" smoke_examples
chk "ar passthrough (ARHUD=1)" ar_test
chk "xr sink seam (XR=1 stub)" xr_test
chk "obd2 reader" bash auto/test/obd2_selftest.sh
chk "dcf loopback (UDP)" bash audio-stack/bridge/test/loopback.sh
chk "dcf loopback (WebSocket)" bash audio-stack/bridge/test/ws_loopback.sh
[ $fast = 1 ] || chk "real-engine e2e (skips w/o JACK)" bash audio-stack/bridge/test/engine_e2e.sh
printf ' %-34s' "style (advisory)"
if cmd_lint --all >/dev/null 2>&1; then printf 'clean\n'; else printf '\033[33mdiffs — ./dev fmt\033[0m\n'; fi
rm -f "$log"
say "$((SECONDS - t0))s total"
[ "$fail" = 0 ] && say "CHECK PASSED" || die "CHECK FAILED"
}
case "${1:-help}" in
build) shift; build "${1:-}" ;;
run) shift; cmd_run "$@" ;;
shot) shift; cmd_shot "$@" ;;
test) shift; cmd_test "$@" ;;
check) shift; cmd_check "$@" ;;
fmt) shift; cmd_fmt "$@" ;;
lint) shift; cmd_lint "$@" ;;
compiledb) cmd_compiledb ;;
doctor) cmd_doctor ;;
watch) shift; cmd_watch "$@" ;;
help | -h | --help) usage ;;
*) usage; exit 1 ;;
esac