chore: 🤖 add build monitor
This commit is contained in:
parent
9ab7158057
commit
c7a71ff2a7
6 changed files with 494 additions and 8 deletions
|
|
@ -20,13 +20,30 @@
|
||||||
#
|
#
|
||||||
# Binaryen is downloaded automatically - no prerequisites needed.
|
# Binaryen is downloaded automatically - no prerequisites needed.
|
||||||
|
|
||||||
|
# Auto-launch the live progress dashboard in this terminal (handled by logging.sh,
|
||||||
|
# which owns the TTY before it re-execs us with output redirected). Set KICAD_NO_MONITOR=1
|
||||||
|
# to disable. MUST be set before sourcing logging.sh — that's where the dashboard is
|
||||||
|
# launched, in the pre-re-exec process.
|
||||||
|
export KICAD_MONITOR=1
|
||||||
|
|
||||||
# Redirect all output to a log file (re-execs script with redirection).
|
# Redirect all output to a log file (re-execs script with redirection).
|
||||||
# MUST be sourced before arg parsing — the re-exec relies on the original
|
# MUST be sourced before arg parsing — the re-exec relies on the original
|
||||||
# "$@", so any shifts before this point would strip args from the re-exec.
|
# "$@", so any shifts before this point would strip args from the re-exec.
|
||||||
source "$(dirname "$0")/../scripts/common/logging.sh"
|
source "$(dirname "$0")/../scripts/common/logging.sh"
|
||||||
|
|
||||||
|
# Build-progress markers (parsed by scripts/build-monitor.sh).
|
||||||
|
source "$(dirname "$0")/../scripts/common/stages.sh"
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Emit a completion/failure marker no matter how the build ends, so the monitor
|
||||||
|
# can stop on a clean "done" or show an aborted state instead of hanging.
|
||||||
|
trap '_rc=$?; if [ $_rc -eq 0 ]; then kw_done; else kw_fail $_rc; fi' EXIT
|
||||||
|
# On Ctrl-C, mark the build aborted so the final dashboard frame shows failed
|
||||||
|
# (not a stale "running" state). The EXIT trap above also fires; the monitor reads
|
||||||
|
# the last marker, so the duplicate is harmless.
|
||||||
|
trap 'kw_fail 130; exit 130' INT TERM
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
VALID_APPS="pcbnew | eeschema | calculator | pl_editor | all"
|
VALID_APPS="pcbnew | eeschema | calculator | pl_editor | all"
|
||||||
|
|
@ -79,6 +96,7 @@ docker compose -f docker/docker-compose.yml up -d
|
||||||
# Use --checksum to only transfer files with different CONTENT, not timestamps.
|
# Use --checksum to only transfer files with different CONTENT, not timestamps.
|
||||||
# This avoids the timestamp mismatch cycle that caused full rebuilds every time.
|
# This avoids the timestamp mismatch cycle that caused full rebuilds every time.
|
||||||
# Transferred files get current container time, so make detects them correctly.
|
# Transferred files get current container time, so make detects them correctly.
|
||||||
|
kw_stage container-sync
|
||||||
echo "Syncing source code to container..."
|
echo "Syncing source code to container..."
|
||||||
# rsync into the macOS-backed volume intermittently hits transient VirtioFS glitches:
|
# rsync into the macOS-backed volume intermittently hits transient VirtioFS glitches:
|
||||||
# temp-file rename failures (exit 23) or vanished-source files (exit 24, harmless).
|
# temp-file rename failures (exit 23) or vanished-source files (exit 24, harmless).
|
||||||
|
|
@ -121,12 +139,16 @@ kicad_subdir_for() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build one app: compile in container, then run host-side post-processing.
|
# Build one app: compile in container, then run host-side post-processing.
|
||||||
|
# Args: <app> [index] [total] — index/total drive the monitor's app counter.
|
||||||
build_app() {
|
build_app() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
|
local index="${2:-1}"
|
||||||
|
local total="${3:-1}"
|
||||||
local subdir
|
local subdir
|
||||||
subdir=$(kicad_subdir_for "$app")
|
subdir=$(kicad_subdir_for "$app")
|
||||||
|
kw_app "$app" "$index" "$total"
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Building ${app} ==="
|
echo "=== Building ${app} (${index}/${total}) ==="
|
||||||
|
|
||||||
# Run build inside the container.
|
# Run build inside the container.
|
||||||
# -e EMSDK=/emsdk: `docker compose exec` bypasses the entrypoint that sources
|
# -e EMSDK=/emsdk: `docker compose exec` bypasses the entrypoint that sources
|
||||||
|
|
@ -137,6 +159,7 @@ build_app() {
|
||||||
|
|
||||||
# Copy output to host-accessible directory.
|
# Copy output to host-accessible directory.
|
||||||
# ${app}.wasm.debug.wasm contains DWARF debug info (when built with -gseparate-dwarf).
|
# ${app}.wasm.debug.wasm contains DWARF debug info (when built with -gseparate-dwarf).
|
||||||
|
kw_stage copy-output
|
||||||
echo "Copying ${app} build output to ./output/..."
|
echo "Copying ${app} build output to ./output/..."
|
||||||
docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
|
docker compose -f docker/docker-compose.yml exec kicad-wasm-builder \
|
||||||
bash -c "mkdir -p /workspace/output && \
|
bash -c "mkdir -p /workspace/output && \
|
||||||
|
|
@ -146,22 +169,25 @@ build_app() {
|
||||||
cp /workspace/build-wasm/wxwidgets/build/wasm/wx.js /workspace/output/ 2>/dev/null || true"
|
cp /workspace/build-wasm/wxwidgets/build/wasm/wx.js /workspace/output/ 2>/dev/null || true"
|
||||||
|
|
||||||
# Inject dynCall shims (fixes "dynCall_* is not defined" errors in Emscripten 4.x)
|
# Inject dynCall shims (fixes "dynCall_* is not defined" errors in Emscripten 4.x)
|
||||||
|
kw_stage dyncall-shims
|
||||||
./scripts/common/inject-dyncall-shims.sh "output/${app}.js"
|
./scripts/common/inject-dyncall-shims.sh "output/${app}.js"
|
||||||
|
|
||||||
# Apply wasm-emscripten-finalize on host (skipped in Docker due to memory limits)
|
# Apply wasm-emscripten-finalize on host (skipped in Docker due to memory limits)
|
||||||
|
kw_stage finalize
|
||||||
./scripts/common/apply-finalize.sh "output/${app}.wasm" "output/${app}.wasm"
|
./scripts/common/apply-finalize.sh "output/${app}.wasm" "output/${app}.wasm"
|
||||||
|
|
||||||
# Apply asyncify transformation on host
|
# Apply asyncify transformation on host
|
||||||
|
kw_stage asyncify
|
||||||
./scripts/common/apply-asyncify.sh "output/${app}.wasm" "output/${app}.wasm"
|
./scripts/common/apply-asyncify.sh "output/${app}.wasm" "output/${app}.wasm"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${APP_NAME}" == "all" ]]; then
|
if [[ "${APP_NAME}" == "all" ]]; then
|
||||||
build_app pcbnew
|
build_app pcbnew 1 4
|
||||||
build_app eeschema
|
build_app eeschema 2 4
|
||||||
build_app calculator
|
build_app calculator 3 4
|
||||||
build_app pl_editor
|
build_app pl_editor 4 4
|
||||||
else
|
else
|
||||||
build_app "${APP_NAME}"
|
build_app "${APP_NAME}" 1 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
|
||||||
393
scripts/build-monitor.sh
Executable file
393
scripts/build-monitor.sh
Executable file
|
|
@ -0,0 +1,393 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Live terminal dashboard for KiCad WASM builds.
|
||||||
|
#
|
||||||
|
# Builds redirect all output to logs/<script>/<timestamp>.log (see
|
||||||
|
# scripts/common/logging.sh). This monitor tails the newest log and renders a
|
||||||
|
# single, self-refreshing screen of named stages + progress, so you never have to
|
||||||
|
# open the log to see "where are we, X of N".
|
||||||
|
#
|
||||||
|
# It reads the @KW@ progress markers emitted by scripts/common/stages.sh, and the
|
||||||
|
# CMake "[ N%]" lines for within-compile progress. If a log has no @KW@ markers
|
||||||
|
# (old logs, or a build started before this tooling existed), it falls back to
|
||||||
|
# inferring stages from the existing "=== ... ===" / "[INFO] ..." log lines.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/build-monitor.sh # follow newest logs/build/*.log
|
||||||
|
# ./scripts/build-monitor.sh --dir build-calculator # watch a different subdir
|
||||||
|
# ./scripts/build-monitor.sh --once [logfile] # one-shot snapshot (non-TTY ok)
|
||||||
|
# ./scripts/build-monitor.sh <logfile> # follow a specific log file
|
||||||
|
#
|
||||||
|
# --managed: don't self-exit on the done/failed marker; run until killed. Used when
|
||||||
|
# a parent process (logging.sh's auto-launch) owns the monitor's lifecycle.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
LOGS_ROOT="${PROJECT_ROOT}/logs"
|
||||||
|
|
||||||
|
ONCE=0
|
||||||
|
MANAGED=0
|
||||||
|
DIR="build"
|
||||||
|
LOGFILE=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--once) ONCE=1; shift ;;
|
||||||
|
--managed) MANAGED=1; shift ;;
|
||||||
|
--dir) DIR="$2"; shift 2 ;;
|
||||||
|
--dir=*) DIR="${1#--dir=}"; shift ;;
|
||||||
|
-h|--help)
|
||||||
|
grep '^#' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//; 1d'
|
||||||
|
exit 0 ;;
|
||||||
|
*) LOGFILE="$1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Newest *.log under logs/<dir>, or empty string if none yet.
|
||||||
|
find_newest_log() {
|
||||||
|
ls -t "${LOGS_ROOT}/${DIR}"/*.log 2>/dev/null | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Colors + cursor/erase escapes (only when stdout is a terminal). Captured once so
|
||||||
|
# the redraw doesn't fork tput per line. C_HOME/C_EL/C_ED drive the flicker-free
|
||||||
|
# in-place repaint (move home + erase each line + erase to end of screen) instead of
|
||||||
|
# blanking the screen with `clear`, which would flash.
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
C_RESET=$(tput sgr0); C_BOLD=$(tput bold); C_DIM=$(tput dim)
|
||||||
|
C_GREEN=$(tput setaf 2); C_CYAN=$(tput setaf 6); C_RED=$(tput setaf 1)
|
||||||
|
C_YELLOW=$(tput setaf 3)
|
||||||
|
C_HOME=$(tput cup 0 0); C_EL=$(tput el); C_ED=$(tput ed)
|
||||||
|
else
|
||||||
|
C_RESET=""; C_BOLD=""; C_DIM=""; C_GREEN=""; C_CYAN=""; C_RED=""; C_YELLOW=""
|
||||||
|
C_HOME=""; C_EL=""; C_ED=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
SPINNER='|/-\'
|
||||||
|
|
||||||
|
# All parsing + state computation lives in awk: portable across macOS bash 3.2 /
|
||||||
|
# BSD awk, and avoids associative-array bashisms. It reads the log and emits simple
|
||||||
|
# pipe-delimited records that the bash render() function formats.
|
||||||
|
#
|
||||||
|
# Records:
|
||||||
|
# H|<app>|<index>|<total>|<elapsedSec>|<state> state = running|done|failed
|
||||||
|
# R|<status>|<label>|<detail> status = done|active|pending|skipped|failed
|
||||||
|
# B|<pct> active-compile bar pct, or -1
|
||||||
|
# N|<note> optional note line
|
||||||
|
read -r -d '' AWK_PROG <<'AWK' || true
|
||||||
|
function fmt(sec, m,s) {
|
||||||
|
if (sec < 0) sec = 0
|
||||||
|
m = int(sec / 60); s = sec % 60
|
||||||
|
if (m > 0) return m "m" s "s"
|
||||||
|
return s "s"
|
||||||
|
}
|
||||||
|
# Start ts of the next stage row that actually ran after index i (else terminal ts).
|
||||||
|
function next_seen_start(i, j) {
|
||||||
|
for (j = i + 1; j <= ROWN; j++)
|
||||||
|
if (ord[j] in rowSeen) return rowStartTs[ord[j]]
|
||||||
|
return termTs
|
||||||
|
}
|
||||||
|
BEGIN {
|
||||||
|
# Canonical per-app stage rows, in order. Sub-stages (wxwidgets-configure /
|
||||||
|
# -compile) fold into the wxwidgets row via parent[].
|
||||||
|
ROWN = 0
|
||||||
|
ord[++ROWN] = "deps"; lab["deps"] = "Dependencies"
|
||||||
|
ord[++ROWN] = "wxwidgets"; lab["wxwidgets"] = "wxWidgets"
|
||||||
|
ord[++ROWN] = "kicad-stubs"; lab["kicad-stubs"] = "Stub libraries"
|
||||||
|
ord[++ROWN] = "kicad-configure"; lab["kicad-configure"] = "KiCad configure (CMake)"
|
||||||
|
ord[++ROWN] = "kicad-compile"; lab["kicad-compile"] = "KiCad compile"
|
||||||
|
ord[++ROWN] = "kicad-bitmaps"; lab["kicad-bitmaps"] = "Bitmap resources"
|
||||||
|
ord[++ROWN] = "copy-output"; lab["copy-output"] = "Copy output"
|
||||||
|
ord[++ROWN] = "dyncall-shims"; lab["dyncall-shims"] = "dynCall shims"
|
||||||
|
ord[++ROWN] = "finalize"; lab["finalize"] = "Finalize WASM"
|
||||||
|
ord[++ROWN] = "asyncify"; lab["asyncify"] = "Asyncify"
|
||||||
|
for (i = 1; i <= ROWN; i++) ridx[ord[i]] = i
|
||||||
|
parent["wxwidgets-configure"] = "wxwidgets"
|
||||||
|
parent["wxwidgets-compile"] = "wxwidgets"
|
||||||
|
|
||||||
|
markerCount = 0; firstTs = 0
|
||||||
|
appName = ""; appIndex = 0; appTotal = 0
|
||||||
|
curRawKey = ""; doneFlag = 0; failFlag = 0; failCode = 0
|
||||||
|
lastpct = -1; cc = 0
|
||||||
|
# heuristic-mode flags
|
||||||
|
h_app = ""; h_phase = ""; h_pct = -1; h_cc = 0; h_done = 0
|
||||||
|
}
|
||||||
|
$1 == "@KW@" {
|
||||||
|
markerCount++
|
||||||
|
ts = $2 + 0
|
||||||
|
if (firstTs == 0) firstTs = ts
|
||||||
|
kind = $3
|
||||||
|
if (kind == "app") {
|
||||||
|
appName = $4; appIndex = $5 + 0; appTotal = $6 + 0
|
||||||
|
# New app segment: reset per-segment stage tracking.
|
||||||
|
delete rowSeen; delete rowStartTs
|
||||||
|
curRawKey = ""; lastpct = -1; cc = 0
|
||||||
|
} else if (kind == "stage") {
|
||||||
|
key = $4
|
||||||
|
pk = (key in parent) ? parent[key] : key
|
||||||
|
if (!(pk in rowSeen)) { rowSeen[pk] = 1; rowStartTs[pk] = ts }
|
||||||
|
curRawKey = key; curTs = ts
|
||||||
|
if (key == "kicad-compile") { lastpct = -1; cc = 0 }
|
||||||
|
} else if (kind == "done") {
|
||||||
|
doneFlag = 1; termRaw = ts
|
||||||
|
} else if (kind == "failed") {
|
||||||
|
failFlag = 1; failCode = $4 + 0; termRaw = ts
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
# Within an active KiCad compile, track CMake percent and compiled-object count.
|
||||||
|
curRawKey == "kicad-compile" {
|
||||||
|
if (match($0, /[0-9]+%\]/)) {
|
||||||
|
p = substr($0, RSTART, RLENGTH); gsub(/[^0-9]/, "", p); lastpct = p + 0
|
||||||
|
}
|
||||||
|
if ($0 ~ /Building (C|CXX) object/) cc++
|
||||||
|
}
|
||||||
|
# ---- Heuristic fallback signal collection (only used if no @KW@ markers) ----
|
||||||
|
{
|
||||||
|
if ($0 ~ /=== Building (pcbnew|eeschema|calculator)/) {
|
||||||
|
s = $0; sub(/.*=== Building /, "", s); sub(/ .*/, "", s); h_app = s
|
||||||
|
}
|
||||||
|
if ($0 ~ /=== Building wxWidgets/) h_phase = "wxwidgets"
|
||||||
|
if ($0 ~ /Configuring KiCad with CMake/) h_phase = "kicad-configure"
|
||||||
|
if ($0 ~ /\(CMake target:/) h_phase = "kicad-compile"
|
||||||
|
if ($0 ~ /Building bitmap resources/) h_phase = "kicad-bitmaps"
|
||||||
|
if ($0 ~ /Applying asyncify transformation/) h_phase = "asyncify"
|
||||||
|
if (h_phase == "kicad-compile" && match($0, /[0-9]+%\]/)) {
|
||||||
|
p = substr($0, RSTART, RLENGTH); gsub(/[^0-9]/, "", p); h_pct = p + 0
|
||||||
|
}
|
||||||
|
if (h_phase == "kicad-compile" && $0 ~ /Building (C|CXX) object/) h_cc++
|
||||||
|
if ($0 ~ /Build complete\. Output files/ || $0 ~ /Asyncify complete/) h_done = 1
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (markerCount > 0) { render_markers(); }
|
||||||
|
else { render_heuristic(); }
|
||||||
|
}
|
||||||
|
function render_markers( state, totalEl, pk, curIdx, i, k, st, det, subdet, endts) {
|
||||||
|
state = doneFlag ? "done" : (failFlag ? "failed" : "running")
|
||||||
|
termTs = (doneFlag || failFlag) ? termRaw : NOW
|
||||||
|
totalEl = (firstTs > 0) ? termTs - firstTs : 0
|
||||||
|
|
||||||
|
# Pre-app phase (container sync runs before the first app marker).
|
||||||
|
if (appName == "") {
|
||||||
|
printf "H|-|0|0|%d|%s\n", totalEl, state
|
||||||
|
if (curRawKey == "container-sync") {
|
||||||
|
printf "R|active|Sync source to container|%s\n", fmt(termTs - rowStartTs["container-sync"])
|
||||||
|
} else {
|
||||||
|
printf "N|waiting for build to start...\n"
|
||||||
|
}
|
||||||
|
printf "B|-1\n"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "H|%s|%d|%d|%d|%s\n", appName, appIndex, appTotal, totalEl, state
|
||||||
|
pk = (curRawKey in parent) ? parent[curRawKey] : curRawKey
|
||||||
|
curIdx = (pk in ridx) ? ridx[pk] : 0
|
||||||
|
|
||||||
|
for (i = 1; i <= ROWN; i++) {
|
||||||
|
k = ord[i]
|
||||||
|
det = ""
|
||||||
|
if (doneFlag) {
|
||||||
|
st = (k in rowSeen) ? "done" : "skipped"
|
||||||
|
} else if (failFlag) {
|
||||||
|
if (i == curIdx) st = "failed"
|
||||||
|
else if (i < curIdx) st = (k in rowSeen) ? "done" : "skipped"
|
||||||
|
else st = "pending"
|
||||||
|
} else {
|
||||||
|
if (i < curIdx) st = (k in rowSeen) ? "done" : "skipped"
|
||||||
|
else if (i == curIdx) st = "active"
|
||||||
|
else st = "pending"
|
||||||
|
}
|
||||||
|
if (st == "done") {
|
||||||
|
endts = next_seen_start(i)
|
||||||
|
det = fmt(endts - rowStartTs[k])
|
||||||
|
} else if (st == "active") {
|
||||||
|
if (k == "kicad-compile") {
|
||||||
|
if (lastpct >= 0) det = sprintf("[%3d%%] %d files", lastpct, cc)
|
||||||
|
else det = sprintf("%d files", cc)
|
||||||
|
} else {
|
||||||
|
subdet = ""
|
||||||
|
if (k == "wxwidgets") {
|
||||||
|
if (curRawKey == "wxwidgets-configure") subdet = "configure "
|
||||||
|
else if (curRawKey == "wxwidgets-compile") subdet = "compile "
|
||||||
|
}
|
||||||
|
det = subdet fmt(termTs - rowStartTs[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf "R|%s|%s|%s\n", st, lab[k], det
|
||||||
|
}
|
||||||
|
if (curIdx > 0 && ord[curIdx] == "kicad-compile" && !doneFlag && !failFlag && lastpct >= 0)
|
||||||
|
printf "B|%d\n", lastpct
|
||||||
|
else
|
||||||
|
printf "B|-1\n"
|
||||||
|
}
|
||||||
|
function render_heuristic( state, order, ph, i, k, st, names) {
|
||||||
|
state = h_done ? "done" : "running"
|
||||||
|
printf "H|%s|0|0|-1|%s\n", (h_app == "" ? "-" : h_app), state
|
||||||
|
# Freshly-created/empty log at startup: nothing to infer yet.
|
||||||
|
if (h_app == "" && h_phase == "" && !h_done) {
|
||||||
|
printf "N|starting build...\n"
|
||||||
|
printf "B|-1\n"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
printf "N|(no progress markers in this log - inferred from log text)\n"
|
||||||
|
# Reduced ordered phase set we can detect heuristically.
|
||||||
|
split("wxwidgets kicad-configure kicad-compile kicad-bitmaps asyncify", order, " ")
|
||||||
|
names["wxwidgets"]="wxWidgets"; names["kicad-configure"]="KiCad configure (CMake)"
|
||||||
|
names["kicad-compile"]="KiCad compile"; names["kicad-bitmaps"]="Bitmap resources"
|
||||||
|
names["asyncify"]="Asyncify"
|
||||||
|
ph = 0
|
||||||
|
for (i = 1; i <= 5; i++) if (order[i] == h_phase) ph = i
|
||||||
|
for (i = 1; i <= 5; i++) {
|
||||||
|
k = order[i]
|
||||||
|
if (h_done) st = "done"
|
||||||
|
else if (i < ph) st = "done"
|
||||||
|
else if (i == ph) st = "active"
|
||||||
|
else st = "pending"
|
||||||
|
det = ""
|
||||||
|
if (st == "active" && k == "kicad-compile")
|
||||||
|
det = (h_pct >= 0) ? sprintf("[%3d%%] %d files", h_pct, h_cc) : sprintf("%d files", h_cc)
|
||||||
|
printf "R|%s|%s|%s\n", st, names[k], det
|
||||||
|
}
|
||||||
|
printf "B|%s\n", (h_phase == "kicad-compile" && h_pct >= 0) ? h_pct : "-1"
|
||||||
|
}
|
||||||
|
AWK
|
||||||
|
|
||||||
|
# Draw a width-w progress bar for percent p.
|
||||||
|
bar() {
|
||||||
|
local p="$1" w=24 filled i out=""
|
||||||
|
filled=$(( p * w / 100 ))
|
||||||
|
for ((i = 0; i < w; i++)); do
|
||||||
|
if (( i < filled )); then out+="#"; else out+="."; fi
|
||||||
|
done
|
||||||
|
printf '%s' "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Render one snapshot from awk output ($1) to the screen.
|
||||||
|
render() {
|
||||||
|
local data="$1" tick="$2"
|
||||||
|
local spin="${SPINNER:$((tick % 4)):1}"
|
||||||
|
local app idx total elapsed state note="" barpct=-1
|
||||||
|
local -a rows=()
|
||||||
|
|
||||||
|
while IFS='|' read -r kind a b c d e; do
|
||||||
|
case "$kind" in
|
||||||
|
H) app="$a"; idx="$b"; total="$c"; elapsed="$d"; state="$e" ;;
|
||||||
|
R) rows+=("$a|$b|$c") ;;
|
||||||
|
B) barpct="$a" ;;
|
||||||
|
N) note="$a" ;;
|
||||||
|
esac
|
||||||
|
done <<< "$data"
|
||||||
|
|
||||||
|
local title="KiCad WASM build"
|
||||||
|
[[ "$app" != "-" && -n "$app" ]] && title+=" ${C_BOLD}${app}${C_RESET}"
|
||||||
|
[[ "${total:-0}" -gt 0 ]] && title+=" (${idx}/${total})"
|
||||||
|
case "$state" in
|
||||||
|
done) title+=" ${C_GREEN}[done]${C_RESET}" ;;
|
||||||
|
failed) title+=" ${C_RED}[FAILED]${C_RESET}" ;;
|
||||||
|
*) title+=" ${C_CYAN}[building ${spin}]${C_RESET}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf '%s== %s ==%s\n' "$C_BOLD" "$title" "$C_RESET"
|
||||||
|
printf '%s\n' "------------------------------------------------------------"
|
||||||
|
[[ -n "$note" ]] && printf ' %s%s%s\n' "$C_DIM" "$note" "$C_RESET"
|
||||||
|
|
||||||
|
local r st label detail glyph color
|
||||||
|
for r in ${rows[@]+"${rows[@]}"}; do
|
||||||
|
IFS='|' read -r st label detail <<< "$r"
|
||||||
|
case "$st" in
|
||||||
|
done) glyph="[x]"; color="$C_GREEN" ;;
|
||||||
|
active) glyph="[${spin}]"; color="$C_CYAN" ;;
|
||||||
|
pending) glyph="[ ]"; color="$C_DIM" ;;
|
||||||
|
skipped) glyph="[-]"; color="$C_DIM" ;;
|
||||||
|
failed) glyph="[!]"; color="$C_RED" ;;
|
||||||
|
*) glyph="[ ]"; color="$C_RESET" ;;
|
||||||
|
esac
|
||||||
|
printf ' %s%s %-24s%s %s\n' "$color" "$glyph" "$label" "$C_RESET" "$detail"
|
||||||
|
if [[ "$st" == "active" && "$barpct" -ge 0 ]]; then
|
||||||
|
printf ' %s %s%%\n' "$(bar "$barpct")" "$barpct"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "------------------------------------------------------------"
|
||||||
|
local elapsed_str="--"
|
||||||
|
[[ "${elapsed:--1}" -ge 0 ]] 2>/dev/null && elapsed_str="$(fmt_sec "$elapsed")"
|
||||||
|
printf ' elapsed %s · log %s\n' "$elapsed_str" "$(basename "${CUR_LOG:-?}")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Seconds -> "Xm YYs" (bash side, for the footer).
|
||||||
|
fmt_sec() {
|
||||||
|
local s="$1" m
|
||||||
|
(( s < 0 )) && s=0
|
||||||
|
m=$(( s / 60 )); s=$(( s % 60 ))
|
||||||
|
if (( m > 0 )); then printf '%dm%02ds' "$m" "$s"; else printf '%ds' "$s"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot() {
|
||||||
|
local now data
|
||||||
|
now=$(date +%s)
|
||||||
|
data=$(awk -v NOW="$now" "$AWK_PROG" "$CUR_LOG")
|
||||||
|
render "$data" "${1:-0}"
|
||||||
|
# Surface the build state to the caller via global.
|
||||||
|
LAST_STATE=$(printf '%s\n' "$data" | awk -F'|' '$1=="H"{print $6}')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write a pre-built frame in place, without clearing the screen first (no flash).
|
||||||
|
# Home the cursor, erase each line to its end (handles a shorter new line), then
|
||||||
|
# erase to end of screen (handles a shorter new frame, e.g. the bar/note lines
|
||||||
|
# disappearing). On a non-TTY C_HOME/C_EL/C_ED are empty, so this is plain printing.
|
||||||
|
paint() {
|
||||||
|
local frame="$1" line
|
||||||
|
printf '%s' "$C_HOME"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
printf '%s%s\n' "$line" "$C_EL"
|
||||||
|
done <<< "$frame"
|
||||||
|
printf '%s' "$C_ED"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- one-shot mode -----------------------------------------------------------
|
||||||
|
if [[ "$ONCE" -eq 1 ]]; then
|
||||||
|
CUR_LOG="${LOGFILE:-$(find_newest_log)}"
|
||||||
|
if [[ -z "$CUR_LOG" || ! -f "$CUR_LOG" ]]; then
|
||||||
|
echo "No log found in ${LOGS_ROOT}/${DIR}/ (and none given)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
snapshot 0
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- live follow mode --------------------------------------------------------
|
||||||
|
tick=0
|
||||||
|
trap 'tput cnorm 2>/dev/null; printf "%s" "$C_RESET"; echo; exit 0' INT TERM
|
||||||
|
[[ -t 1 ]] && tput civis 2>/dev/null
|
||||||
|
# Clear once up front; every later frame overwrites in place via paint() so the
|
||||||
|
# screen is never blanked between refreshes (no flash).
|
||||||
|
[[ -t 1 ]] && tput clear 2>/dev/null
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# Re-resolve newest log each tick (unless a specific file was given) so the
|
||||||
|
# monitor latches onto a build started after it.
|
||||||
|
if [[ -z "$LOGFILE" ]]; then CUR_LOG="$(find_newest_log)"; else CUR_LOG="$LOGFILE"; fi
|
||||||
|
|
||||||
|
if [[ -z "$CUR_LOG" || ! -f "$CUR_LOG" ]]; then
|
||||||
|
paint "$(printf '%sWaiting for a build log in %s/%s/ ...%s\n%s(spinner %s)%s' \
|
||||||
|
"$C_DIM" "$LOGS_ROOT" "$DIR" "$C_RESET" \
|
||||||
|
"$C_DIM" "${SPINNER:$((tick % 4)):1}" "$C_RESET")"
|
||||||
|
sleep 1; tick=$((tick + 1)); continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fetch state once, derive build state in this scope (a $(...) capture of
|
||||||
|
# render() can't propagate a global), then render to a string and paint it.
|
||||||
|
now=$(date +%s)
|
||||||
|
data=$(awk -v NOW="$now" "$AWK_PROG" "$CUR_LOG")
|
||||||
|
state=$(printf '%s\n' "$data" | awk -F'|' '$1=="H"{print $6}')
|
||||||
|
paint "$(render "$data" "$tick")"
|
||||||
|
|
||||||
|
# In managed mode the parent owns our lifecycle and will kill us; keep painting
|
||||||
|
# the final frame until then. Otherwise self-exit once the build is finished.
|
||||||
|
if [[ "$MANAGED" -eq 0 && ( "${state:-running}" == "done" || "${state:-running}" == "failed" ) ]]; then
|
||||||
|
tput cnorm 2>/dev/null
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
tick=$((tick + 1))
|
||||||
|
done
|
||||||
|
|
@ -6,6 +6,8 @@ source "$(dirname "$0")/common/logging.sh"
|
||||||
|
|
||||||
# Source common environment (sets up local emsdk)
|
# Source common environment (sets up local emsdk)
|
||||||
source "$(dirname "$0")/common/env.sh"
|
source "$(dirname "$0")/common/env.sh"
|
||||||
|
# Build-progress markers (parsed by scripts/build-monitor.sh).
|
||||||
|
source "$(dirname "$0")/common/stages.sh"
|
||||||
# This builds the GUI-enabled wxWidgets needed for KiCad
|
# This builds the GUI-enabled wxWidgets needed for KiCad
|
||||||
#
|
#
|
||||||
# Prerequisites:
|
# Prerequisites:
|
||||||
|
|
@ -98,6 +100,7 @@ if [ $NEEDS_CONFIGURE -eq 1 ]; then
|
||||||
# --without-libtiff Avoid external dependencies
|
# --without-libtiff Avoid external dependencies
|
||||||
# --disable-xlocale Browser environment handles locale
|
# --disable-xlocale Browser environment handles locale
|
||||||
|
|
||||||
|
kw_stage wxwidgets-configure
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Configuring ==="
|
echo "=== Configuring ==="
|
||||||
|
|
||||||
|
|
@ -162,6 +165,7 @@ if [ $NEEDS_CONFIGURE -eq 1 ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build wxWidgets
|
# Build wxWidgets
|
||||||
|
kw_stage wxwidgets-compile
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Building wxWidgets (using ${JOBS} parallel jobs) ==="
|
echo "=== Building wxWidgets (using ${JOBS} parallel jobs) ==="
|
||||||
emmake make -j${JOBS}
|
emmake make -j${JOBS}
|
||||||
|
|
|
||||||
|
|
@ -38,15 +38,46 @@ _LOG_FILE="${_LOGS_DIR}/${_TIMESTAMP}.log"
|
||||||
|
|
||||||
mkdir -p "${_LOGS_DIR}"
|
mkdir -p "${_LOGS_DIR}"
|
||||||
|
|
||||||
|
# Optionally drive the live progress dashboard in this terminal (the re-exec'd build
|
||||||
|
# below has its stdout redirected to the log, so THIS process — which still owns the
|
||||||
|
# TTY — is the one that can paint it). Opt-in: a build script sets KICAD_MONITOR=1
|
||||||
|
# before sourcing this file. Skipped for non-interactive stdout or KICAD_NO_MONITOR=1.
|
||||||
|
_MONITOR="${_PROJECT_ROOT}/scripts/build-monitor.sh"
|
||||||
|
_USE_MONITOR=0
|
||||||
|
if [[ "${KICAD_MONITOR:-0}" == "1" ]] && [[ -z "${KICAD_NO_MONITOR:-}" ]] \
|
||||||
|
&& [[ -t 1 ]] && [[ -x "${_MONITOR}" ]]; then
|
||||||
|
_USE_MONITOR=1
|
||||||
|
fi
|
||||||
|
|
||||||
# Show log path before re-exec
|
# Show log path before re-exec
|
||||||
echo "Logging to: ${_LOG_FILE}"
|
echo "Logging to: ${_LOG_FILE}"
|
||||||
|
# Only suggest the manual command when we're NOT auto-launching the dashboard.
|
||||||
|
[[ ${_USE_MONITOR} -eq 0 ]] && \
|
||||||
|
echo "Watch progress: ${_MONITOR} --dir ${_SCRIPT_NAME}"
|
||||||
|
|
||||||
# Run the calling script with output redirected
|
# Run the calling script with output redirected
|
||||||
# This is equivalent to: ./script.sh > logfile 2>&1
|
# This is equivalent to: ./script.sh > logfile 2>&1
|
||||||
# and reliably captures ALL output including docker compose TTY messages
|
# and reliably captures ALL output including docker compose TTY messages
|
||||||
export KICAD_LOG_NESTED=1
|
export KICAD_LOG_NESTED=1
|
||||||
"${_CALLER_SCRIPT}" "$@" > "${_LOG_FILE}" 2>&1
|
export KICAD_LOG_FILE="${_LOG_FILE}" # let the build body know its own log path
|
||||||
_EXIT_CODE=$?
|
|
||||||
|
if [[ ${_USE_MONITOR} -eq 1 ]]; then
|
||||||
|
: > "${_LOG_FILE}" # create now so the follower has a file
|
||||||
|
"${_MONITOR}" "${_LOG_FILE}" & # live dashboard on this terminal
|
||||||
|
_MON_PID=$!
|
||||||
|
# Tear down the dashboard if the user interrupts the build.
|
||||||
|
trap 'kill ${_MON_PID} 2>/dev/null' INT TERM
|
||||||
|
"${_CALLER_SCRIPT}" "$@" >> "${_LOG_FILE}" 2>&1
|
||||||
|
_EXIT_CODE=$?
|
||||||
|
# The build body's EXIT trap writes the done/failed marker, so the monitor paints
|
||||||
|
# its final frame in place and self-exits — no separate redraw needed (which would
|
||||||
|
# otherwise print the frame a second time).
|
||||||
|
wait "${_MON_PID}" 2>/dev/null
|
||||||
|
trap - INT TERM
|
||||||
|
else
|
||||||
|
"${_CALLER_SCRIPT}" "$@" > "${_LOG_FILE}" 2>&1
|
||||||
|
_EXIT_CODE=$?
|
||||||
|
fi
|
||||||
|
|
||||||
# Print completion message (this runs AFTER the script finishes)
|
# Print completion message (this runs AFTER the script finishes)
|
||||||
if [[ ${_EXIT_CODE} -eq 0 ]]; then
|
if [[ ${_EXIT_CODE} -eq 0 ]]; then
|
||||||
|
|
|
||||||
25
scripts/common/stages.sh
Executable file
25
scripts/common/stages.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Emit machine-parseable build-progress markers into the log stream.
|
||||||
|
#
|
||||||
|
# All build output is captured by logging.sh into logs/<script>/<ts>.log, and
|
||||||
|
# scripts/build-monitor.sh parses these @KW@ lines to render live progress.
|
||||||
|
# These are plain printf so they work identically on the host (docker/build.sh)
|
||||||
|
# and inside `docker compose exec` (build-kicad-target.sh, build-wxuniversal-wasm.sh):
|
||||||
|
# the container's stdout flows back through build.sh's redirected stdout into the
|
||||||
|
# same host log, so a single log is the source of truth for the whole build.
|
||||||
|
#
|
||||||
|
# Each marker carries an epoch timestamp so the monitor can reconstruct per-stage
|
||||||
|
# elapsed time even when it attaches mid-build or runs --once on a finished log.
|
||||||
|
#
|
||||||
|
# Marker grammar (one per line):
|
||||||
|
# @KW@ <epoch> stage <key>
|
||||||
|
# @KW@ <epoch> app <name> <index> <total>
|
||||||
|
# @KW@ <epoch> done
|
||||||
|
# @KW@ <epoch> failed <exit_code>
|
||||||
|
#
|
||||||
|
# The monitor owns human-readable labels (key -> label), so keys stay terse here.
|
||||||
|
|
||||||
|
kw_stage() { printf '@KW@ %s stage %s\n' "$(date +%s)" "$1"; }
|
||||||
|
kw_app() { printf '@KW@ %s app %s %s %s\n' "$(date +%s)" "$1" "$2" "$3"; }
|
||||||
|
kw_done() { printf '@KW@ %s done\n' "$(date +%s)"; }
|
||||||
|
kw_fail() { printf '@KW@ %s failed %s\n' "$(date +%s)" "${1:-1}"; }
|
||||||
|
|
@ -54,6 +54,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "${SCRIPT_DIR}/../common/env.sh"
|
source "${SCRIPT_DIR}/../common/env.sh"
|
||||||
source "${SCRIPT_DIR}/../common/versions.sh"
|
source "${SCRIPT_DIR}/../common/versions.sh"
|
||||||
source "${SCRIPT_DIR}/../common/functions.sh"
|
source "${SCRIPT_DIR}/../common/functions.sh"
|
||||||
|
source "${SCRIPT_DIR}/../common/stages.sh"
|
||||||
|
|
||||||
KICAD_DIR="${PROJECT_ROOT}/kicad"
|
KICAD_DIR="${PROJECT_ROOT}/kicad"
|
||||||
KICAD_BUILD="${BUILD_ROOT}/kicad-${APP_NAME}"
|
KICAD_BUILD="${BUILD_ROOT}/kicad-${APP_NAME}"
|
||||||
|
|
@ -155,6 +156,7 @@ fi
|
||||||
# Step 2: Build dependencies
|
# Step 2: Build dependencies
|
||||||
# Note: --with-occ for OpenCASCADE, but NOT ngspice since KICAD_SPICE=OFF
|
# Note: --with-occ for OpenCASCADE, but NOT ngspice since KICAD_SPICE=OFF
|
||||||
if [ $SKIP_DEPS -eq 0 ]; then
|
if [ $SKIP_DEPS -eq 0 ]; then
|
||||||
|
kw_stage deps
|
||||||
log_info "Building dependencies..."
|
log_info "Building dependencies..."
|
||||||
"${SCRIPT_DIR}/../deps/build-all-deps.sh" --with-occ
|
"${SCRIPT_DIR}/../deps/build-all-deps.sh" --with-occ
|
||||||
else
|
else
|
||||||
|
|
@ -167,6 +169,7 @@ fi
|
||||||
# scripts that want to know if KiCad was ever built successfully.
|
# scripts that want to know if KiCad was ever built successfully.
|
||||||
|
|
||||||
# Step 4: Build wxWidgets (incremental - only recompiles changed files)
|
# Step 4: Build wxWidgets (incremental - only recompiles changed files)
|
||||||
|
kw_stage wxwidgets
|
||||||
log_info "Building wxWidgets..."
|
log_info "Building wxWidgets..."
|
||||||
"${SCRIPT_DIR}/../build-wxuniversal-wasm.sh" --no-clean
|
"${SCRIPT_DIR}/../build-wxuniversal-wasm.sh" --no-clean
|
||||||
|
|
||||||
|
|
@ -208,6 +211,7 @@ STUBS_DIR="${PROJECT_ROOT}/wasm/stubs"
|
||||||
STUBS_BUILD="${BUILD_ROOT}/stubs"
|
STUBS_BUILD="${BUILD_ROOT}/stubs"
|
||||||
mkdir -p "${STUBS_BUILD}"
|
mkdir -p "${STUBS_BUILD}"
|
||||||
|
|
||||||
|
kw_stage kicad-stubs
|
||||||
log_info "Building stub libraries..."
|
log_info "Building stub libraries..."
|
||||||
# Compile libgit2 stub
|
# Compile libgit2 stub
|
||||||
emcc -c "${STUBS_DIR}/libgit2_stub.c" -o "${STUBS_BUILD}/libgit2_stub.o"
|
emcc -c "${STUBS_DIR}/libgit2_stub.c" -o "${STUBS_BUILD}/libgit2_stub.o"
|
||||||
|
|
@ -303,6 +307,7 @@ EMBIND_SRC="${PROJECT_ROOT}/wasm/bindings/${APP_NAME}_embind.cpp"
|
||||||
|
|
||||||
# Step 7: Configure KiCad with CMake
|
# Step 7: Configure KiCad with CMake
|
||||||
# We use CMAKE_MODULE_PATH to inject our compatibility layer
|
# We use CMAKE_MODULE_PATH to inject our compatibility layer
|
||||||
|
kw_stage kicad-configure
|
||||||
log_info "Configuring KiCad with CMake..."
|
log_info "Configuring KiCad with CMake..."
|
||||||
|
|
||||||
# Use ccache if available (CMAKE_*_COMPILER_LAUNCHER is the proper CMake way)
|
# Use ccache if available (CMAKE_*_COMPILER_LAUNCHER is the proper CMake way)
|
||||||
|
|
@ -392,11 +397,13 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Step 8: Build the app target
|
# Step 8: Build the app target
|
||||||
|
kw_stage kicad-compile
|
||||||
log_info "Building ${APP_NAME} (CMake target: ${KICAD_TARGET})..."
|
log_info "Building ${APP_NAME} (CMake target: ${KICAD_TARGET})..."
|
||||||
emmake make -j${JOBS} "${KICAD_TARGET}"
|
emmake make -j${JOBS} "${KICAD_TARGET}"
|
||||||
|
|
||||||
# Step 8.1: Build bitmap resources (images.tar.gz)
|
# Step 8.1: Build bitmap resources (images.tar.gz)
|
||||||
# This creates the icon archive that KiCad loads at runtime
|
# This creates the icon archive that KiCad loads at runtime
|
||||||
|
kw_stage kicad-bitmaps
|
||||||
log_info "Building bitmap resources..."
|
log_info "Building bitmap resources..."
|
||||||
emmake make bitmap_archive_build
|
emmake make bitmap_archive_build
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue